|  | # Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
|  | # for details. All rights reserved. Use of this source code is governed by a | 
|  | # BSD-style license that can be found in the LICENSE file. | 
|  | """Top-level presubmit script for Dart. | 
|  |  | 
|  | See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts | 
|  | for more details about the presubmit API built into gcl. | 
|  | """ | 
|  |  | 
|  | import imp | 
|  | import os | 
|  | import os.path | 
|  | import scm | 
|  | import subprocess | 
|  | import tempfile | 
|  | import platform | 
|  |  | 
|  | def is_cpp_file(path): | 
|  | return path.endswith('.cc') or path.endswith('.h') | 
|  |  | 
|  |  | 
|  | def _CheckNnbdTestSync(input_api, output_api): | 
|  | """Make sure that any forked SDK tests are kept in sync. If a CL touches | 
|  | a test, the test's counterpart (if it exists at all) should be in the CL | 
|  | too. | 
|  | """ | 
|  | DIRS = [ | 
|  | "tests/co19", "tests/corelib", "tests/ffi", "tests/language", | 
|  | "tests/lib", "tests/standalone", "runtime/tests/vm/dart" | 
|  | ] | 
|  |  | 
|  | files = [git_file.LocalPath() for git_file in input_api.AffectedTextFiles()] | 
|  | unsynchronized = [] | 
|  | for file in files: | 
|  | if file.endswith('.status'): continue | 
|  |  | 
|  | for dir in DIRS: | 
|  | legacy_dir = "{}_2/".format(dir) | 
|  | nnbd_dir = "{}/".format(dir) | 
|  |  | 
|  | counterpart = None | 
|  | if file.startswith(legacy_dir): | 
|  | counterpart = file.replace(legacy_dir, nnbd_dir) | 
|  | elif file.startswith(nnbd_dir): | 
|  | counterpart = file.replace(nnbd_dir, legacy_dir) | 
|  |  | 
|  | if counterpart: | 
|  | # Changed one file with a potential counterpart. | 
|  | if counterpart not in files: | 
|  | missing = '' if os.path.exists( | 
|  | counterpart) else ' (missing)' | 
|  | unsynchronized.append("- {} -> {}{}".format( | 
|  | file, counterpart, missing)) | 
|  | break | 
|  |  | 
|  | if unsynchronized: | 
|  | return [ | 
|  | output_api.PresubmitPromptWarning( | 
|  | 'Make sure to keep the forked legacy and NNBD tests in sync.\n' | 
|  | 'You may need to synchronize these files:\n' + | 
|  | '\n'.join(unsynchronized)) | 
|  | ] | 
|  |  | 
|  | return [] | 
|  |  | 
|  |  | 
|  | def _CheckFormat(input_api, | 
|  | identification, | 
|  | extension, | 
|  | windows, | 
|  | hasFormatErrors, | 
|  | should_skip=lambda path: False): | 
|  | local_root = input_api.change.RepositoryRoot() | 
|  | upstream = input_api.change._upstream | 
|  | unformatted_files = [] | 
|  | for git_file in input_api.AffectedTextFiles(): | 
|  | if git_file.LocalPath().startswith("pkg/front_end/testcases/"): | 
|  | continue | 
|  | if should_skip(git_file.LocalPath()): | 
|  | continue | 
|  | filename = git_file.AbsoluteLocalPath() | 
|  | if filename.endswith(extension) and hasFormatErrors(filename=filename): | 
|  | old_version_has_errors = False | 
|  | try: | 
|  | path = git_file.LocalPath() | 
|  | if windows: | 
|  | # Git expects a linux style path. | 
|  | path = path.replace(os.sep, '/') | 
|  | old_contents = scm.GIT.Capture(['show', upstream + ':' + path], | 
|  | cwd=local_root, | 
|  | strip_out=False) | 
|  | if hasFormatErrors(contents=old_contents): | 
|  | old_version_has_errors = True | 
|  | except subprocess.CalledProcessError as e: | 
|  | old_version_has_errors = False | 
|  |  | 
|  | if old_version_has_errors: | 
|  | print("WARNING: %s has existing and possibly new %s issues" % | 
|  | (git_file.LocalPath(), identification)) | 
|  | else: | 
|  | unformatted_files.append(filename) | 
|  |  | 
|  | return unformatted_files | 
|  |  | 
|  |  | 
|  | def _CheckDartFormat(input_api, output_api): | 
|  | local_root = input_api.change.RepositoryRoot() | 
|  | upstream = input_api.change._upstream | 
|  | utils = imp.load_source('utils', | 
|  | os.path.join(local_root, 'tools', 'utils.py')) | 
|  |  | 
|  | dart = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dart') | 
|  |  | 
|  | windows = utils.GuessOS() == 'win32' | 
|  | if windows: | 
|  | dart += '.exe' | 
|  |  | 
|  | if not os.path.isfile(dart): | 
|  | print('WARNING: dart not found: %s' % (dart)) | 
|  | return [] | 
|  |  | 
|  | def HasFormatErrors(filename=None, contents=None): | 
|  | # Don't look for formatting errors in multitests. Since those are very | 
|  | # sensitive to whitespace, many cannot be formatted with dartfmt without | 
|  | # breaking them. | 
|  | if filename and filename.endswith('_test.dart'): | 
|  | with open(filename) as f: | 
|  | contents = f.read() | 
|  | if '//#' in contents: | 
|  | return False | 
|  |  | 
|  | args = [ | 
|  | dart, | 
|  | 'format', | 
|  | '--set-exit-if-changed', | 
|  | '--output=none', | 
|  | '--summary=none', | 
|  | filename, | 
|  | ] | 
|  |  | 
|  | process = subprocess.Popen( | 
|  | args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | 
|  |  | 
|  | # Check for exit code 1 explicitly to distinguish it from a syntax error | 
|  | # in the file (exit code 65). The repo contains many Dart files that are | 
|  | # known to have syntax errors for testing purposes and which can't be | 
|  | # parsed and formatted. Don't treat those as errors. | 
|  | return process.returncode == 1 | 
|  |  | 
|  | unformatted_files = _CheckFormat(input_api, "dart format", ".dart", windows, | 
|  | HasFormatErrors) | 
|  |  | 
|  | if unformatted_files: | 
|  | lineSep = " \\\n" | 
|  | if windows: | 
|  | lineSep = " ^\n" | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'File output does not match dartfmt.\n' | 
|  | 'Fix these issues with:\n' | 
|  | '%s -w%s%s' % (prebuilt_dartfmt, lineSep, | 
|  | lineSep.join(unformatted_files))) | 
|  | ] | 
|  |  | 
|  | return [] | 
|  |  | 
|  |  | 
|  | def _CheckStatusFiles(input_api, output_api): | 
|  | local_root = input_api.change.RepositoryRoot() | 
|  | upstream = input_api.change._upstream | 
|  | utils = imp.load_source('utils', | 
|  | os.path.join(local_root, 'tools', 'utils.py')) | 
|  |  | 
|  | dart = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dart') | 
|  | lint = os.path.join(local_root, 'pkg', 'status_file', 'bin', 'lint.dart') | 
|  |  | 
|  | windows = utils.GuessOS() == 'win32' | 
|  | if windows: | 
|  | dart += '.exe' | 
|  |  | 
|  | if not os.path.isfile(dart): | 
|  | print('WARNING: dart not found: %s' % dart) | 
|  | return [] | 
|  |  | 
|  | if not os.path.isfile(lint): | 
|  | print('WARNING: Status file linter not found: %s' % lint) | 
|  | return [] | 
|  |  | 
|  | def HasFormatErrors(filename=None, contents=None): | 
|  | args = [dart, lint] + (['-t'] if contents else [filename]) | 
|  | process = subprocess.Popen( | 
|  | args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | 
|  | process.communicate(input=contents) | 
|  | return process.returncode != 0 | 
|  |  | 
|  | def should_skip(path): | 
|  | return (path.startswith("pkg/status_file/test/data/") or | 
|  | path.startswith("pkg/front_end/")) | 
|  |  | 
|  | unformatted_files = _CheckFormat(input_api, "status file", ".status", | 
|  | windows, HasFormatErrors, should_skip) | 
|  |  | 
|  | if unformatted_files: | 
|  | normalize = os.path.join(local_root, 'pkg', 'status_file', 'bin', | 
|  | 'normalize.dart') | 
|  | lineSep = " \\\n" | 
|  | if windows: | 
|  | lineSep = " ^\n" | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'Status files are not normalized.\n' | 
|  | 'Fix these issues with:\n' | 
|  | '%s %s -w%s%s' % (dart, normalize, lineSep, | 
|  | lineSep.join(unformatted_files))) | 
|  | ] | 
|  |  | 
|  | return [] | 
|  |  | 
|  |  | 
|  | def _CheckPackageConfigUpToDate(input_api, output_api): | 
|  | """Checks that .dart_tool/package_config.json is up to date.""" | 
|  | # Run only if DEPS file or package_config.json have been modified. | 
|  | if not any(p == 'DEPS' or p == '.dart_tool/package_config.json' or | 
|  | p.endswith('pubspec.yaml') for p in input_api.LocalPaths()): | 
|  | return [] | 
|  | local_root = input_api.change.RepositoryRoot() | 
|  | utils = imp.load_source('utils', | 
|  | os.path.join(local_root, 'tools', 'utils.py')) | 
|  |  | 
|  | dart = utils.CheckedInSdkExecutable() | 
|  | generate = os.path.join(local_root, 'tools', 'generate_package_config.dart') | 
|  | cmd = [dart, generate, '--check'] | 
|  | pipe = subprocess.Popen(cmd, | 
|  | stdout=subprocess.PIPE, | 
|  | stderr=subprocess.PIPE, | 
|  | shell=utils.IsWindows()) | 
|  | output = pipe.communicate() | 
|  | if pipe.returncode != 0: | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'File .dart_tool/package_config.json is out of date.\n' | 
|  | 'Fix these issues with:\n' | 
|  | 'gclient sync -D && %s tools/generate_package_config.dart' % | 
|  | (dart)) | 
|  | ] | 
|  | return [] | 
|  |  | 
|  |  | 
|  | def _CheckValidHostsInDEPS(input_api, output_api): | 
|  | """Checks that DEPS file deps are from allowed_hosts.""" | 
|  | # Run only if DEPS file has been modified to annoy fewer bystanders. | 
|  | if all(f.LocalPath() != 'DEPS' for f in input_api.AffectedFiles()): | 
|  | return [] | 
|  | # Outsource work to gclient verify | 
|  | try: | 
|  | input_api.subprocess.check_output(['gclient', 'verify']) | 
|  | return [] | 
|  | except input_api.subprocess.CalledProcessError, error: | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'DEPS file must have only dependencies from allowed hosts.', | 
|  | long_text=error.output) | 
|  | ] | 
|  |  | 
|  |  | 
|  | def _CheckLayering(input_api, output_api): | 
|  | """Run VM layering check. | 
|  |  | 
|  | This check validates that sources from one layer do not reference sources | 
|  | from another layer accidentally. | 
|  | """ | 
|  |  | 
|  | # Run only if .cc or .h file was modified. | 
|  | if all(not is_cpp_file(f.LocalPath()) for f in input_api.AffectedFiles()): | 
|  | return [] | 
|  |  | 
|  | local_root = input_api.change.RepositoryRoot() | 
|  | compiler_layering_check = imp.load_source( | 
|  | 'compiler_layering_check', | 
|  | os.path.join(local_root, 'runtime', 'tools', | 
|  | 'compiler_layering_check.py')) | 
|  | errors = compiler_layering_check.DoCheck(local_root) | 
|  | embedder_layering_check = imp.load_source( | 
|  | 'embedder_layering_check', | 
|  | os.path.join(local_root, 'runtime', 'tools', | 
|  | 'embedder_layering_check.py')) | 
|  | errors += embedder_layering_check.DoCheck(local_root) | 
|  | if errors: | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'Layering check violation for C++ sources.', | 
|  | long_text='\n'.join(errors)) | 
|  | ] | 
|  |  | 
|  | return [] | 
|  |  | 
|  |  | 
|  | def _CheckClangTidy(input_api, output_api): | 
|  | """Run clang-tidy on VM changes.""" | 
|  |  | 
|  | # Only run clang-tidy on linux x64. | 
|  | if platform.system() != 'Linux' or platform.machine() != 'x86_64': | 
|  | return [] | 
|  |  | 
|  | # Run only for modified .cc or .h files. | 
|  | files = [] | 
|  | for f in input_api.AffectedFiles(): | 
|  | path = f.LocalPath() | 
|  | if is_cpp_file(path) and os.path.isfile(path): files.append(path) | 
|  |  | 
|  | if not files: | 
|  | return [] | 
|  |  | 
|  | args = [ | 
|  | 'tools/sdks/dart-sdk/bin/dart', | 
|  | 'runtime/tools/run_clang_tidy.dart', | 
|  | ] | 
|  | args.extend(files) | 
|  | stdout = input_api.subprocess.check_output(args).strip() | 
|  | if not stdout: | 
|  | return [] | 
|  |  | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'The `clang-tidy` linter revealed issues:', | 
|  | long_text=stdout) | 
|  | ] | 
|  |  | 
|  |  | 
|  | def _CheckTestMatrixValid(input_api, output_api): | 
|  | """Run script to check that the test matrix has no errors.""" | 
|  |  | 
|  | def test_matrix_filter(affected_file): | 
|  | """Only run test if either the test matrix or the code that | 
|  | validates it was modified.""" | 
|  | path = affected_file.LocalPath() | 
|  | return (path == 'tools/bots/test_matrix.json' or | 
|  | path == 'tools/validate_test_matrix.dart' or | 
|  | path.startswith('pkg/smith/')) | 
|  |  | 
|  | if len( | 
|  | input_api.AffectedFiles( | 
|  | include_deletes=False, file_filter=test_matrix_filter)) == 0: | 
|  | return [] | 
|  |  | 
|  | command = [ | 
|  | 'tools/sdks/dart-sdk/bin/dart', | 
|  | 'tools/validate_test_matrix.dart', | 
|  | ] | 
|  | stdout = input_api.subprocess.check_output(command).strip() | 
|  | if not stdout: | 
|  | return [] | 
|  | else: | 
|  | return [ | 
|  | output_api.PresubmitError( | 
|  | 'The test matrix is not valid:', long_text=stdout) | 
|  | ] | 
|  |  | 
|  |  | 
|  | def _CommonChecks(input_api, output_api): | 
|  | results = [] | 
|  | results.extend(_CheckNnbdTestSync(input_api, output_api)) | 
|  | results.extend(_CheckValidHostsInDEPS(input_api, output_api)) | 
|  | results.extend(_CheckDartFormat(input_api, output_api)) | 
|  | results.extend(_CheckStatusFiles(input_api, output_api)) | 
|  | results.extend(_CheckLayering(input_api, output_api)) | 
|  | results.extend(_CheckClangTidy(input_api, output_api)) | 
|  | results.extend(_CheckTestMatrixValid(input_api, output_api)) | 
|  | results.extend(_CheckPackageConfigUpToDate(input_api, output_api)) | 
|  | results.extend( | 
|  | input_api.canned_checks.CheckPatchFormatted(input_api, output_api)) | 
|  | return results | 
|  |  | 
|  |  | 
|  | def CheckChangeOnCommit(input_api, output_api): | 
|  | return _CommonChecks(input_api, output_api) | 
|  |  | 
|  |  | 
|  | def CheckChangeOnUpload(input_api, output_api): | 
|  | return _CommonChecks(input_api, output_api) |