| 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 |