Flutter Engine
The Flutter Engine
PRESUBMIT.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5"""Top-level presubmit script for Dart.
6
7See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8for more details about the presubmit API built into gcl.
9"""
10
11import datetime
12import importlib.util
13import importlib.machinery
14import os
15import os.path
16from typing import Callable
17import scm
18import subprocess
19import tempfile
20import platform
21
22USE_PYTHON3 = True
23
24
25def is_cpp_file(path):
26 return path.endswith('.cc') or path.endswith('.h')
27
28
29def is_dart_file(path):
30 return path.endswith('.dart')
31
32
33def get_old_contents(input_api, path):
34 local_root = input_api.change.RepositoryRoot()
35 upstream = input_api.change._upstream
36 return scm.GIT.Capture(['show', upstream + ':' + path],
37 cwd=local_root,
38 strip_out=False)
39
40
41def files_to_check_for_format(input_api, extension, exclude_folders):
42 files = []
43 exclude_folders += [
44 "pkg/front_end/testcases/", "pkg/front_end/parser_testcases/"
45 ]
46 for git_file in input_api.AffectedTextFiles():
47 local_path = git_file.LocalPath()
48 if not local_path.endswith(extension):
49 continue
50 if any([local_path.startswith(f) for f in exclude_folders]):
51 continue
52 files.append(git_file)
53 return files
54
55
56def _CheckFormat(input_api, identification, extension, windows,
57 hasFormatErrors: Callable[[str, list, str],
58 bool], exclude_folders):
59 files = files_to_check_for_format(input_api, extension, exclude_folders)
60 if not files:
61 return []
62
63 # Check for formatting errors in bulk first. This is orders of magnitude
64 # faster than checking file-by-file on large changes with hundreds of files.
65 if not hasFormatErrors(filenames=[f.AbsoluteLocalPath() for f in files]):
66 return []
67
68 print("Formatting errors found, comparing against old versions.")
69 unformatted_files = []
70 for git_file in files:
71 filename = git_file.AbsoluteLocalPath()
72 if hasFormatErrors(filename=filename):
73 old_version_has_errors = False
74 try:
75 path = git_file.LocalPath()
76 if windows:
77 # Git expects a linux style path.
78 path = path.replace(os.sep, '/')
79 if hasFormatErrors(contents=get_old_contents(input_api, path)):
80 old_version_has_errors = True
81 except subprocess.CalledProcessError as e:
82 old_version_has_errors = False
83
84 if old_version_has_errors:
85 print("WARNING: %s has existing and possibly new %s issues" %
86 (git_file.LocalPath(), identification))
87 else:
88 unformatted_files.append(filename)
89
90 return unformatted_files
91
92
93def load_source(modname, filename):
94 loader = importlib.machinery.SourceFileLoader(modname, filename)
95 spec = importlib.util.spec_from_file_location(modname,
96 filename,
97 loader=loader)
98 module = importlib.util.module_from_spec(spec)
99 # The module is always executed and not cached in sys.modules.
100 # Uncomment the following line to cache the module.
101 # sys.modules[module.__name__] = module
102 loader.exec_module(module)
103 return module
104
105
106def _CheckDartFormat(input_api, output_api):
107 local_root = input_api.change.RepositoryRoot()
108 utils = load_source('utils', os.path.join(local_root, 'tools', 'utils.py'))
109
110 dart = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dart')
111
112 windows = utils.GuessOS() == 'win32'
113 if windows:
114 dart += '.exe'
115
116 if not os.path.isfile(dart):
117 print('WARNING: dart not found: %s' % (dart))
118 return []
119
120 dartFixes = [
121 '--fix-named-default-separator',
122 ]
123
124 def HasFormatErrors(filename: str = None,
125 filenames: list = None,
126 contents: str = None):
127 # Don't look for formatting errors in multitests. Since those are very
128 # sensitive to whitespace, many cannot be reformatted without breaking
129 # them.
130 def skip_file(path):
131 if path.endswith('_test.dart'):
132 with open(path, encoding='utf-8') as f:
133 contents = f.read()
134 if '//#' in contents:
135 return True
136 return False
137
138 if filename and skip_file(filename):
139 return False
140
141 args = [
142 dart,
143 'format',
144 ] + dartFixes + [
145 '--set-exit-if-changed',
146 '--output=none',
147 '--summary=none',
148 ]
149
150 # TODO(https://github.com/dart-lang/sdk/issues/46947): Remove this hack.
151 if windows and contents:
152 f = tempfile.NamedTemporaryFile(
153 encoding='utf-8',
154 delete=False,
155 mode='w',
156 suffix='.dart',
157 )
158 try:
159 f.write(contents)
160 f.close()
161 args.append(f.name)
162 process = subprocess.run(args)
163 finally:
164 os.unlink(f.name)
165 elif contents:
166 process = subprocess.run(args, input=contents, text=True)
167 elif filenames:
168 args += [f for f in filenames if not skip_file(f)]
169 process = subprocess.run(args)
170 else:
171 args.append(filename)
172 process = subprocess.run(args)
173
174 # Check for exit code 1 explicitly to distinguish it from a syntax error
175 # in the file (exit code 65). The repo contains many Dart files that are
176 # known to have syntax errors for testing purposes and which can't be
177 # parsed and formatted. Don't treat those as errors.
178 return process.returncode == 1
179
180 unformatted_files = _CheckFormat(input_api, "dart format", ".dart", windows,
181 HasFormatErrors, [])
182
183 if unformatted_files:
184 lineSep = " \\\n"
185 if windows:
186 lineSep = " ^\n"
187 return [
188 output_api.PresubmitError(
189 'File output does not match dart format.\n'
190 'Fix these issues with:\n'
191 '%s format %s%s%s' % (dart, ' '.join(dartFixes), lineSep,
192 lineSep.join(unformatted_files)))
193 ]
194
195 return []
196
197
198def _CheckStatusFiles(input_api, output_api):
199 local_root = input_api.change.RepositoryRoot()
200 utils = load_source('utils', os.path.join(local_root, 'tools', 'utils.py'))
201
202 dart = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dart')
203 lint = os.path.join(local_root, 'pkg', 'status_file', 'bin', 'lint.dart')
204
205 windows = utils.GuessOS() == 'win32'
206 if windows:
207 dart += '.exe'
208
209 if not os.path.isfile(dart):
210 print('WARNING: dart not found: %s' % dart)
211 return []
212
213 if not os.path.isfile(lint):
214 print('WARNING: Status file linter not found: %s' % lint)
215 return []
216
217 def HasFormatErrors(filename=None, filenames=None, contents=None):
218 if filenames:
219 # The status file linter doesn't support checking files in bulk.
220 # Returning `True` causes `_CheckFormat` to fallback to check
221 # formatting file by file below.
222 return True
223 args = [dart, lint] + (['-t'] if contents else [filename])
224 process = subprocess.run(args, input=contents, text=True)
225 return process.returncode != 0
226
227 exclude_folders = [
228 "pkg/status_file/test/data/",
229 "pkg/front_end/",
230 ]
231 unformatted_files = _CheckFormat(input_api, "status file", ".status",
232 windows, HasFormatErrors, exclude_folders)
233
234 if unformatted_files:
235 normalize = os.path.join(local_root, 'pkg', 'status_file', 'bin',
236 'normalize.dart')
237 lineSep = " \\\n"
238 if windows:
239 lineSep = " ^\n"
240 return [
241 output_api.PresubmitError(
242 'Status files are not normalized.\n'
243 'Fix these issues with:\n'
244 '%s %s -w%s%s' % (dart, normalize, lineSep,
245 lineSep.join(unformatted_files)))
246 ]
247
248 return []
249
250
251def _CheckValidHostsInDEPS(input_api, output_api):
252 """Checks that DEPS file deps are from allowed_hosts."""
253 # Run only if DEPS file has been modified to annoy fewer bystanders.
254 if all(f.LocalPath() != 'DEPS' for f in input_api.AffectedFiles()):
255 return []
256 # Outsource work to gclient verify
257 try:
258 input_api.subprocess.check_output(['gclient', 'verify'])
259 return []
260 except input_api.subprocess.CalledProcessError as error:
261 return [
262 output_api.PresubmitError(
263 'DEPS file must have only dependencies from allowed hosts.',
264 long_text=error.output)
265 ]
266
267
268def _CheckLayering(input_api, output_api):
269 """Run VM layering check.
270
271 This check validates that sources from one layer do not reference sources
272 from another layer accidentally.
273 """
274
275 # Run only if .cc or .h file was modified.
276 if all(not is_cpp_file(f.LocalPath()) for f in input_api.AffectedFiles()):
277 return []
278
279 local_root = input_api.change.RepositoryRoot()
280 compiler_layering_check = load_source(
281 'compiler_layering_check',
282 os.path.join(local_root, 'runtime', 'tools',
283 'compiler_layering_check.py'))
284 errors = compiler_layering_check.DoCheck(local_root)
285 embedder_layering_check = load_source(
286 'embedder_layering_check',
287 os.path.join(local_root, 'runtime', 'tools',
288 'embedder_layering_check.py'))
289 errors += embedder_layering_check.DoCheck(local_root)
290 if errors:
291 return [
292 output_api.PresubmitError(
293 'Layering check violation for C++ sources.',
294 long_text='\n'.join(errors))
295 ]
296
297 return []
298
299
300def _CheckClangTidy(input_api, output_api):
301 """Run clang-tidy on VM changes."""
302
303 # Only run clang-tidy on linux x64.
304 if platform.system() != 'Linux' or platform.machine() != 'x86_64':
305 return []
306
307 # Run only for modified .cc or .h files.
308 files = []
309 for f in input_api.AffectedFiles():
310 path = f.LocalPath()
311 if is_cpp_file(path) and os.path.isfile(path): files.append(path)
312
313 if not files:
314 return []
315
316 args = [
317 'tools/sdks/dart-sdk/bin/dart',
318 'runtime/tools/run_clang_tidy.dart',
319 ]
320 args.extend(files)
321 stdout = input_api.subprocess.check_output(args).strip()
322 if not stdout:
323 return []
324
325 return [
326 output_api.PresubmitError(
327 'The `clang-tidy` linter revealed issues:',
328 long_text=stdout)
329 ]
330
331
332def _CheckClangFormat(input_api, output_api):
333 """Run clang-format on VM changes."""
334
335 # Only run clang-format on linux x64.
336 if platform.system() != 'Linux' or platform.machine() != 'x86_64':
337 return []
338
339 # Run only for modified .cc or .h files, except for DEPS changes.
340 files = []
341 is_deps = False
342 for f in input_api.AffectedFiles():
343 path = f.LocalPath()
344 if path == 'DEPS' and any(
345 map(lambda content: 'clang' in content[1],
346 f.ChangedContents())):
347 is_deps = True
348 break
349 if is_cpp_file(path) and os.path.isfile(path):
350 files.append(path)
351
352 if is_deps:
353 find_args = [
354 'find',
355 'runtime/',
356 '-iname',
357 '*.h',
358 '-o',
359 '-iname',
360 '*.cc',
361 ]
362 files = subprocess.check_output(find_args, text=True).split()
363
364 if not files:
365 return []
366
367 args = [
368 'buildtools/linux-x64/clang/bin/clang-format',
369 '--dry-run',
370 '--Werror',
371 ]
372 args.extend(files)
373 stdout = input_api.subprocess.check_output(args).strip()
374 if not stdout:
375 return []
376
377 return [
378 output_api.PresubmitError('The `clang-format` revealed issues:',
379 long_text=stdout)
380 ]
381
382
383def _CheckAnalyzerFiles(input_api, output_api):
384 """Run analyzer checks on source files."""
385
386 # Verify the "error fix status" file.
387 code_files = [
388 "pkg/analyzer/lib/src/error/error_code_values.g.dart",
389 "pkg/linter/lib/src/rules.dart",
390 ]
391
392 if any(f.LocalPath() in code_files for f in input_api.AffectedFiles()):
393 args = [
394 "tools/sdks/dart-sdk/bin/dart",
395 "pkg/analysis_server/tool/presubmit/verify_error_fix_status.dart",
396 ]
397 stdout = input_api.subprocess.check_output(args).strip()
398 if not stdout:
399 return []
400
401 return [
402 output_api.PresubmitError(
403 "The verify_error_fix_status Analyzer tool revealed issues:",
404 long_text=stdout)
405 ]
406
407 # Verify the linter's `example/all.yaml` file.
408 if any(f.LocalPath().startswith('pkg/linter/lib/src/rules')
409 for f in input_api.AffectedFiles()):
410 args = [
411 "tools/sdks/dart-sdk/bin/dart",
412 "pkg/linter/tool/checks/check_all_yaml.dart",
413 ]
414 stdout = input_api.subprocess.check_output(args).strip()
415 if not stdout:
416 return []
417
418 return [
419 output_api.PresubmitError(
420 "The check_all_yaml linter tool revealed issues:",
421 long_text=stdout)
422 ]
423
424 # TODO(srawlins): Check more:
425 # * "verify_sorted" for individual modified (not deleted) files in
426 # Analyzer-team-owned directories.
427 # * "verify_tests" for individual modified (not deleted) test files in
428 # Analyzer-team-owned directories.
429 # * Verify that `messages/generate.dart` does not produce different
430 # content, when `pkg/analyzer/messages.yaml` is modified.
431 # * Verify that `diagnostics/generate.dart` does not produce different
432 # content, when `pkg/analyzer/messages.yaml` is modified.
433 # * Verify that `machine.json` is not outdated, when any
434 # `pkg/linter/lib/src/rules` file is modified.
435 # * Maybe "verify_no_solo" for individual modified (not deleted test files
436 # in Analyzer-team-owned directories.
437
438 # No files are relevant.
439 return []
440
441
442def _CheckTestMatrixValid(input_api, output_api):
443 """Run script to check that the test matrix has no errors."""
444
445 def test_matrix_filter(affected_file):
446 """Only run test if either the test matrix or the code that
447 validates it was modified."""
448 path = affected_file.LocalPath()
449 return (path == 'tools/bots/test_matrix.json' or
450 path == 'tools/validate_test_matrix.dart' or
451 path.startswith('pkg/smith/'))
452
453 if len(
454 input_api.AffectedFiles(
455 include_deletes=False, file_filter=test_matrix_filter)) == 0:
456 return []
457
458 command = [
459 'tools/sdks/dart-sdk/bin/dart',
460 'tools/validate_test_matrix.dart',
461 ]
462 stdout = input_api.subprocess.check_output(command).strip()
463 if not stdout:
464 return []
465 else:
466 return [
467 output_api.PresubmitError(
468 'The test matrix is not valid:', long_text=stdout)
469 ]
470
471
472def _CheckCopyrightYear(input_api, output_api):
473 """Check copyright year in new files."""
474
475 files = []
476 year = str(datetime.datetime.now().year)
477 for f in input_api.AffectedFiles(include_deletes=False):
478 path = f.LocalPath()
479 if (is_dart_file(path) or is_cpp_file(path)
480 ) and f.Action() == 'A' and os.path.isfile(path):
481 with open(path, encoding='utf-8') as f:
482 first_line = f.readline()
483 if 'Copyright' in first_line and year not in first_line:
484 files.append(path)
485
486 if not files:
487 return []
488
489 return [
490 output_api.PresubmitPromptWarning(
491 'Copyright year for new files should be ' + year + ':\n' +
492 '\n'.join(files))
493 ]
494
495
496def _CheckNoNewObservatoryServiceTests(input_api, output_api):
497 """Ensures that no new tests are added to the Observatory test suite."""
498 files = []
499
500 for f in input_api.AffectedFiles(include_deletes=False):
501 path = f.LocalPath()
502 if is_dart_file(path) and path.startswith(
503 "runtime/observatory/tests/service/") and f.Action(
504 ) == 'A' and os.path.isfile(path):
505 files.append(path)
506
507 if not files:
508 return []
509
510 return [
511 output_api.PresubmitError(
512 'New VM service tests should be added to pkg/vm_service/test, ' +
513 'not runtime/observatory/tests/service:\n' + '\n'.join(files))
514 ]
515
516def _CommonChecks(input_api, output_api):
517 results = []
518 results.extend(_CheckValidHostsInDEPS(input_api, output_api))
519 results.extend(_CheckDartFormat(input_api, output_api))
520 results.extend(_CheckStatusFiles(input_api, output_api))
521 results.extend(_CheckLayering(input_api, output_api))
522 results.extend(_CheckClangTidy(input_api, output_api))
523 results.extend(_CheckClangFormat(input_api, output_api))
524 results.extend(_CheckTestMatrixValid(input_api, output_api))
525 results.extend(
526 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
527 results.extend(_CheckCopyrightYear(input_api, output_api))
528 results.extend(_CheckAnalyzerFiles(input_api, output_api))
529 results.extend(_CheckNoNewObservatoryServiceTests(input_api, output_api))
530 return results
531
532
533def CheckChangeOnCommit(input_api, output_api):
534 return _CommonChecks(input_api, output_api)
535
536
537def CheckChangeOnUpload(input_api, output_api):
538 return _CommonChecks(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api)
Definition: PRESUBMIT.py:66
def CheckChangeOnUpload(input_api, output_api)
Definition: PRESUBMIT.py:70
def files_to_check_for_format(input_api, extension, exclude_folders)
Definition: PRESUBMIT.py:41
def load_source(modname, filename)
Definition: PRESUBMIT.py:19
def get_old_contents(input_api, path)
Definition: PRESUBMIT.py:33
def is_cpp_file(path)
Definition: PRESUBMIT.py:25
def is_dart_file(path)
Definition: PRESUBMIT.py:29
def print(*args, **kwargs)
Definition: run_tests.py:49
SIT bool all(const Vec< 1, T > &x)
Definition: SkVx.h:582
SI auto map(std::index_sequence< I... >, Fn &&fn, const Args &... args) -> skvx::Vec< sizeof...(I), decltype(fn(args[0]...))>
Definition: SkVx.h:680
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530
def GuessOS()
Definition: utils.py:21
def CheckedInSdkPath()
Definition: utils.py:499
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741