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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Checks that released mojom.dart files in the source tree are up to date"""
7
8 import argparse
9 import os
10 import subprocess
11 import sys
12
13 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
14 SRC_DIR = os.path.dirname(
15 os.path.dirname(
16 os.path.dirname(
17 os.path.dirname(SCRIPT_DIR))))
18
19 # Insert path to mojom parsing library.
20 sys.path.insert(0, os.path.join(SRC_DIR,
21 'mojo',
22 'public',
23 'tools',
24 'bindings',
25 'pylib'))
26
27 from mojom.error import Error
28 from mojom.parse.parser import Parse
29 from mojom.parse.translate import Translate
30
31 PACKAGES_DIR = os.path.join(SRC_DIR, 'mojo', 'dart', 'packages')
32
33 # Script that calculates mojom output paths.
34 DART_OUTPUTS_SCRIPT = os.path.join(SRC_DIR,
35 'mojo',
36 'public',
37 'tools',
38 'bindings',
39 'mojom_list_dart_outputs.py')
40
41 # Runs command line in args from cwd. Returns the output as a string.
42 def run(cwd, args):
43 return subprocess.check_output(args, cwd=cwd)
44
45
46 # Given a parsed mojom, return the path of the .mojom.dart relative to its
47 # package directory.
48 def _mojom_output_path(mojom):
49 name = mojom['name']
50 namespace = mojom['namespace']
51 elements = ['lib']
52 elements.extend(namespace.split('.'))
53 elements.append("%s.dart" % name)
54 return os.path.join(*elements)
55
56
57 # Given a parsed mojom, return the package or None.
58 def _mojom_package(mojom):
59 attributes = mojom['attributes']
60 return attributes['DartPackage']
61
62
63 # Load and parse a .mojom file. Returns the parsed mojom.
64 def _load_mojom(path_to_mojom):
65 filename = os.path.abspath(path_to_mojom)
66 name = os.path.basename(filename)
67
68 # Read in mojom file.
69 with open(filename) as f:
70 source = f.read()
71 # Parse
72 tree = Parse(source, name)
73 mojom = Translate(tree, name)
74 return mojom
75
76
77 def _print_regenerate_message(package):
78 print("""
79 *** Dart Generated Bindings Check Failed for package: %s
80
81 To regenerate bindings, from the src directory, run:
82 ./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/
83 """ % (package))
84
85
86 # Returns a map from package name to source directory.
87 def _build_package_map():
88 packages = {}
89 for package in os.listdir(PACKAGES_DIR):
90 package_path = os.path.join(PACKAGES_DIR, package)
91 # Skip everything but directories.
92 if not os.path.isdir(package_path):
93 continue
94 packages[package] = package_path
95 return packages
96
97
98 # Returns a list of paths to .mojom files vended by package_name.
99 def _find_mojoms_for_package(package_name):
100 # Run git grep for all .mojom files with DartPackage="package_name"
101 try:
102 output = run(SRC_DIR, ['git',
103 'grep',
104 '--name-only',
105 'DartPackage="' + package_name + '"',
106 '--',
107 '*.mojom'])
108 except subprocess.CalledProcessError as e:
109 # git grep exits with code 1 if nothing was found.
110 if e.returncode == 1:
111 return []
112
113 # Process output
114 mojoms = []
115 for line in output.splitlines():
116 line = line.strip()
117 # Skip empty lines.
118 if not line:
119 continue
120 mojoms.append(line)
121 return mojoms
122
123
124 # Return the list of expected mojom.dart files for a package.
125 def _expected_mojom_darts_for_package(mojoms):
126 output = run(SRC_DIR, ['python',
127 DART_OUTPUTS_SCRIPT,
128 '--mojoms'] + mojoms)
129 mojom_darts = []
130 for line in output.splitlines():
131 line = line.strip()
132 # Skip empty lines.
133 if not line:
134 continue
135 mojom_darts.append(line)
136 return mojom_darts
137
138
139 # Returns a map indexed by output mojom.dart name with the value of
140 # the modification time of the .mojom file in the source tree.
141 def _build_expected_map(mojoms, mojom_darts):
142 assert(len(mojom_darts) == len(mojoms))
143 expected = {}
144 for i in range(0, len(mojoms)):
145 mojom_path = os.path.join(SRC_DIR, mojoms[i])
146 expected[mojom_darts[i]] = os.path.getmtime(mojom_path)
147 return expected
148
149
150 # Returns a map indexed by output mojom.dart name with the value of
151 # the modification time of the .mojom.dart file in the source tree.
152 def _build_current_map(package):
153 current = {}
154 package_path = os.path.join(PACKAGES_DIR, package)
155 for directory, _, files in os.walk(package_path):
156 for filename in files:
157 if filename.endswith('.mojom.dart'):
158 path = os.path.abspath(os.path.join(directory, filename))
159 relpath = os.path.relpath(path, start=PACKAGES_DIR)
160 current[relpath] = os.path.getmtime(path)
161 return current
162
163
164 # Checks if a mojom.dart file we expected in the source tree isn't there.
165 def _check_new(package, expected, current):
166 check_failure = False
167 for mojom_dart in expected:
168 if not current.get(mojom_dart):
169 print("Package %s missing %s" % (package, mojom_dart))
170 check_failure = True
171 return check_failure
172
173
174 # Checks if a mojom.dart file exists without an associated .mojom file.
175 def _check_delete(package, expected, current):
176 check_failure = False
177 for mojom_dart in current:
178 if not expected.get(mojom_dart):
179 print("Package %s no longer has %s." % (package, mojom_dart))
180 print("Delete %s", os.path.join(PACKAGES_DIR, mojom_dart))
181 check_failure = True
182 return check_failure
183
184
185 # Checks if a .mojom.dart file is older than the associated .mojom file.
186 # TODO(johnmccutchan): Handle the case where someone edited a .mojom.dart file
187 # directly instead of through the bindings generation script.
188 def _check_stale(package, expected, current):
189 check_failure = False
190 for mojom_dart in expected:
191 # Missing mojom.dart file in source tree case handled by _check_new.
192 source_mtime = expected[mojom_dart]
193 if not current.get(mojom_dart):
194 continue
195 generated_mtime = current[mojom_dart]
196 if generated_mtime < source_mtime:
197 print("Package %s has old %s" % (package, mojom_dart))
198 check_failure = True
199 return check_failure
200
201
202 # Returns True if any checks fail.
203 def _check(package, expected, current):
204 check_failure = False
205 if _check_new(package, expected, current):
206 check_failure = True
207 if _check_stale(package, expected, current):
208 check_failure = True
209 if _check_delete(package, expected, current):
210 check_failure = True
211 return check_failure
212
213
214 def global_check(packages):
215 check_failure = False
216 for package in packages:
217 mojoms = _find_mojoms_for_package(package)
218 if not mojoms:
219 continue
220 mojom_darts = _expected_mojom_darts_for_package(mojoms)
221 # We only feed in mojom files with DartPackage annotations, therefore, we
222 # should have a 1:1 mapping from mojoms[i] to mojom_darts[i].
223 assert(len(mojom_darts) == len(mojoms))
224 expected = _build_expected_map(mojoms, mojom_darts)
225 current = _build_current_map(package)
226 if _check(package, expected, current):
227 _print_regenerate_message(package)
228 check_failure = True
229 return check_failure
230
231
232 def is_mojom_dart(path):
233 return path.endswith('.mojom.dart')
234
235
236 def is_mojom(path):
237 return path.endswith('.mojom')
238
239
240 def filter_paths(paths, path_filter):
241 result = []
242 for path in paths:
243 path = os.path.abspath(os.path.join(SRC_DIR, path))
244 if path_filter(path):
245 result.append(path)
246 return result
247
248
249 def safe_mtime(path):
250 try:
251 return os.path.getmtime(path)
252 except Exception:
253 pass
254 return 0
255
256
257 def presubmit_check(packages, affected_files):
258 mojoms = filter_paths(affected_files, is_mojom)
259 mojom_darts = filter_paths(affected_files, is_mojom_dart)
260 updated_mojom_dart_files = []
261 packages_with_failures = []
262 check_failure = False
263
264 # Check for updated .mojom without updated .mojom.dart
265 for mojom_file in mojoms:
266 try:
267 mojom = _load_mojom(mojom_file)
268 except Exception:
269 # Could not load .mojom file
270 return True
271
272 package = _mojom_package(mojom)
273 # If a mojom doesn't have a package, ignore it.
274 if not package:
275 continue
276 package_dir = packages.get(package)
277 # If the package isn't a known package, ignore it.
278 if not package_dir:
279 continue
280 # Expected output path relative to src.
281 mojom_dart_path = os.path.relpath(
282 os.path.join(package_dir, _mojom_output_path(mojom)), start=SRC_DIR)
283
284 mojom_mtime = safe_mtime(mojom_file)
285 mojom_dart_mtime = safe_mtime(os.path.join(SRC_DIR, mojom_dart_path))
286
287 if mojom_mtime > mojom_dart_mtime:
288 check_failure = True
289 print("Package %s has old %s" % (package, mojom_dart_path))
290 if not (package in packages_with_failures):
291 packages_with_failures.append(package)
292 continue
293 # Remember that this .mojom.dart file was updated after the .mojom file.
294 # This list is used to verify that all updated .mojom.dart files were
295 # updated because their source .mojom file changed.
296 updated_mojom_dart_files.append(mojom_dart_path)
297
298 # Check for updated .mojom.dart file without updated .mojom file.
299 for mojom_dart_file in mojom_darts:
300 # mojom_dart_file is not inside //mojo/dart/packages.
301 if not mojom_dart_file.startswith(PACKAGES_DIR):
302 continue
303
304 # Path relative to //mojo/dart/packages/
305 path_relative_to_packages = os.path.relpath(mojom_dart_file,
306 start=PACKAGES_DIR)
307 # Package name is first element of split path.
308 package = path_relative_to_packages.split(os.sep)[0]
309 # Path relative to src.
310 mojom_dart_path = os.path.relpath(mojom_dart_file, start=SRC_DIR)
311 # If mojom_dart_path is not in updated_mojom_dart_files, a .mojom.dart
312 # file was updated without updating the related .mojom file.
313 if not (mojom_dart_path in updated_mojom_dart_files):
314 check_failure = True
315 print("Package %s has new %s without updating source .mojom file." %
316 (package, mojom_dart_path))
317 if not (package in packages_with_failures):
318 packages_with_failures.append(package)
319
320 for package in packages_with_failures:
321 _print_regenerate_message(package)
322
323 return check_failure
324
325
326 def main():
327 parser = argparse.ArgumentParser(description='Generate a dart-pkg')
328 parser.add_argument('--affected-files',
329 action='store',
330 metavar='affected_files',
331 help='List of files that should be checked.',
332 nargs='+')
333 args = parser.parse_args()
334 packages = _build_package_map()
335
336 # This script runs in two modes, the first mode is invoked by PRESUBMIT.py
337 # and passes the list of affected files. This checks for the following cases:
338 # 1) An updated .mojom file without an updated .mojom.dart file.
339 # 2) An updated .mojom.dart file without an updated .mojom file.
340 # NOTE: Case 1) also handles the case of a new .mojom file being added.
341 #
342 # The second mode does a global check of all packages under
343 # //mojo/dart/packages. This checks for the following cases:
344 # 1) An updated .mojom file without an updated .mojom.dart file.
345 # 2) A .mojom.dart file without an associated .mojom file (deletion case).
346 if args.affected_files:
347 check_failure = presubmit_check(packages, args.affected_files)
348 else:
349 check_failure = global_check(packages)
350 if check_failure:
351 return 2
352 return 0
353
354 if __name__ == '__main__':
355 sys.exit(main())
OLDNEW
« 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