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 |