16
16
from distutils .version import LooseVersion
17
17
from urllib .request import urlopen
18
18
19
- regex = re .compile (
20
- r"^(?P<filename>[^:]*):(?P<line>\d*):(?:(?P<column>\d*):)? (?P<message_id>[^:]*): (?P<message>.*?) \((?P<message_url>.*?)\)$" ,
21
- re .MULTILINE | re .DOTALL
19
+ BUILDIFIER_VERSION_PATTERN = re .compile (
20
+ r"^buildifier version: ([\.\w]+)$" , re .MULTILINE
22
21
)
23
22
24
- BUILDIFIER_VERSION_PATTERN = re .compile (r"^buildifier version: ([\.\w]+)$" , re .MULTILINE )
25
-
26
23
# https://github.com/bazelbuild/buildtools/blob/master/buildifier/buildifier.go#L333
27
24
# Buildifier error code for "needs formatting". We should fail on all other error codes > 0
28
25
# since they indicate a problem in how Buildifier is used.
34
31
35
32
BUILDIFIER_RELEASES_URL = "https://api.github.com/repos/bazelbuild/buildtools/releases"
36
33
37
- BUILDIFIER_DEFAULT_DISPLAY_URL = "https://github.com/bazelbuild/buildtools/tree/master/buildifier"
34
+ BUILDIFIER_DEFAULT_DISPLAY_URL = (
35
+ "https://github.com/bazelbuild/buildtools/tree/master/buildifier"
36
+ )
38
37
39
38
40
39
def eprint (* args , ** kwargs ):
@@ -51,7 +50,14 @@ def upload_output(output):
51
50
52
51
eprint ("+++ :buildkite: Uploading output via 'buildkite annotate'" )
53
52
result = subprocess .run (
54
- ["buildkite-agent" , "annotate" , "--style" , "warning" , "--context" , "buildifier" ],
53
+ [
54
+ "buildkite-agent" ,
55
+ "annotate" ,
56
+ "--style" ,
57
+ "warning" ,
58
+ "--context" ,
59
+ "buildifier" ,
60
+ ],
55
61
input = output .encode (locale .getpreferredencoding (False )),
56
62
)
57
63
if result .returncode != 0 :
@@ -66,14 +72,18 @@ def print_error(failing_task, message):
66
72
output = "##### :bazel: buildifier: error while {}:\n " .format (failing_task )
67
73
output += "<pre><code>{}</code></pre>" .format (html .escape (message ))
68
74
if "BUILDKITE_JOB_ID" in os .environ :
69
- output += "\n \n See [job {job}](#{job})\n " .format (job = os .environ ["BUILDKITE_JOB_ID" ])
75
+ output += "\n \n See [job {job}](#{job})\n " .format (
76
+ job = os .environ ["BUILDKITE_JOB_ID" ]
77
+ )
70
78
71
79
upload_output (output )
72
80
73
81
74
82
def get_file_url (filename , line ):
75
83
commit = os .environ .get ("BUILDKITE_COMMIT" )
76
- repo = os .environ .get ("BUILDKITE_PULL_REQUEST_REPO" , os .environ .get ("BUILDKITE_REPO" , None ))
84
+ repo = os .environ .get (
85
+ "BUILDKITE_PULL_REQUEST_REPO" , os .environ .get ("BUILDKITE_REPO" , None )
86
+ )
77
87
if not commit or not repo :
78
88
return None
79
89
@@ -89,7 +99,7 @@ def get_file_url(filename, line):
89
99
return None
90
100
91
101
92
- def run_buildifier (binary , flags , files = None , version = None , what = None ):
102
+ def run_buildifier (binary , flags , version = None , what = None ):
93
103
label = "+++ :bazel: Running "
94
104
if version :
95
105
label += "Buildifier " + version
@@ -100,11 +110,9 @@ def run_buildifier(binary, flags, files=None, version=None, what=None):
100
110
101
111
eprint (label )
102
112
103
- args = [binary ] + flags
104
- if files :
105
- args += files
106
-
107
- return subprocess .run (args , capture_output = True , universal_newlines = True )
113
+ return subprocess .run (
114
+ [binary ] + flags , capture_output = True , universal_newlines = True
115
+ )
108
116
109
117
110
118
def create_heading (issue_type , issue_count ):
@@ -134,15 +142,23 @@ def get_releases():
134
142
body = res .read ()
135
143
content = body .decode (res .info ().get_content_charset ("iso-8859-1" ))
136
144
137
- return {r ["tag_name" ]: get_release_urls (r ) for r in json .loads (content ) if not r ["prerelease" ]}
145
+ return {
146
+ r ["tag_name" ]: get_release_urls (r )
147
+ for r in json .loads (content )
148
+ if not r ["prerelease" ]
149
+ }
138
150
139
151
140
152
def get_release_urls (release ):
141
153
buildifier_assets = [
142
- a for a in release ["assets" ] if a ["name" ] in ("buildifier" , "buildifier-linux-amd64" )
154
+ a
155
+ for a in release ["assets" ]
156
+ if a ["name" ] in ("buildifier" , "buildifier-linux-amd64" )
143
157
]
144
158
if not buildifier_assets :
145
- raise Exception ("There is no Buildifier binary for release {}" .format (release ["tag_name" ]))
159
+ raise Exception (
160
+ "There is no Buildifier binary for release {}" .format (release ["tag_name" ])
161
+ )
146
162
147
163
return release ["html_url" ], buildifier_assets [0 ]["browser_download_url" ]
148
164
@@ -156,6 +172,31 @@ def download_buildifier(url):
156
172
return path
157
173
158
174
175
+ def format_lint_warning (filename , warning ):
176
+ line_number = warning ["start" ]["line" ]
177
+ link_start , link_end , column_text = "" , "" , ""
178
+
179
+ file_url = get_file_url (filename , line_number )
180
+ if file_url :
181
+ link_start = '<a href="{}">' .format (file_url )
182
+ link_end = "</a>"
183
+
184
+ column = warning ["start" ].get ("column" )
185
+ if column :
186
+ column_text = ":{}" .format (column )
187
+
188
+ return '{link_start}{filename}:{line}{column}{link_end}: <a href="{help_url}">{category}</a>: {message}' .format (
189
+ link_start = link_start ,
190
+ filename = filename ,
191
+ line = line_number ,
192
+ column = column_text ,
193
+ link_end = link_end ,
194
+ help_url = warning ["url" ],
195
+ category = warning ["category" ],
196
+ message = warning ["message" ],
197
+ )
198
+
199
+
159
200
def main (argv = None ):
160
201
if argv is None :
161
202
argv = sys .argv [1 :]
@@ -173,102 +214,75 @@ def main(argv=None):
173
214
print_error ("downloading Buildifier" , str (ex ))
174
215
return 1
175
216
176
- # Gather all files to process.
177
- eprint ("+++ :female-detective: Looking for WORKSPACE, BUILD, BUILD.bazel and *.bzl files" )
178
- files = []
179
- build_bazel_found = False
180
- for root , _ , filenames in os .walk ("." ):
181
- for filename in filenames :
182
- if fnmatch .fnmatch (filename , "BUILD.bazel" ):
183
- build_bazel_found = True
184
- for pattern in ("WORKSPACE" , "BUILD" , "BUILD.bazel" , "*.bzl" ):
185
- if fnmatch .fnmatch (filename , pattern ):
186
- files .append (os .path .relpath (os .path .join (root , filename )))
187
- if build_bazel_found :
188
- eprint (
189
- "Found BUILD.bazel files in the workspace, thus ignoring BUILD files without suffix."
190
- )
191
- files = [fname for fname in files if not fnmatch .fnmatch (os .path .basename (fname ), "BUILD" )]
192
- if not files :
193
- eprint ("No files found, exiting." )
194
- return 0
195
-
196
- files = sorted (files )
197
-
198
217
# Determine Buildifier version if the user did not request a specific version.
199
218
if not version :
200
219
eprint ("+++ :female-detective: Detecting Buildifier version" )
201
- version_result = run_buildifier (buildifier_binary , ["--version" ], what = "Version info" )
220
+ version_result = run_buildifier (
221
+ buildifier_binary , ["--version" ], what = "Version info"
222
+ )
202
223
match = BUILDIFIER_VERSION_PATTERN .search (version_result .stdout )
203
224
version = match .group (1 ) if match and match .group (1 ) != "redacted" else None
204
225
205
- # Run formatter before linter since --lint=warn implies --mode=fix,
206
- # thus fixing any format issues.
207
- formatter_result = run_buildifier (
208
- buildifier_binary , ["--mode=check" ], files = files , version = version , what = "Format check"
209
- )
210
- if formatter_result .returncode and formatter_result .returncode != BUILDIFIER_FORMAT_ERROR_CODE :
211
- print_error ("checking format" , formatter_result .stderr )
212
- return formatter_result .returncode
213
-
214
- # Format: "<file name> # reformated"
215
- unformatted_files = [l .partition (" " )[0 ] for l in formatter_result .stderr .splitlines ()]
216
- if unformatted_files :
217
- eprint (
218
- "+++ :construction: Found {} file(s) that must be formatted" .format (
219
- len (unformatted_files )
220
- )
221
- )
222
-
223
- lint_flags = ["--lint=warn" ]
226
+ flags = ["--mode=check" , "--lint=warn" ]
224
227
warnings = os .getenv (WARNINGS_ENV_VAR )
225
228
if warnings :
226
229
eprint ("Running Buildifier with the following warnings: {}" .format (warnings ))
227
- lint_flags .append ("--warnings={}" .format (warnings ))
230
+ flags .append ("--warnings={}" .format (warnings ))
228
231
229
- linter_result = run_buildifier (
230
- buildifier_binary , lint_flags , files = files , version = version , what = "Lint checks"
232
+ result = run_buildifier (
233
+ buildifier_binary ,
234
+ flags + ["--format=json" , "-r" , "." ],
235
+ version = version ,
236
+ what = "Format & lint checks" ,
231
237
)
232
- if linter_result .returncode == 0 and not unformatted_files :
238
+
239
+ if result .returncode and result .returncode != BUILDIFIER_FORMAT_ERROR_CODE :
240
+ print_error ("Buildifier failed" , result .stderr )
241
+ return result .returncode
242
+
243
+ data = json .loads (result .stdout )
244
+ if data ["success" ]:
233
245
# If buildifier was happy, there's nothing left to do for us.
234
246
eprint ("+++ :tada: Buildifier found nothing to complain about" )
235
247
return 0
236
248
249
+ unformatted_files = []
250
+ lint_findings = []
251
+ for file in data ["files" ]:
252
+ filename = file ["filename" ]
253
+
254
+ if not file ["formatted" ]:
255
+ unformatted_files .append (filename )
256
+
257
+ for warning in file ["warnings" ]:
258
+ lint_findings .append (format_lint_warning (filename , warning ))
259
+
237
260
output = ""
238
261
if unformatted_files :
262
+ eprint (
263
+ "+++ :construction: Found {} file(s) that must be formatted" .format (
264
+ len (unformatted_files )
265
+ )
266
+ )
239
267
output = create_heading ("format" , len (unformatted_files ))
240
268
display_version = " {}" .format (version ) if version else ""
241
269
output += (
242
- " Please download <a href=\ " {}\ " >buildifier{}</a> and run the following "
270
+ ' Please download <a href="{}">buildifier{}</a> and run the following '
243
271
"command in your workspace:<br/><pre><code>buildifier {}</code></pre>"
244
272
"\n " .format (display_url , display_version , " " .join (unformatted_files ))
245
273
)
246
274
247
- # Parse output.
248
- if linter_result .returncode :
249
- eprint ("+++ :gear: Parsing buildifier output" )
250
- findings = list (regex .finditer (linter_result .stderr ))
251
- output += create_heading ("lint" , len (findings ))
275
+ if lint_findings :
276
+ eprint ("+++ :gear: Rendering lint warnings" )
277
+ output += create_heading ("lint" , len (lint_findings ))
252
278
output += "<pre><code>"
253
- for finding in findings :
254
- file_url = get_file_url (finding ["filename" ], finding ["line" ])
255
- if file_url :
256
- output += '<a href="{}">{}:{}</a>:' .format (
257
- file_url , finding ["filename" ], finding ["line" ]
258
- )
259
- else :
260
- output += "{}:{}:" .format (finding ["filename" ], finding ["line" ])
261
- if finding ["column" ]:
262
- output += "{}:" .format (finding ["column" ])
263
- output += ' <a href="{}">{}</a>: {}\n ' .format (
264
- finding ["message_url" ], finding ["message_id" ], finding ["message" ]
265
- )
279
+ output += "\n " .join (lint_findings )
266
280
output = output .strip () + "</pre></code>"
267
281
268
282
upload_output (output )
269
283
270
284
# Preserve buildifier's exit code.
271
- return max ( linter_result . returncode , formatter_result . returncode )
285
+ return BUILDIFIER_FORMAT_ERROR_CODE
272
286
273
287
274
288
if __name__ == "__main__" :
0 commit comments