| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /// This script should be run by every project that consumes Mojom IDL | 5 /// This script should be run by every project that consumes Mojom IDL |
| 6 /// interfaces. It populates the 'mojom' package with the generated Dart | 6 /// interfaces. It populates the 'mojom' package with the generated Dart |
| 7 /// bindings for the Mojom IDL files. | 7 /// bindings for the Mojom IDL files. |
| 8 /// | 8 /// |
| 9 /// From a consuming project, it should be invoked as follows: | 9 /// From a consuming project, it should be invoked as follows: |
| 10 /// | 10 /// |
| 11 /// $ dart packages/mojom/generate.dart [-p package-root] | 11 /// $ dart packages/mojom/generate.dart [-p package-root] |
| 12 /// [-a additional-dirs] | 12 /// [-a additional-dirs] |
| 13 /// [-m mojo-sdk] | 13 /// [-m mojo-sdk] |
| 14 /// [-g] # Generate from .mojom files | 14 /// [-g] # Generate from .mojom files |
| 15 /// [-d] # Download from .mojoms files |
| 15 /// [-v] # verbose | 16 /// [-v] # verbose |
| 16 /// [-d] # Dry run | 17 /// [-f] # Fake (dry) run |
| 18 |
| 19 library generate; |
| 17 | 20 |
| 18 import 'dart:async'; | 21 import 'dart:async'; |
| 22 import 'dart:convert'; |
| 19 import 'dart:io'; | 23 import 'dart:io'; |
| 20 | 24 |
| 21 import 'package:args/args.dart' as args; | 25 import 'package:args/args.dart' as args; |
| 22 import 'package:path/path.dart' as path; | 26 import 'package:path/path.dart' as path; |
| 23 | 27 |
| 28 part 'src/utils.dart'; |
| 29 |
| 24 bool verbose; | 30 bool verbose; |
| 25 bool dryRun; | 31 bool dryRun; |
| 26 | 32 |
| 27 bool isMojomDart(String path) => path.endsWith('.mojom.dart'); | |
| 28 bool isMojom(String path) => path.endsWith('.mojom'); | |
| 29 | |
| 30 /// An Error for problems on the command line. | |
| 31 class CommandLineError extends Error { | |
| 32 final _msg; | |
| 33 CommandLineError(this._msg); | |
| 34 toString() => _msg; | |
| 35 } | |
| 36 | |
| 37 /// An Error for failures of the bindings generation script. | |
| 38 class GenerationError extends Error { | |
| 39 final _msg; | |
| 40 GenerationError(this._msg); | |
| 41 toString() => _msg; | |
| 42 } | |
| 43 | |
| 44 /// The base type of data passed to actions for [mojomDirIter]. | |
| 45 class PackageIterData { | |
| 46 final Directory _mojomPackage; | |
| 47 PackageIterData(this._mojomPackage); | |
| 48 Directory get mojomPackage => _mojomPackage; | |
| 49 } | |
| 50 | |
| 51 /// Data for [mojomDirIter] that includes the path to the Mojo SDK for bindings | |
| 52 /// generation. | |
| 53 class GenerateIterData extends PackageIterData { | |
| 54 final Directory _mojoSdk; | |
| 55 GenerateIterData(this._mojoSdk, Directory mojomPackage) | |
| 56 : super(mojomPackage); | |
| 57 Directory get mojoSdk => _mojoSdk; | |
| 58 } | |
| 59 | |
| 60 /// The type of action performed by [mojomDirIter]. | |
| 61 typedef Future MojomAction(PackageIterData data, Directory mojomDirectory); | |
| 62 | |
| 63 /// Iterates over mojom directories of Dart packages, taking some action for | |
| 64 /// each. | |
| 65 /// | |
| 66 /// For each 'mojom' subdirectory of each subdirectory in [packages], runs | |
| 67 /// [action] on the subdirectory passing along [data] to [action]. | |
| 68 mojomDirIter( | |
| 69 Directory packages, PackageIterData data, MojomAction action) async { | |
| 70 await for (var package in packages.list()) { | |
| 71 if (package is Directory) { | |
| 72 if (package.path == data.mojomPackage.path) continue; | |
| 73 if (verbose) print("package = $package"); | |
| 74 final mojomDirectory = new Directory(path.join(package.path, 'mojom')); | |
| 75 if (verbose) print("looking for = $mojomDirectory"); | |
| 76 if (await mojomDirectory.exists()) { | |
| 77 await action(data, mojomDirectory); | |
| 78 } else if (verbose) { | |
| 79 print("$mojomDirectory not found"); | |
| 80 } | |
| 81 } | |
| 82 } | |
| 83 } | |
| 84 | |
| 85 | |
| 86 /// Searches for .mojom.dart files under [mojomDirectory] and copies them to | 33 /// Searches for .mojom.dart files under [mojomDirectory] and copies them to |
| 87 /// the 'mojom' packages. | 34 /// the 'mojom' packages. |
| 88 copyAction(PackageIterData data, Directory mojomDirectory) async { | 35 copyAction(PackageIterData data, Directory mojomDirectory) async { |
| 89 await for (var mojom in mojomDirectory.list(recursive: true)) { | 36 await for (var mojom in mojomDirectory.list(recursive: true)) { |
| 90 if (mojom is! File) continue; | 37 if (mojom is! File) continue; |
| 91 if (!isMojomDart(mojom.path)) continue; | 38 if (!isMojomDart(mojom.path)) continue; |
| 92 if (verbose) print("Found $mojom"); | 39 if (verbose) print("Found $mojom"); |
| 93 | 40 |
| 94 final relative = path.relative(mojom.path, from: mojomDirectory.path); | 41 final relative = path.relative(mojom.path, from: mojomDirectory.path); |
| 95 final dest = path.join(data.mojomPackage.path, relative); | 42 final dest = path.join(data.mojomPackage.path, relative); |
| 96 final destDirectory = new Directory(path.dirname(dest)); | 43 final destDirectory = new Directory(path.dirname(dest)); |
| 97 | 44 |
| 98 if (verbose || dryRun) { | 45 if (verbose || dryRun) { |
| 99 print('Copying $mojom to $dest'); | 46 print('Copying $mojom to $dest'); |
| 100 } | 47 } |
| 101 | 48 |
| 102 if (!dryRun) { | 49 if (!dryRun) { |
| 103 final File source = new File(mojom.path); | 50 final File source = new File(mojom.path); |
| 104 if (verbose) print("Ensuring $destDirectory exists"); | 51 if (verbose) print("Ensuring $destDirectory exists"); |
| 105 await destDirectory.create(recursive: true); | 52 await destDirectory.create(recursive: true); |
| 106 source.copy(dest); | 53 source.copy(dest); |
| 107 } | 54 } |
| 108 } | 55 } |
| 109 } | 56 } |
| 110 | 57 |
| 111 | |
| 112 /// Searches for .mojom files under [mojomDirectory], generates .mojom.dart | 58 /// Searches for .mojom files under [mojomDirectory], generates .mojom.dart |
| 113 /// files for them, and copies them to the 'mojom' package. | 59 /// files for them, and copies them to the 'mojom' package. |
| 114 generateAction(GenerateIterData data, Directory mojomDirectory) async { | 60 generateAction(GenerateIterData data, Directory mojomDirectory) async { |
| 115 await for (var mojom in mojomDirectory.list(recursive: true)) { | 61 await for (var mojom in mojomDirectory.list(recursive: true)) { |
| 116 if (mojom is! File) continue; | 62 if (mojom is! File) continue; |
| 117 if (!isMojom(mojom.path)) continue; | 63 if (!isMojom(mojom.path)) continue; |
| 118 if (verbose) print("Found $mojom"); | 64 if (verbose) print("Found $mojom"); |
| 119 | 65 |
| 120 final script = path.join(data.mojoSdk.path, | 66 final script = path.join(data.mojoSdk.path, |
| 121 'mojo', 'public', 'tools', 'bindings', 'mojom_bindings_generator.py'); | 67 'tools', 'bindings', 'mojom_bindings_generator.py'); |
| 68 final sdkInc = path.normalize(path.join(data.mojoSdk.path, '..', '..')); |
| 122 final outputDir = await data.mojomPackage.createTemp(); | 69 final outputDir = await data.mojomPackage.createTemp(); |
| 123 final output = outputDir.path; | 70 final output = outputDir.path; |
| 124 final arguments = [ | 71 final arguments = [ |
| 125 '--use_bundled_pylibs', | 72 '--use_bundled_pylibs', |
| 73 '-g', 'dart', |
| 126 '-o', output, | 74 '-o', output, |
| 127 // TODO(zra): Are other include paths needed? | 75 // TODO(zra): Are other include paths needed? |
| 128 '-I', data.mojoSdk.path, | 76 '-I', sdkInc, |
| 129 '-I', mojomDirectory.path, | 77 '-I', mojomDirectory.path, |
| 130 mojom.path]; | 78 mojom.path]; |
| 131 | 79 |
| 132 if (verbose || dryRun) { | 80 if (verbose || dryRun) { |
| 133 print('Generating $mojom'); | 81 print('Generating $mojom'); |
| 134 print('$script ${arguments.join(" ")}'); | 82 print('$script ${arguments.join(" ")}'); |
| 135 } | 83 } |
| 136 if (!dryRun) { | 84 if (!dryRun) { |
| 137 final result = await Process.run(script, arguments); | 85 final result = await Process.run(script, arguments); |
| 138 if (result.exitCode != 0) { | 86 if (result.exitCode != 0) { |
| 139 throw new GenerationError("$script failed:\n${result.stderr}"); | 87 throw new GenerationError("$script failed:\n${result.stderr}"); |
| 140 } | 88 } |
| 141 // Generated .mojom.dart is under $output/dart-gen/mojom/lib/X | 89 // Generated .mojom.dart is under $output/dart-gen/mojom/lib/X |
| 142 // Move X to $mojomPackage. Then rm -rf $output | 90 // Move X to $mojomPackage. Then rm -rf $output |
| 143 final generatedDirName = path.join(output, 'dart-gen', 'mojom', 'lib'); | 91 final generatedDirName = path.join(output, 'dart-gen', 'mojom', 'lib'); |
| 144 final generatedDir = new Directory(generatedDirName); | 92 final generatedDir = new Directory(generatedDirName); |
| 145 | 93 |
| 146 await copyAction(data, generatedDir); | 94 await copyAction(data, generatedDir); |
| 147 | 95 |
| 148 await outputDir.delete(recursive: true); | 96 await outputDir.delete(recursive: true); |
| 149 } | 97 } |
| 150 } | 98 } |
| 151 } | 99 } |
| 152 | 100 |
| 101 /// In each package, look for a file named .mojoms. Populate a package's |
| 102 /// mojom directory with the downloaded mojoms, creating the directory if |
| 103 /// needed. The .mojoms file should be formatted as follows: |
| 104 /// ''' |
| 105 /// root: https://www.example.com/mojoms |
| 106 /// path/to/some/mojom1.mojom |
| 107 /// path/to/some/other/mojom2.mojom |
| 108 /// |
| 109 /// root: https://www.example-two.com/mojoms |
| 110 /// path/to/example/two/mojom1.mojom |
| 111 /// ... |
| 112 /// |
| 113 /// Lines beginning with '#' are ignored. |
| 114 downloadAction(GenerateIterData data, Directory packageDirectory) async { |
| 115 var mojomsPath = path.join(packageDirectory.path, '.mojoms'); |
| 116 var mojomsFile = new File(mojomsPath); |
| 117 if (!await mojomsFile.exists()) return; |
| 118 if (verbose) print("Found .mojoms file: $mojomsPath"); |
| 119 |
| 120 Directory mojomsDir; |
| 121 var httpClient = new HttpClient(); |
| 122 int repoCount = 0; |
| 123 int mojomCount = 0; |
| 124 String repoRoot; |
| 125 for (String line in await mojomsFile.readAsLines()) { |
| 126 line = line.trim(); |
| 127 if (line.isEmpty || line.startsWith('#')) continue; |
| 128 |
| 129 if (line.startsWith('root:')) { |
| 130 if ((mojomsDir != null) && (mojomCount == 0)) { |
| 131 throw new DownloadError("root with no mojoms: $repoRoot"); |
| 132 } |
| 133 mojomCount = 0; |
| 134 var rootWords = line.split(" "); |
| 135 if (rootWords.length != 2) { |
| 136 throw new DownloadError("Malformed root: $line"); |
| 137 } |
| 138 repoRoot = rootWords[1]; |
| 139 if (verbose) print("Found repo root: $repoRoot"); |
| 140 if (!repoRoot.startsWith('http://') && |
| 141 !repoRoot.startsWith('https://')) { |
| 142 throw new DownloadError( |
| 143 'Mojom repo "root" should be an http or https URL: $line'); |
| 144 } |
| 145 mojomsDir = new Directory(path.join( |
| 146 packageDirectory.parent.path, 'mojm.repo.$repoCount', 'mojom')); |
| 147 await mojomsDir.create(recursive: true); |
| 148 repoCount++; |
| 149 } else { |
| 150 if (mojomsDir == null) { |
| 151 throw new DownloadError('Malformed .mojoms file: $mojomsPath'); |
| 152 } |
| 153 String url = "$repoRoot/$line"; |
| 154 if (verbose) print("Found $url"); |
| 155 String fileString = await getUrl(httpClient, url); |
| 156 if (verbose) print("Downloaded $url"); |
| 157 String filePath = path.join(mojomsDir.path, line); |
| 158 var file = new File(filePath); |
| 159 if (!await file.exists()) { |
| 160 await file.create(recursive: true); |
| 161 await file.writeAsString(fileString); |
| 162 if (verbose) print("Wrote $filePath"); |
| 163 } |
| 164 mojomCount++; |
| 165 } |
| 166 } |
| 167 } |
| 153 | 168 |
| 154 /// Ensures that the directories in [additionalPaths] are absolute and exist, | 169 /// Ensures that the directories in [additionalPaths] are absolute and exist, |
| 155 /// and creates Directories for them, which are returned. | 170 /// and creates Directories for them, which are returned. |
| 156 validateAdditionalDirs(Iterable additionalPaths) async { | 171 Future<List<Directory>> validateAdditionalDirs(Iterable additionalPaths) async { |
| 157 var additionalDirs = []; | 172 var additionalDirs = []; |
| 158 for (var mojomPath in additionalPaths) { | 173 for (var mojomPath in additionalPaths) { |
| 159 final mojomDir = new Directory(mojomPath); | 174 final mojomDir = new Directory(mojomPath); |
| 160 if (!mojomDir.isAbsolute) { | 175 if (!mojomDir.isAbsolute) { |
| 161 throw new CommandLineError( | 176 throw new CommandLineError( |
| 162 "All --additional-mojom-dir parameters must be absolute paths."); | 177 "All --additional-mojom-dir parameters must be absolute paths."); |
| 163 } | 178 } |
| 164 if (!(await mojomDir.exists())) { | 179 if (!(await mojomDir.exists())) { |
| 165 throw new CommandLineError( | 180 throw new CommandLineError( |
| 166 "The additional mojom directory $mojomDir must exist"); | 181 "The additional mojom directory $mojomDir must exist"); |
| 167 } | 182 } |
| 168 additionalDirs.add(mojomDir); | 183 additionalDirs.add(mojomDir); |
| 169 } | 184 } |
| 170 if (verbose) print("additional_mojom_dirs = $additionalDirs"); | 185 if (verbose) print("additional_mojom_dirs = $additionalDirs"); |
| 171 return additionalDirs; | 186 return additionalDirs; |
| 172 } | 187 } |
| 173 | 188 |
| 189 class GenerateOptions { |
| 190 final Directory packages; |
| 191 final Directory mojomPackage; |
| 192 final Directory mojoSdk; |
| 193 final List<Directory> additionalDirs; |
| 194 final bool download; |
| 195 final bool generate; |
| 196 GenerateOptions( |
| 197 this.packages, this.mojomPackage, this.mojoSdk, this.additionalDirs, |
| 198 this.download, this.generate); |
| 199 } |
| 174 | 200 |
| 175 main(List<String> arguments) async { | 201 Future<GenerateOptions> parseArguments(List<String> arguments) async { |
| 176 final parser = new args.ArgParser() | 202 final parser = new args.ArgParser() |
| 177 ..addOption('additional-mojom-dir', | 203 ..addOption('additional-mojom-dir', |
| 178 abbr: 'a', | 204 abbr: 'a', |
| 179 allowMultiple: true, | 205 allowMultiple: true, |
| 180 help: 'Absolute path to an additional directory containing mojom.dart' | 206 help: 'Absolute path to an additional directory containing mojom.dart' |
| 181 'files to put in the mojom package. May be specified multiple times.') | 207 'files to put in the mojom package. May be specified multiple times.') |
| 182 ..addFlag('dry-run', | 208 ..addFlag('download', |
| 183 abbr: 'd', | 209 abbr: 'd', |
| 184 defaultsTo: false, | 210 defaultsTo: false, |
| 185 help: 'Print the copy operations that would have been run, but' | 211 help: 'Searches packages for a .mojoms file, and downloads .mojom files' |
| 186 'do not copy anything.') | 212 'as speficied in that file. Implies -g.') |
| 213 ..addFlag('fake', |
| 214 abbr: 'f', |
| 215 defaultsTo: false, |
| 216 help: 'Print the operations that would have been run, but' |
| 217 'do not run anything.') |
| 187 ..addFlag('generate', | 218 ..addFlag('generate', |
| 188 abbr: 'g', | 219 abbr: 'g', |
| 189 defaultsTo: false, | 220 defaultsTo: false, |
| 190 help: 'Generate Dart bindings for .mojom files.') | 221 help: 'Generate Dart bindings for .mojom files.') |
| 191 ..addOption('mojo-sdk', | 222 ..addOption('mojo-sdk', |
| 192 abbr: 'm', | 223 abbr: 'm', |
| 193 defaultsTo: Platform.environment['MOJO_SDK'], | 224 defaultsTo: Platform.environment['MOJO_SDK'], |
| 194 help: 'Absolute path to the Mojo SDK, which can also be specified ' | 225 help: 'Absolute path to the Mojo SDK, which can also be specified ' |
| 195 'with the environment variable MOJO_SDK.') | 226 'with the environment variable MOJO_SDK.') |
| 196 ..addOption('package-root', | 227 ..addOption('package-root', |
| 197 abbr: 'p', | 228 abbr: 'p', |
| 198 defaultsTo: path.join(Directory.current.path, 'packages'), | 229 defaultsTo: path.join(Directory.current.path, 'packages'), |
| 199 help: 'An absolute path to an application\'s package root') | 230 help: 'An absolute path to an application\'s package root') |
| 200 ..addFlag('verbose', abbr: 'v', defaultsTo: false); | 231 ..addFlag('verbose', abbr: 'v', defaultsTo: false); |
| 201 final result = parser.parse(arguments); | 232 final result = parser.parse(arguments); |
| 202 verbose = result['verbose']; | 233 verbose = result['verbose']; |
| 203 dryRun = result['dry-run']; | 234 dryRun = result['fake']; |
| 204 | 235 |
| 205 final packages = new Directory(result['package-root']); | 236 final packages = new Directory(result['package-root']); |
| 206 if (!packages.isAbsolute) { | 237 if (!packages.isAbsolute) { |
| 207 throw new CommandLineError( | 238 throw new CommandLineError( |
| 208 "The --package-root parameter must be an absolute path."); | 239 "The --package-root parameter must be an absolute path."); |
| 209 } | 240 } |
| 210 if (verbose) print("packages = $packages"); | 241 if (verbose) print("packages = $packages"); |
| 211 if (!(await packages.exists())) { | 242 if (!(await packages.exists())) { |
| 212 throw new CommandLineError( | 243 throw new CommandLineError( |
| 213 "The packages directory $packages must exist"); | 244 "The packages directory $packages must exist"); |
| 214 } | 245 } |
| 215 | 246 |
| 216 final mojomPackage = new Directory(path.join(packages.path, 'mojom')); | 247 final mojomPackage = new Directory(path.join(packages.path, 'mojom')); |
| 217 if (verbose) print("mojom package = $mojomPackage"); | 248 if (verbose) print("mojom package = $mojomPackage"); |
| 218 if (!(await mojomPackage.exists())) { | 249 if (!(await mojomPackage.exists())) { |
| 219 throw new CommandLineError( | 250 throw new CommandLineError( |
| 220 "The mojom package directory $mojomPackage must exist"); | 251 "The mojom package directory $mojomPackage must exist"); |
| 221 } | 252 } |
| 222 | 253 |
| 223 final generate = result['generate']; | 254 final download = result['download']; |
| 255 final generate = result['generate'] || download; |
| 224 var mojoSdk = null; | 256 var mojoSdk = null; |
| 225 if (generate) { | 257 if (generate) { |
| 226 final mojoSdkPath = result['mojo-sdk']; | 258 final mojoSdkPath = result['mojo-sdk']; |
| 227 if (mojoSdkPath == null) { | 259 if (mojoSdkPath == null) { |
| 228 throw new CommandLineError( | 260 throw new CommandLineError( |
| 229 "The Mojo SDK directory must be specified with the --mojo-sdk flag or" | 261 "The Mojo SDK directory must be specified with the --mojo-sdk flag or" |
| 230 "the MOJO_SDK environment variable."); | 262 "the MOJO_SDK environment variable."); |
| 231 } | 263 } |
| 232 mojoSdk = new Directory(mojoSdkPath); | 264 mojoSdk = new Directory(mojoSdkPath); |
| 233 if (verbose) print("Mojo SDK = $mojoSdk"); | 265 if (verbose) print("Mojo SDK = $mojoSdk"); |
| 234 if (!(await mojoSdk.exists())) { | 266 if (!(await mojoSdk.exists())) { |
| 235 throw new CommandLineError( | 267 throw new CommandLineError( |
| 236 "The specified Mojo SDK directory $mojoSdk must exist."); | 268 "The specified Mojo SDK directory $mojoSdk must exist."); |
| 237 } | 269 } |
| 238 } | 270 } |
| 239 | 271 |
| 240 await mojomDirIter(packages, new PackageIterData(mojomPackage), copyAction); | |
| 241 if (generate) { | |
| 242 await mojomDirIter(packages, new GenerateIterData(mojoSdk, mojomPackage), | |
| 243 generateAction); | |
| 244 } | |
| 245 | |
| 246 final additionalDirs = | 272 final additionalDirs = |
| 247 await validateAdditionalDirs(result['additional-mojom-dir']); | 273 await validateAdditionalDirs(result['additional-mojom-dir']); |
| 248 final data = new GenerateIterData(mojoSdk, mojomPackage); | 274 |
| 249 for (var mojomDir in additionalDirs) { | 275 return new GenerateOptions( |
| 276 packages, mojomPackage, mojoSdk, additionalDirs, download, generate); |
| 277 } |
| 278 |
| 279 main(List<String> arguments) async { |
| 280 var options = await parseArguments(arguments); |
| 281 |
| 282 // Copy any pregenerated files form packages. |
| 283 await mojomDirIter( |
| 284 options.packages, |
| 285 new PackageIterData(options.mojomPackage), |
| 286 copyAction); |
| 287 |
| 288 // Download .mojom files. These will be picked up by the generation step |
| 289 // below. |
| 290 if (options.download) { |
| 291 await packageDirIter(options.packages, null, downloadAction); |
| 292 } |
| 293 |
| 294 // Generate mojom files. |
| 295 if (options.generate) { |
| 296 await mojomDirIter( |
| 297 options.packages, |
| 298 new GenerateIterData(options.mojoSdk, options.mojomPackage), |
| 299 generateAction); |
| 300 } |
| 301 |
| 302 // Copy pregenerated files from specified external directories. |
| 303 final data = new GenerateIterData(options.mojoSdk, options.mojomPackage); |
| 304 for (var mojomDir in options.additionalDirs) { |
| 250 await copyAction(data, mojomDir); | 305 await copyAction(data, mojomDir); |
| 251 if (generate) { | 306 if (options.generate) { |
| 252 await generateAction(data, mojomDir); | 307 await generateAction(data, mojomDir); |
| 253 } | 308 } |
| 254 } | 309 } |
| 255 } | 310 } |
| OLD | NEW |