OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /// This library generates Mojo bindings for a Dart package. | |
6 | |
7 library generate; | |
8 | |
9 import 'dart:async'; | |
10 import 'dart:convert'; | |
11 import 'dart:developer' as dev; | |
12 import 'dart:io'; | |
13 | |
14 import 'package:mojom/src/utils.dart'; | |
15 import 'package:path/path.dart' as path; | |
16 | |
17 part 'mojom_finder.dart'; | |
18 | |
19 class MojomGenerator { | |
20 static dev.Counter _genMs; | |
21 final bool _errorOnDuplicate; | |
22 final bool _dryRun; | |
23 final Directory _mojoSdk; | |
24 | |
25 Map<String, String> _duplicateDetection; | |
26 int _generationMs; | |
27 | |
28 MojomGenerator(this._mojoSdk, | |
29 {bool errorOnDuplicate: true, bool profile: false, bool dryRun: false}) | |
30 : _errorOnDuplicate = errorOnDuplicate, | |
31 _dryRun = dryRun, | |
32 _generationMs = 0, | |
33 _duplicateDetection = new Map<String, String>() { | |
34 if (_genMs == null) { | |
35 _genMs = new dev.Counter("mojom generation", | |
36 "Time spent waiting for bindings generation script in ms."); | |
37 dev.Metrics.register(_genMs); | |
38 } | |
39 } | |
40 | |
41 generate(PackageInfo info) async { | |
42 // Count the .mojom files, and find the modification time of the most | |
43 // recently modified one. | |
44 int mojomCount = info.mojomFiles.length; | |
45 DateTime newestMojomTime; | |
46 for (File mojom in info.mojomFiles) { | |
47 DateTime mojomTime = await getModificationTime(mojom); | |
48 if ((newestMojomTime == null) || newestMojomTime.isBefore(mojomTime)) { | |
49 newestMojomTime = mojomTime; | |
50 } | |
51 } | |
52 | |
53 // Count the .mojom.dart files, and find the modification time of the | |
54 // least recently modified one. | |
55 List mojomDartInfo = await _findOldestMojomDart(info.packageDir); | |
56 DateTime oldestMojomDartTime = null; | |
57 int mojomDartCount = 0; | |
58 if (mojomDartInfo != null) { | |
59 oldestMojomDartTime = mojomDartInfo[0]; | |
60 mojomDartCount = mojomDartInfo[1]; | |
61 } | |
62 | |
63 // If we don't have enough .mojom.dart files, or if a .mojom file is | |
64 // newer than the oldest .mojom.dart file, then regenerate. | |
65 if ((mojomDartCount < mojomCount) || | |
66 _shouldRegenerate(newestMojomTime, oldestMojomDartTime)) { | |
67 for (File mojom in info.mojomFiles) { | |
68 await _generateForMojom( | |
69 mojom, info.importDir, info.packageDir, info.name); | |
70 } | |
71 // Delete any .mojom.dart files that are still older than mojomTime. | |
72 await _deleteOldMojomDart(info.packageDir, newestMojomTime); | |
73 } | |
74 } | |
75 | |
76 /// Under [package]/lib, returns the oldest modification time for a | |
77 /// .mojom.dart file. | |
78 _findOldestMojomDart(Directory package) async { | |
79 int mojomDartCount = 0; | |
80 DateTime oldestModTime; | |
81 Directory libDir = new Directory(path.join(package.path, 'lib')); | |
82 if (!await libDir.exists()) return null; | |
83 await for (var file in libDir.list(recursive: true, followLinks: false)) { | |
84 if (file is! File) continue; | |
85 if (!isMojomDart(file.path)) continue; | |
86 DateTime modTime = (await file.stat()).modified; | |
87 if ((oldestModTime == null) || oldestModTime.isAfter(modTime)) { | |
88 oldestModTime = modTime; | |
89 } | |
90 mojomDartCount++; | |
91 } | |
92 return [oldestModTime, mojomDartCount]; | |
93 } | |
94 | |
95 // Delete .mojom.dart files under [package] that are [olderThanThis]. | |
96 _deleteOldMojomDart(Directory package, DateTime olderThanThis) async { | |
97 Directory libDir = new Directory(path.join(package.path, 'lib')); | |
98 assert(await libDir.exists()); | |
99 await for (var file in libDir.list(recursive: true, followLinks: false)) { | |
100 if (file is! File) continue; | |
101 if (!isMojomDart(file.path)) continue; | |
102 DateTime modTime = (await file.stat()).modified; | |
103 if (modTime.isBefore(olderThanThis)) { | |
104 log.warning("Deleting stale .mojom.dart: $file"); | |
105 await file.delete(); | |
106 } | |
107 } | |
108 } | |
109 | |
110 /// If the .mojoms file or the newest .mojom is newer than the oldest | |
111 /// .mojom.dart, then regenerate everything. | |
112 bool _shouldRegenerate(DateTime mojomTime, DateTime mojomDartTime) { | |
113 return (mojomTime == null) || | |
114 (mojomDartTime == null) || | |
115 mojomTime.isAfter(mojomDartTime); | |
116 } | |
117 | |
118 _runBindingsGeneration(String script, List<String> arguments) async { | |
119 var result; | |
120 var stopwatch = new Stopwatch()..start(); | |
121 result = await Process.run(script, arguments); | |
122 stopwatch.stop(); | |
123 _genMs.value += stopwatch.elapsedMilliseconds; | |
124 return result; | |
125 } | |
126 | |
127 _generateForMojom(File mojom, Directory importDir, Directory destination, | |
128 String packageName) async { | |
129 if (!isMojom(mojom.path)) return; | |
130 log.info("_generateForMojom($mojom)"); | |
131 | |
132 final script = path.join( | |
133 _mojoSdk.path, 'tools', 'bindings', 'mojom_bindings_generator.py'); | |
134 final sdkInc = path.normalize(path.join(_mojoSdk.path, '..', '..')); | |
135 final outputDir = await destination.createTemp(); | |
136 final output = outputDir.path; | |
137 final arguments = [ | |
138 '--use_bundled_pylibs', | |
139 '-g', | |
140 'dart', | |
141 '-o', | |
142 output, | |
143 // TODO(zra): Are other include paths needed? | |
144 '-I', | |
145 sdkInc, | |
146 '-I', | |
147 importDir.path, | |
148 mojom.path | |
149 ]; | |
150 | |
151 log.info('Generating $mojom'); | |
152 log.info('$script ${arguments.join(" ")}'); | |
153 log.info('dryRun = $_dryRun'); | |
154 if (!_dryRun) { | |
155 final result = await _runBindingsGeneration(script, arguments); | |
156 if (result.exitCode != 0) { | |
157 log.info("bindings generation result = ${result.exitCode}"); | |
158 await outputDir.delete(recursive: true); | |
159 throw new GenerationError("$script failed:\n" | |
160 "code: ${result.exitCode}\n" | |
161 "stderr: ${result.stderr}\n" | |
162 "stdout: ${result.stdout}"); | |
163 } else { | |
164 log.info("bindings generation result = 0"); | |
165 } | |
166 | |
167 // Generated .mojom.dart is under $output/dart-pkg/$PACKAGE/lib/$X | |
168 // Move $X to |destination|/lib/$X. | |
169 // Throw an exception if $PACKGE != [packageName]. | |
170 final generatedDirName = path.join(output, 'dart-pkg'); | |
171 final generatedDir = new Directory(generatedDirName); | |
172 log.info("generatedDir= $generatedDir"); | |
173 assert(await generatedDir.exists()); | |
174 await for (var genpack in generatedDir.list()) { | |
175 if (genpack is! Directory) continue; | |
176 log.info("genpack = $genpack"); | |
177 var libDir = new Directory(path.join(genpack.path, 'lib')); | |
178 var name = path.relative(genpack.path, from: generatedDirName); | |
179 log.info("Found generated lib dir: $libDir"); | |
180 | |
181 if (packageName != name) { | |
182 await outputDir.delete(recursive: true); | |
183 throw new GenerationError( | |
184 "Tried to generate for package $name in package $packageName"); | |
185 } | |
186 | |
187 var copyDest = new Directory(path.join(destination.path, 'lib')); | |
188 log.info("Copy $libDir to $copyDest"); | |
189 await _copyBindings(copyDest, libDir); | |
190 } | |
191 | |
192 await outputDir.delete(recursive: true); | |
193 } | |
194 } | |
195 | |
196 /// Searches for .mojom.dart files under [sourceDir] and copies them to | |
197 /// [destDir]. | |
198 _copyBindings(Directory destDir, Directory sourceDir) async { | |
199 var sourceList = sourceDir.list(recursive: true, followLinks: false); | |
200 await for (var mojom in sourceList) { | |
201 if (mojom is! File) continue; | |
202 if (!isMojomDart(mojom.path)) continue; | |
203 log.info("Found $mojom"); | |
204 | |
205 final relative = path.relative(mojom.path, from: sourceDir.path); | |
206 final dest = path.join(destDir.path, relative); | |
207 final destDirectory = new Directory(path.dirname(dest)); | |
208 | |
209 if (_errorOnDuplicate && _duplicateDetection.containsKey(dest)) { | |
210 String original = _duplicateDetection[dest]; | |
211 throw new GenerationError( | |
212 'Conflict: Both ${original} and ${mojom.path} supply ${dest}'); | |
213 } | |
214 _duplicateDetection[dest] = mojom.path; | |
215 | |
216 log.info('Copying $mojom to $dest'); | |
217 if (!_dryRun) { | |
218 final File source = new File(mojom.path); | |
219 final File destFile = new File(dest); | |
220 if (await destFile.exists()) { | |
221 await destFile.delete(); | |
222 } | |
223 log.info("Ensuring $destDirectory exists"); | |
224 await destDirectory.create(recursive: true); | |
225 await source.copy(dest); | |
226 await markFileReadOnly(dest); | |
227 } | |
228 } | |
229 } | |
230 } | |
231 | |
232 /// Given the location of the Mojo SDK and a root directory from which to begin | |
233 /// a search. Find .mojom files, and generate bindings for the relevant | |
234 /// packages. | |
235 class TreeGenerator { | |
236 final MojomGenerator _generator; | |
237 final Directory _mojoSdk; | |
238 final Directory _mojomRootDir; | |
239 final Directory _dartRootDir; | |
240 final List<String> _skip; | |
241 final bool _dryRun; | |
242 | |
243 Set<String> _processedPackages; | |
244 | |
245 int errors; | |
246 | |
247 TreeGenerator( | |
248 Directory mojoSdk, this._mojomRootDir, this._dartRootDir, this._skip, | |
249 {bool dryRun: false}) | |
250 : _mojoSdk = mojoSdk, | |
251 _dryRun = dryRun, | |
252 _generator = new MojomGenerator(mojoSdk, dryRun: dryRun), | |
253 _processedPackages = new Set<String>(), | |
254 errors = 0; | |
255 | |
256 generate() async { | |
257 var mojomFinder = new MojomFinder(_mojomRootDir, _dartRootDir, _skip); | |
258 List<PackageInfo> packageInfos = await mojomFinder.find(); | |
259 for (PackageInfo info in packageInfos) { | |
260 await _runGenerate(info); | |
261 } | |
262 } | |
263 | |
264 _runGenerate(PackageInfo info) async { | |
265 try { | |
266 log.info('Generating bindings for ${info.name}'); | |
267 await _generator.generate(info); | |
268 log.info('Done generating bindings for ${info.name}'); | |
269 } on GenerationError catch (e) { | |
270 log.severe('Bindings generation failed for package ${info.name}: $e'); | |
271 errors += 1; | |
272 } | |
273 } | |
274 | |
275 bool _shouldSkip(File f) => containsPrefix(f.path, _skip); | |
276 } | |
277 | |
278 /// Given the root of a directory tree to check, and the root of a directory | |
279 /// tree containing the canonical generated bindings, checks that the files | |
280 /// match, and recommends actions to take in case they don't. The canonical | |
281 /// directory should contain a subdirectory for each package that might be | |
282 /// encountered while traversing the directory tree being checked. | |
283 class TreeChecker { | |
284 final Directory _mojoSdk; | |
285 final Directory _mojomRootDir; | |
286 final Directory _dartRootDir; | |
287 final Directory _canonical; | |
288 final List<String> _skip; | |
289 int _errors; | |
290 | |
291 TreeChecker(this._mojoSdk, this._mojomRootDir, this._dartRootDir, | |
292 this._canonical, this._skip) | |
293 : _errors = 0; | |
294 | |
295 check() async { | |
296 // Generate missing .mojoms files if needed. | |
297 var mojomFinder = new MojomFinder(_mojomRootDir, _dartRootDir, _skip); | |
298 List<PackageInfo> packageInfos = await mojomFinder.find(); | |
299 | |
300 for (PackageInfo info in packageInfos) { | |
301 log.info("Checking package at ${info.packageDir}"); | |
302 await _checkAll(info.packageDir); | |
303 await _checkSame(info.packageDir); | |
304 } | |
305 | |
306 // If there were multiple mismatches, explain how to regenerate the bindings | |
307 // for the whole tree. | |
308 if (_errors > 1) { | |
309 String dart = makeRelative(Platform.executable); | |
310 String packRoot = (Platform.packageRoot == "") | |
311 ? "" | |
312 : "-p " + makeRelative(Platform.packageRoot); | |
313 String scriptPath = makeRelative(path.fromUri(Platform.script)); | |
314 String mojoSdk = makeRelative(_mojoSdk.path); | |
315 String root = makeRelative(_mojomRootDir.path); | |
316 String dartRoot = makeRelative(_dartRootDir.path); | |
317 String skips = _skip.map((s) => "-s " + makeRelative(s)).join(" "); | |
318 stderr.writeln('It looks like there were multiple problems. ' | |
319 'You can run the following command to regenerate bindings for your ' | |
320 'whole tree:\n' | |
321 '\t$dart $packRoot $scriptPath gen -m $mojoSdk -r $root -o $dartRoot ' | |
322 '$skips'); | |
323 } | |
324 } | |
325 | |
326 int get errors => _errors; | |
327 | |
328 // Check that the files are the same. | |
329 _checkSame(Directory package) async { | |
330 Directory libDir = new Directory(path.join(package.path, 'lib')); | |
331 await for (var entry in libDir.list(recursive: true, followLinks: false)) { | |
332 if (entry is! File) continue; | |
333 if (!isMojomDart(entry.path)) continue; | |
334 | |
335 String relPath = path.relative(entry.path, from: package.parent.path); | |
336 File canonicalFile = new File(path.join(_canonical.path, relPath)); | |
337 if (!await canonicalFile.exists()) { | |
338 log.info("No canonical file for $entry"); | |
339 continue; | |
340 } | |
341 | |
342 log.info("Comparing $entry with $canonicalFile"); | |
343 int fileComparison = await compareFiles(entry, canonicalFile); | |
344 if (fileComparison != 0) { | |
345 String entryPath = makeRelative(entry.path); | |
346 String canonicalPath = makeRelative(canonicalFile.path); | |
347 if (fileComparison > 0) { | |
348 stderr.writeln('The generated file:\n\t$entryPath\n' | |
349 'is newer thanthe canonical file\n\t$canonicalPath\n,' | |
350 'and they are different. Regenerate canonical files?'); | |
351 } else { | |
352 String dart = makeRelative(Platform.executable); | |
353 String packRoot = (Platform.packageRoot == "") | |
354 ? "" | |
355 : "-p " + makeRelative(Platform.packageRoot); | |
356 String root = makeRelative(_mojomRootDir.path); | |
357 String packagePath = makeRelative(package.path); | |
358 String scriptPath = makeRelative(path.fromUri(Platform.script)); | |
359 String mojoSdk = makeRelative(_mojoSdk.path); | |
360 String skips = _skip.map((s) => "-s " + makeRelative(s)).join(" "); | |
361 stderr.writeln('For the package: $packagePath\n' | |
362 'The generated file:\n\t$entryPath\n' | |
363 'is older than the canonical file:\n\t$canonicalPath\n' | |
364 'and they are different. Regenerate by running:\n' | |
365 '\t$dart $packRoot $scriptPath single -m $mojoSdk -r $root ' | |
366 '-p $packagePath $skips'); | |
367 } | |
368 _errors++; | |
369 return; | |
370 } | |
371 } | |
372 } | |
373 | |
374 // Check that every .mojom.dart in the canonical package is also in the | |
375 // package we are checking. | |
376 _checkAll(Directory package) async { | |
377 String packageName = path.relative(package.path, from: package.parent.path); | |
378 String canonicalPackagePath = | |
379 path.join(_canonical.path, packageName, 'lib'); | |
380 Directory canonicalPackage = new Directory(canonicalPackagePath); | |
381 if (!await canonicalPackage.exists()) return; | |
382 | |
383 var canonicalPackages = | |
384 canonicalPackage.list(recursive: true, followLinks: false); | |
385 await for (var entry in canonicalPackages) { | |
386 if (entry is! File) continue; | |
387 if (!isMojomDart(entry.path)) continue; | |
388 | |
389 String relPath = path.relative(entry.path, from: canonicalPackage.path); | |
390 File genFile = new File(path.join(package.path, 'lib', relPath)); | |
391 log.info("Checking that $genFile exists"); | |
392 if (!await genFile.exists()) { | |
393 String dart = makeRelative(Platform.executable); | |
394 String packRoot = (Platform.packageRoot == "") | |
395 ? "" | |
396 : "-p " + makeRelative(Platform.packageRoot); | |
397 String root = makeRelative(_mojomRootDir.path); | |
398 String genFilePath = makeRelative(genFile.path); | |
399 String packagePath = makeRelative(package.path); | |
400 String scriptPath = makeRelative(path.fromUri(Platform.script)); | |
401 String mojoSdk = makeRelative(_mojoSdk.path); | |
402 String skips = _skip.map((s) => "-s " + makeRelative(s)).join(" "); | |
403 stderr.writeln('The generated file:\n\t$genFilePath\n' | |
404 'is needed but does not exist. Run the command\n' | |
405 '\t$dart $packRoot $scriptPath single -m $mojoSdk -r $root ' | |
406 '-p $packagePath $skips'); | |
407 _errors++; | |
408 return; | |
409 } | |
410 } | |
411 } | |
412 | |
413 bool _shouldSkip(File f) => containsPrefix(f.path, _skip); | |
414 } | |
OLD | NEW |