Index: PRESUBMIT.py |
diff --git a/PRESUBMIT.py b/PRESUBMIT.py |
index fb3d60f0e2407a658872f671900bd39b3b9984a1..8308310257763930b7a570f3c21a1ce258898df9 100644 |
--- a/PRESUBMIT.py |
+++ b/PRESUBMIT.py |
@@ -298,6 +298,17 @@ _VALID_OS_MACROS = ( |
) |
+_ANDROID_SPECIFIC_PYDEPS_FILES = [ |
+ 'build/android/test_runner.pydeps', |
+] |
+ |
+_GENERIC_PYDEPS_FILES = [ |
+ 'build/secondary/tools/swarming_client/isolate.pydeps', |
+] |
+ |
+_ALL_PYDEPS_FILES = _ANDROID_SPECIFIC_PYDEPS_FILES + _GENERIC_PYDEPS_FILES |
+ |
+ |
def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api): |
"""Attempts to prevent use of functions intended only for testing in |
non-testing code. For now this is just a best-effort implementation |
@@ -1501,6 +1512,99 @@ def _CheckAndroidNewMdpiAssetLocation(input_api, output_api): |
return results |
+class PydepsChecker(object): |
+ def __init__(self, input_api, pydeps_files): |
+ self._file_cache = {} |
+ self._input_api = input_api |
+ self._pydeps_files = pydeps_files |
+ |
+ def _LoadFile(self, path): |
+ """Returns the list of paths within a .pydeps file relative to //.""" |
+ if path not in self._file_cache: |
+ with open(path) as f: |
+ self._file_cache[path] = f.read() |
+ return self._file_cache[path] |
+ |
+ def _ComputeNormalizedPydepsEntries(self, pydeps_path): |
+ """Returns an interable of paths within the .pydep, relativized to //.""" |
+ os_path = self._input_api.os_path |
+ pydeps_dir = os_path.dirname(pydeps_path) |
+ entries = (l.rstrip() for l in self._LoadFile(pydeps_path).splitlines() |
+ if not l.startswith('*')) |
+ return (os_path.normpath(os_path.join(pydeps_dir, e)) for e in entries) |
+ |
+ def _CreateFilesToPydepsMap(self): |
+ """Returns a map of local_path -> list_of_pydeps.""" |
+ ret = {} |
+ for pydep_local_path in self._pydeps_files: |
+ for path in self._ComputeNormalizedPydepsEntries(pydep_local_path): |
+ ret.setdefault(path, []).append(pydep_local_path) |
+ return ret |
+ |
+ def ComputeAffectedPydeps(self): |
+ """Returns an iterable of .pydeps files that might need regenerating.""" |
+ affected_pydeps = set() |
+ file_to_pydeps_map = None |
+ for f in self._input_api.AffectedFiles(include_deletes=True): |
+ local_path = f.LocalPath() |
+ if local_path == 'DEPS': |
+ return self._pydeps_files |
+ elif local_path.endswith('.pydeps'): |
+ if local_path in self._pydeps_files: |
+ affected_pydeps.add(local_path) |
+ elif local_path.endswith('.py'): |
+ if file_to_pydeps_map is None: |
+ file_to_pydeps_map = self._CreateFilesToPydepsMap() |
+ affected_pydeps.update(file_to_pydeps_map.get(local_path, ())) |
+ return affected_pydeps |
+ |
+ def DetermineIfStale(self, pydeps_path): |
+ """Runs print_python_deps.py to see if the files is stale.""" |
+ old_pydeps_data = self._LoadFile(pydeps_path).splitlines() |
+ cmd = old_pydeps_data[1][1:].strip() |
+ new_pydeps_data = self._input_api.subprocess.check_output( |
+ cmd + ' --output ""', shell=True) |
+ if old_pydeps_data[2:] != new_pydeps_data.splitlines()[2:]: |
+ return cmd |
+ |
+ |
+def _CheckPydepsNeedsUpdating(input_api, output_api, checker_for_tests=None): |
+ """Checks if a .pydeps file needs to be regenerated.""" |
+ # TODO(agrieve): Update when there's a better way to detect this: crbug/570091 |
+ is_android = input_api.os_path.exists('third_party/android_tools') |
+ pydeps_files = _ALL_PYDEPS_FILES if is_android else _GENERIC_PYDEPS_FILES |
+ results = [] |
+ # First, check for new / deleted .pydeps. |
+ for f in input_api.AffectedFiles(include_deletes=True): |
+ if f.LocalPath().endswith('.pydeps'): |
+ if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES: |
+ results.append(output_api.PresubmitError( |
+ 'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to ' |
+ 'remove %s' % f.LocalPath())) |
+ elif f.Action() != 'D' and f.LocalPath() not in _ALL_PYDEPS_FILES: |
+ results.append(output_api.PresubmitError( |
+ 'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to ' |
+ 'include %s' % f.LocalPath())) |
+ |
+ if results: |
+ return results |
+ |
+ checker = checker_for_tests or PydepsChecker(input_api, pydeps_files) |
+ |
+ for pydep_path in checker.ComputeAffectedPydeps(): |
+ try: |
+ cmd = checker.DetermineIfStale(pydep_path) |
+ if cmd: |
+ results.append(output_api.PresubmitError( |
+ 'File is stale: %s\nTo regenerate, run:\n\n %s' % |
+ (pydep_path, cmd))) |
+ except input_api.subprocess.CalledProcessError as error: |
+ return [output_api.PresubmitError('Error running: %s' % error.cmd, |
+ long_text=error.output)] |
+ |
+ return results |
+ |
+ |
def _CheckForCopyrightedCode(input_api, output_api): |
"""Verifies that newly added code doesn't contain copyrighted material |
and is properly licensed under the standard Chromium license. |
@@ -1704,6 +1808,7 @@ def _CommonChecks(input_api, output_api): |
results.extend(_CheckForWindowsLineEndings(input_api, output_api)) |
results.extend(_CheckSingletonInHeaders(input_api, output_api)) |
results.extend(_CheckNoDeprecatedCompiledResourcesGYP(input_api, output_api)) |
+ results.extend(_CheckPydepsNeedsUpdating(input_api, output_api)) |
if any('PRESUBMIT.py' == f.LocalPath() for f in input_api.AffectedFiles()): |
results.extend(input_api.canned_checks.RunUnitTestsInDirectory( |