| Index: mojo/dart/tools/presubmit/check_mojom_dart.py
|
| diff --git a/mojo/dart/tools/presubmit/check_mojom_dart.py b/mojo/dart/tools/presubmit/check_mojom_dart.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..96ff593afef584a766ff43a516cf5d684e448117
|
| --- /dev/null
|
| +++ b/mojo/dart/tools/presubmit/check_mojom_dart.py
|
| @@ -0,0 +1,355 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2015 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Checks that released mojom.dart files in the source tree are up to date"""
|
| +
|
| +import argparse
|
| +import os
|
| +import subprocess
|
| +import sys
|
| +
|
| +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| +SRC_DIR = os.path.dirname(
|
| + os.path.dirname(
|
| + os.path.dirname(
|
| + os.path.dirname(SCRIPT_DIR))))
|
| +
|
| +# Insert path to mojom parsing library.
|
| +sys.path.insert(0, os.path.join(SRC_DIR,
|
| + 'mojo',
|
| + 'public',
|
| + 'tools',
|
| + 'bindings',
|
| + 'pylib'))
|
| +
|
| +from mojom.error import Error
|
| +from mojom.parse.parser import Parse
|
| +from mojom.parse.translate import Translate
|
| +
|
| +PACKAGES_DIR = os.path.join(SRC_DIR, 'mojo', 'dart', 'packages')
|
| +
|
| +# Script that calculates mojom output paths.
|
| +DART_OUTPUTS_SCRIPT = os.path.join(SRC_DIR,
|
| + 'mojo',
|
| + 'public',
|
| + 'tools',
|
| + 'bindings',
|
| + 'mojom_list_dart_outputs.py')
|
| +
|
| +# Runs command line in args from cwd. Returns the output as a string.
|
| +def run(cwd, args):
|
| + return subprocess.check_output(args, cwd=cwd)
|
| +
|
| +
|
| +# Given a parsed mojom, return the path of the .mojom.dart relative to its
|
| +# package directory.
|
| +def _mojom_output_path(mojom):
|
| + name = mojom['name']
|
| + namespace = mojom['namespace']
|
| + elements = ['lib']
|
| + elements.extend(namespace.split('.'))
|
| + elements.append("%s.dart" % name)
|
| + return os.path.join(*elements)
|
| +
|
| +
|
| +# Given a parsed mojom, return the package or None.
|
| +def _mojom_package(mojom):
|
| + attributes = mojom['attributes']
|
| + return attributes['DartPackage']
|
| +
|
| +
|
| +# Load and parse a .mojom file. Returns the parsed mojom.
|
| +def _load_mojom(path_to_mojom):
|
| + filename = os.path.abspath(path_to_mojom)
|
| + name = os.path.basename(filename)
|
| +
|
| + # Read in mojom file.
|
| + with open(filename) as f:
|
| + source = f.read()
|
| + # Parse
|
| + tree = Parse(source, name)
|
| + mojom = Translate(tree, name)
|
| + return mojom
|
| +
|
| +
|
| +def _print_regenerate_message(package):
|
| + print("""
|
| +*** Dart Generated Bindings Check Failed for package: %s
|
| +
|
| +To regenerate bindings, from the src directory, run:
|
| +./third_party/dart-sdk/dart-sdk/bin/dart mojo/dart/packages/mojom/bin/mojom.dart gen -m mojo/public -r mojo/ --output mojo/dart/packages/
|
| +""" % (package))
|
| +
|
| +
|
| +# Returns a map from package name to source directory.
|
| +def _build_package_map():
|
| + packages = {}
|
| + for package in os.listdir(PACKAGES_DIR):
|
| + package_path = os.path.join(PACKAGES_DIR, package)
|
| + # Skip everything but directories.
|
| + if not os.path.isdir(package_path):
|
| + continue
|
| + packages[package] = package_path
|
| + return packages
|
| +
|
| +
|
| +# Returns a list of paths to .mojom files vended by package_name.
|
| +def _find_mojoms_for_package(package_name):
|
| + # Run git grep for all .mojom files with DartPackage="package_name"
|
| + try:
|
| + output = run(SRC_DIR, ['git',
|
| + 'grep',
|
| + '--name-only',
|
| + 'DartPackage="' + package_name + '"',
|
| + '--',
|
| + '*.mojom'])
|
| + except subprocess.CalledProcessError as e:
|
| + # git grep exits with code 1 if nothing was found.
|
| + if e.returncode == 1:
|
| + return []
|
| +
|
| + # Process output
|
| + mojoms = []
|
| + for line in output.splitlines():
|
| + line = line.strip()
|
| + # Skip empty lines.
|
| + if not line:
|
| + continue
|
| + mojoms.append(line)
|
| + return mojoms
|
| +
|
| +
|
| +# Return the list of expected mojom.dart files for a package.
|
| +def _expected_mojom_darts_for_package(mojoms):
|
| + output = run(SRC_DIR, ['python',
|
| + DART_OUTPUTS_SCRIPT,
|
| + '--mojoms'] + mojoms)
|
| + mojom_darts = []
|
| + for line in output.splitlines():
|
| + line = line.strip()
|
| + # Skip empty lines.
|
| + if not line:
|
| + continue
|
| + mojom_darts.append(line)
|
| + return mojom_darts
|
| +
|
| +
|
| +# Returns a map indexed by output mojom.dart name with the value of
|
| +# the modification time of the .mojom file in the source tree.
|
| +def _build_expected_map(mojoms, mojom_darts):
|
| + assert(len(mojom_darts) == len(mojoms))
|
| + expected = {}
|
| + for i in range(0, len(mojoms)):
|
| + mojom_path = os.path.join(SRC_DIR, mojoms[i])
|
| + expected[mojom_darts[i]] = os.path.getmtime(mojom_path)
|
| + return expected
|
| +
|
| +
|
| +# Returns a map indexed by output mojom.dart name with the value of
|
| +# the modification time of the .mojom.dart file in the source tree.
|
| +def _build_current_map(package):
|
| + current = {}
|
| + package_path = os.path.join(PACKAGES_DIR, package)
|
| + for directory, _, files in os.walk(package_path):
|
| + for filename in files:
|
| + if filename.endswith('.mojom.dart'):
|
| + path = os.path.abspath(os.path.join(directory, filename))
|
| + relpath = os.path.relpath(path, start=PACKAGES_DIR)
|
| + current[relpath] = os.path.getmtime(path)
|
| + return current
|
| +
|
| +
|
| +# Checks if a mojom.dart file we expected in the source tree isn't there.
|
| +def _check_new(package, expected, current):
|
| + check_failure = False
|
| + for mojom_dart in expected:
|
| + if not current.get(mojom_dart):
|
| + print("Package %s missing %s" % (package, mojom_dart))
|
| + check_failure = True
|
| + return check_failure
|
| +
|
| +
|
| +# Checks if a mojom.dart file exists without an associated .mojom file.
|
| +def _check_delete(package, expected, current):
|
| + check_failure = False
|
| + for mojom_dart in current:
|
| + if not expected.get(mojom_dart):
|
| + print("Package %s no longer has %s." % (package, mojom_dart))
|
| + print("Delete %s", os.path.join(PACKAGES_DIR, mojom_dart))
|
| + check_failure = True
|
| + return check_failure
|
| +
|
| +
|
| +# Checks if a .mojom.dart file is older than the associated .mojom file.
|
| +# TODO(johnmccutchan): Handle the case where someone edited a .mojom.dart file
|
| +# directly instead of through the bindings generation script.
|
| +def _check_stale(package, expected, current):
|
| + check_failure = False
|
| + for mojom_dart in expected:
|
| + # Missing mojom.dart file in source tree case handled by _check_new.
|
| + source_mtime = expected[mojom_dart]
|
| + if not current.get(mojom_dart):
|
| + continue
|
| + generated_mtime = current[mojom_dart]
|
| + if generated_mtime < source_mtime:
|
| + print("Package %s has old %s" % (package, mojom_dart))
|
| + check_failure = True
|
| + return check_failure
|
| +
|
| +
|
| +# Returns True if any checks fail.
|
| +def _check(package, expected, current):
|
| + check_failure = False
|
| + if _check_new(package, expected, current):
|
| + check_failure = True
|
| + if _check_stale(package, expected, current):
|
| + check_failure = True
|
| + if _check_delete(package, expected, current):
|
| + check_failure = True
|
| + return check_failure
|
| +
|
| +
|
| +def global_check(packages):
|
| + check_failure = False
|
| + for package in packages:
|
| + mojoms = _find_mojoms_for_package(package)
|
| + if not mojoms:
|
| + continue
|
| + mojom_darts = _expected_mojom_darts_for_package(mojoms)
|
| + # We only feed in mojom files with DartPackage annotations, therefore, we
|
| + # should have a 1:1 mapping from mojoms[i] to mojom_darts[i].
|
| + assert(len(mojom_darts) == len(mojoms))
|
| + expected = _build_expected_map(mojoms, mojom_darts)
|
| + current = _build_current_map(package)
|
| + if _check(package, expected, current):
|
| + _print_regenerate_message(package)
|
| + check_failure = True
|
| + return check_failure
|
| +
|
| +
|
| +def is_mojom_dart(path):
|
| + return path.endswith('.mojom.dart')
|
| +
|
| +
|
| +def is_mojom(path):
|
| + return path.endswith('.mojom')
|
| +
|
| +
|
| +def filter_paths(paths, path_filter):
|
| + result = []
|
| + for path in paths:
|
| + path = os.path.abspath(os.path.join(SRC_DIR, path))
|
| + if path_filter(path):
|
| + result.append(path)
|
| + return result
|
| +
|
| +
|
| +def safe_mtime(path):
|
| + try:
|
| + return os.path.getmtime(path)
|
| + except Exception:
|
| + pass
|
| + return 0
|
| +
|
| +
|
| +def presubmit_check(packages, affected_files):
|
| + mojoms = filter_paths(affected_files, is_mojom)
|
| + mojom_darts = filter_paths(affected_files, is_mojom_dart)
|
| + updated_mojom_dart_files = []
|
| + packages_with_failures = []
|
| + check_failure = False
|
| +
|
| + # Check for updated .mojom without updated .mojom.dart
|
| + for mojom_file in mojoms:
|
| + try:
|
| + mojom = _load_mojom(mojom_file)
|
| + except Exception:
|
| + # Could not load .mojom file
|
| + return True
|
| +
|
| + package = _mojom_package(mojom)
|
| + # If a mojom doesn't have a package, ignore it.
|
| + if not package:
|
| + continue
|
| + package_dir = packages.get(package)
|
| + # If the package isn't a known package, ignore it.
|
| + if not package_dir:
|
| + continue
|
| + # Expected output path relative to src.
|
| + mojom_dart_path = os.path.relpath(
|
| + os.path.join(package_dir, _mojom_output_path(mojom)), start=SRC_DIR)
|
| +
|
| + mojom_mtime = safe_mtime(mojom_file)
|
| + mojom_dart_mtime = safe_mtime(os.path.join(SRC_DIR, mojom_dart_path))
|
| +
|
| + if mojom_mtime > mojom_dart_mtime:
|
| + check_failure = True
|
| + print("Package %s has old %s" % (package, mojom_dart_path))
|
| + if not (package in packages_with_failures):
|
| + packages_with_failures.append(package)
|
| + continue
|
| + # Remember that this .mojom.dart file was updated after the .mojom file.
|
| + # This list is used to verify that all updated .mojom.dart files were
|
| + # updated because their source .mojom file changed.
|
| + updated_mojom_dart_files.append(mojom_dart_path)
|
| +
|
| + # Check for updated .mojom.dart file without updated .mojom file.
|
| + for mojom_dart_file in mojom_darts:
|
| + # mojom_dart_file is not inside //mojo/dart/packages.
|
| + if not mojom_dart_file.startswith(PACKAGES_DIR):
|
| + continue
|
| +
|
| + # Path relative to //mojo/dart/packages/
|
| + path_relative_to_packages = os.path.relpath(mojom_dart_file,
|
| + start=PACKAGES_DIR)
|
| + # Package name is first element of split path.
|
| + package = path_relative_to_packages.split(os.sep)[0]
|
| + # Path relative to src.
|
| + mojom_dart_path = os.path.relpath(mojom_dart_file, start=SRC_DIR)
|
| + # If mojom_dart_path is not in updated_mojom_dart_files, a .mojom.dart
|
| + # file was updated without updating the related .mojom file.
|
| + if not (mojom_dart_path in updated_mojom_dart_files):
|
| + check_failure = True
|
| + print("Package %s has new %s without updating source .mojom file." %
|
| + (package, mojom_dart_path))
|
| + if not (package in packages_with_failures):
|
| + packages_with_failures.append(package)
|
| +
|
| + for package in packages_with_failures:
|
| + _print_regenerate_message(package)
|
| +
|
| + return check_failure
|
| +
|
| +
|
| +def main():
|
| + parser = argparse.ArgumentParser(description='Generate a dart-pkg')
|
| + parser.add_argument('--affected-files',
|
| + action='store',
|
| + metavar='affected_files',
|
| + help='List of files that should be checked.',
|
| + nargs='+')
|
| + args = parser.parse_args()
|
| + packages = _build_package_map()
|
| +
|
| + # This script runs in two modes, the first mode is invoked by PRESUBMIT.py
|
| + # and passes the list of affected files. This checks for the following cases:
|
| + # 1) An updated .mojom file without an updated .mojom.dart file.
|
| + # 2) An updated .mojom.dart file without an updated .mojom file.
|
| + # NOTE: Case 1) also handles the case of a new .mojom file being added.
|
| + #
|
| + # The second mode does a global check of all packages under
|
| + # //mojo/dart/packages. This checks for the following cases:
|
| + # 1) An updated .mojom file without an updated .mojom.dart file.
|
| + # 2) A .mojom.dart file without an associated .mojom file (deletion case).
|
| + if args.affected_files:
|
| + check_failure = presubmit_check(packages, args.affected_files)
|
| + else:
|
| + check_failure = global_check(packages)
|
| + if check_failure:
|
| + return 2
|
| + return 0
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|