Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(749)

Unified Diff: mojo/dart/tools/presubmit/check_mojom_dart.py

Issue 1449203002: Check in generated Dart bindings and add presubmit script (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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())
« no previous file with comments | « mojo/dart/packages/mojom/lib/src/generate.dart ('k') | mojo/public/tools/bindings/generators/mojom_dart_generator.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698