# 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 def _CheckFormat(input_api, identification, extension, windows, hasFormatErrors): local_root = input_api.change.RepositoryRoot() upstream = input_api.change._upstream unformatted_files = [] for git_file in input_api.AffectedTextFiles(): 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 _CheckBuildStatus(input_api, output_api): results = [] status_check = input_api.canned_checks.CheckTreeIsOpen( input_api, output_api, json_url='http://dart-status.appspot.com/current?format=json') results.extend(status_check) return results 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')) prebuilt_dartfmt = os.path.join(utils.CheckedInSdkPath(), 'bin', 'dartfmt') windows = utils.GuessOS() == 'win32' if windows: prebuilt_dartfmt += '.bat' if not os.path.isfile(prebuilt_dartfmt): print('WARNING: dartfmt not found: %s' % (prebuilt_dartfmt)) return [] def HasFormatErrors(filename=None, contents=None): args = [prebuilt_dartfmt, '--set-exit-if-changed'] if not contents: args += [filename, '-n'] process = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE ) process.communicate(input=contents) # 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, "dartfmt", ".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 _CheckNewTests(input_api, output_api): testsDirectories = [ # Dart 1 tests Dart 2.0 tests # ================= ========================== ("tests/language/", "tests/language_2/"), ("tests/corelib/", "tests/corelib_2/"), ("tests/lib/", "tests/lib_2/"), ("tests/html/", "tests/lib_2/html/"), ("tests/isolate/", "tests/lib_2/isolate/") ] result = [] # Tuples of (new Dart 1 test path, expected Dart 2.0 test path) dart1TestsAdded = [] # Tuples of (original Dart test path, expected Dart 2.0 test path) dart2TestsExists = [] for f in input_api.AffectedTextFiles(): localpath = f.LocalPath() if not(localpath.endswith('.status')): for oldPath, newPath in testsDirectories: if localpath.startswith(oldPath): if f.Action() == 'A': # Compute where the new test should live. dart2TestPath = localpath.replace(oldPath, newPath) dart1TestsAdded.append((localpath, dart2TestPath)) elif f.Action() == 'M': # Find all modified tests in Dart 1.0 for oldPath, newPath in testsDirectories: if localpath.find(oldPath) == 0: dart2TestFilePathAbs = "%s" % \ f.AbsoluteLocalPath().replace(oldPath, newPath) if os.path.isfile(dart2TestFilePathAbs): #originalDart1Test.append(localpath) dart2TestsExists.append((localpath, localpath.replace(oldPath, newPath))) # Does a Dart 2.0 test exist if so it must be changed too. missingDart2TestsChange = [] for (dartTest, dart2Test) in dart2TestsExists: foundDart2TestModified = False for f in input_api.AffectedFiles(): if f.LocalPath() == dart2Test: # Found corresponding Dart 2 test - great. foundDart2TestModified = True break if not foundDart2TestModified: # Add the tuple (dart 1 test path, Dart 2.0 test path) missingDart2TestsChange.append((dartTest, dart2Test)) if missingDart2TestsChange: errorList = [] for idx, (orginalTest, dart2Test) in enumerate(missingDart2TestsChange): errorList.append( '%s. Dart 1.0 test changed: %s\n%s. Only the Dart 2.0 test can '\ 'change: %s\n' % (idx + 1, orginalTest, idx + 1, dart2Test)) result.append(output_api.PresubmitError( 'Error: Changed Dart 1.0 test detected - only 1.0 status files can '\ 'change. Migrate test to Dart 2.0 tests:\n%s' % ''.join(errorList))) if dart1TestsAdded: errorList = [] for idx, (oldTestPath, newTestPath) in enumerate(dart1TestsAdded): errorList.append('%s. New Dart 1.0 test: %s\n' '%s. Should be Dart 2.0 test: %s\n' % \ (idx + 1, oldTestPath, idx + 1, newTestPath)) result.append(output_api.PresubmitError( 'Error: New Dart 1.0 test can not be added the test must be added '\ 'as a Dart 2.0 test:\nFix tests:\n%s' % ''.join(errorList))) return result 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 unformatted_files = _CheckFormat(input_api, "status file", ".status", windows, HasFormatErrors) 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 _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 CheckChangeOnCommit(input_api, output_api): return (_CheckValidHostsInDEPS(input_api, output_api) + _CheckBuildStatus(input_api, output_api) + _CheckNewTests(input_api, output_api) + _CheckDartFormat(input_api, output_api) + _CheckStatusFiles(input_api, output_api)) def CheckChangeOnUpload(input_api, output_api): return (_CheckValidHostsInDEPS(input_api, output_api) + _CheckNewTests(input_api, output_api) + _CheckDartFormat(input_api, output_api) + _CheckStatusFiles(input_api, output_api))