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 direcoty with the downloaded mojoms, creating the directory if needed. | |
Cutch
2015/07/20 15:39:09
direcoty -> directory
zra
2015/07/20 21:09:05
Done.
| |
103 /// The .mojoms file should be formatted as follows: | |
104 /// ''' | |
105 /// root: https://www.example.com/mojoms | |
Cutch
2015/07/20 15:39:08
Why use a stateful format over a list of uris?
zra
2015/07/20 21:09:05
Related mojoms can import each other. The conventi
| |
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 if (line.isEmpty || line.startsWith('#')) continue; | |
Cutch
2015/07/20 15:39:08
strip leading whitespace?
zra
2015/07/20 21:09:05
Done.
| |
127 | |
128 if (line.startsWith('root:')) { | |
129 if ((mojomsDir != null) && (mojomCount == 0)) { | |
130 throw new DownloadError("root with no mojoms: $repoRoot"); | |
131 } | |
132 mojomCount = 0; | |
133 var rootWords = line.split(" "); | |
134 if (rootWords.length != 2) { | |
135 throw new DownloadError("Malformed root: $line"); | |
136 } | |
137 repoRoot = rootWords[1]; | |
138 if (verbose) print("Found repo root: $repoRoot"); | |
139 if (!repoRoot.startsWith('https://')) { | |
Cutch
2015/07/20 15:39:08
What if I want to use http?
zra
2015/07/20 21:09:05
Removed restriction as discussed.
| |
140 throw new DownloadError( | |
141 'Mojom repo "root" should be an https URL: $line'); | |
142 } | |
143 mojomsDir = new Directory(path.join( | |
144 packageDirectory.parent.path, 'mojm.repo.$repoCount', 'mojom')); | |
145 await mojomsDir.create(recursive: true); | |
146 repoCount++; | |
147 } else { | |
148 if (mojomsDir == null) { | |
149 throw new DownloadError('Malformed .mojoms file: $mojomsPath'); | |
150 } | |
151 String url = "$repoRoot/$line"; | |
152 if (verbose) print("Found $url"); | |
153 String fileString = await getUrl(httpClient, url); | |
154 if (verbose) print("Downloaded $url"); | |
155 String filePath = path.join(mojomsDir.path, line); | |
156 var file = new File(filePath); | |
157 if (!await file.exists()) { | |
158 await file.create(recursive: true); | |
159 await file.writeAsString(fileString); | |
160 if (verbose) print("Wrote $filePath"); | |
161 } | |
162 mojomCount++; | |
163 } | |
164 } | |
165 } | |
153 | 166 |
154 /// Ensures that the directories in [additionalPaths] are absolute and exist, | 167 /// Ensures that the directories in [additionalPaths] are absolute and exist, |
155 /// and creates Directories for them, which are returned. | 168 /// and creates Directories for them, which are returned. |
156 validateAdditionalDirs(Iterable additionalPaths) async { | 169 Future<List<Directory>> validateAdditionalDirs(Iterable additionalPaths) async { |
157 var additionalDirs = []; | 170 var additionalDirs = []; |
158 for (var mojomPath in additionalPaths) { | 171 for (var mojomPath in additionalPaths) { |
159 final mojomDir = new Directory(mojomPath); | 172 final mojomDir = new Directory(mojomPath); |
160 if (!mojomDir.isAbsolute) { | 173 if (!mojomDir.isAbsolute) { |
161 throw new CommandLineError( | 174 throw new CommandLineError( |
162 "All --additional-mojom-dir parameters must be absolute paths."); | 175 "All --additional-mojom-dir parameters must be absolute paths."); |
163 } | 176 } |
164 if (!(await mojomDir.exists())) { | 177 if (!(await mojomDir.exists())) { |
165 throw new CommandLineError( | 178 throw new CommandLineError( |
166 "The additional mojom directory $mojomDir must exist"); | 179 "The additional mojom directory $mojomDir must exist"); |
167 } | 180 } |
168 additionalDirs.add(mojomDir); | 181 additionalDirs.add(mojomDir); |
169 } | 182 } |
170 if (verbose) print("additional_mojom_dirs = $additionalDirs"); | 183 if (verbose) print("additional_mojom_dirs = $additionalDirs"); |
171 return additionalDirs; | 184 return additionalDirs; |
172 } | 185 } |
173 | 186 |
187 class GenerateOptions { | |
188 final Directory packages; | |
189 final Directory mojomPackage; | |
190 final Directory mojoSdk; | |
191 final List<Directory> additionalDirs; | |
192 final bool download; | |
193 final bool generate; | |
194 GenerateOptions( | |
195 this.packages, this.mojomPackage, this.mojoSdk, this.additionalDirs, | |
196 this.download, this.generate); | |
197 } | |
174 | 198 |
175 main(List<String> arguments) async { | 199 Future<GenerateOptions> parseArguments(List<String> arguments) async { |
176 final parser = new args.ArgParser() | 200 final parser = new args.ArgParser() |
177 ..addOption('additional-mojom-dir', | 201 ..addOption('additional-mojom-dir', |
178 abbr: 'a', | 202 abbr: 'a', |
179 allowMultiple: true, | 203 allowMultiple: true, |
180 help: 'Absolute path to an additional directory containing mojom.dart' | 204 help: 'Absolute path to an additional directory containing mojom.dart' |
181 'files to put in the mojom package. May be specified multiple times.') | 205 'files to put in the mojom package. May be specified multiple times.') |
182 ..addFlag('dry-run', | 206 ..addFlag('download', |
183 abbr: 'd', | 207 abbr: 'd', |
184 defaultsTo: false, | 208 defaultsTo: false, |
185 help: 'Print the copy operations that would have been run, but' | 209 help: 'Searches packages for a .mojoms file, and downloads .mojom files' |
186 'do not copy anything.') | 210 'as speficied in that file. Implies -g.') |
211 ..addFlag('fake', | |
212 abbr: 'f', | |
213 defaultsTo: false, | |
214 help: 'Print the operations that would have been run, but' | |
215 'do not run anything.') | |
187 ..addFlag('generate', | 216 ..addFlag('generate', |
188 abbr: 'g', | 217 abbr: 'g', |
189 defaultsTo: false, | 218 defaultsTo: false, |
190 help: 'Generate Dart bindings for .mojom files.') | 219 help: 'Generate Dart bindings for .mojom files.') |
191 ..addOption('mojo-sdk', | 220 ..addOption('mojo-sdk', |
192 abbr: 'm', | 221 abbr: 'm', |
193 defaultsTo: Platform.environment['MOJO_SDK'], | 222 defaultsTo: Platform.environment['MOJO_SDK'], |
194 help: 'Absolute path to the Mojo SDK, which can also be specified ' | 223 help: 'Absolute path to the Mojo SDK, which can also be specified ' |
195 'with the environment variable MOJO_SDK.') | 224 'with the environment variable MOJO_SDK.') |
196 ..addOption('package-root', | 225 ..addOption('package-root', |
197 abbr: 'p', | 226 abbr: 'p', |
198 defaultsTo: path.join(Directory.current.path, 'packages'), | 227 defaultsTo: path.join(Directory.current.path, 'packages'), |
199 help: 'An absolute path to an application\'s package root') | 228 help: 'An absolute path to an application\'s package root') |
200 ..addFlag('verbose', abbr: 'v', defaultsTo: false); | 229 ..addFlag('verbose', abbr: 'v', defaultsTo: false); |
201 final result = parser.parse(arguments); | 230 final result = parser.parse(arguments); |
202 verbose = result['verbose']; | 231 verbose = result['verbose']; |
203 dryRun = result['dry-run']; | 232 dryRun = result['fake']; |
204 | 233 |
205 final packages = new Directory(result['package-root']); | 234 final packages = new Directory(result['package-root']); |
206 if (!packages.isAbsolute) { | 235 if (!packages.isAbsolute) { |
207 throw new CommandLineError( | 236 throw new CommandLineError( |
208 "The --package-root parameter must be an absolute path."); | 237 "The --package-root parameter must be an absolute path."); |
209 } | 238 } |
210 if (verbose) print("packages = $packages"); | 239 if (verbose) print("packages = $packages"); |
211 if (!(await packages.exists())) { | 240 if (!(await packages.exists())) { |
212 throw new CommandLineError( | 241 throw new CommandLineError( |
213 "The packages directory $packages must exist"); | 242 "The packages directory $packages must exist"); |
214 } | 243 } |
215 | 244 |
216 final mojomPackage = new Directory(path.join(packages.path, 'mojom')); | 245 final mojomPackage = new Directory(path.join(packages.path, 'mojom')); |
217 if (verbose) print("mojom package = $mojomPackage"); | 246 if (verbose) print("mojom package = $mojomPackage"); |
218 if (!(await mojomPackage.exists())) { | 247 if (!(await mojomPackage.exists())) { |
219 throw new CommandLineError( | 248 throw new CommandLineError( |
220 "The mojom package directory $mojomPackage must exist"); | 249 "The mojom package directory $mojomPackage must exist"); |
221 } | 250 } |
222 | 251 |
223 final generate = result['generate']; | 252 final download = result['download']; |
253 final generate = result['generate'] || download; | |
224 var mojoSdk = null; | 254 var mojoSdk = null; |
225 if (generate) { | 255 if (generate) { |
226 final mojoSdkPath = result['mojo-sdk']; | 256 final mojoSdkPath = result['mojo-sdk']; |
227 if (mojoSdkPath == null) { | 257 if (mojoSdkPath == null) { |
228 throw new CommandLineError( | 258 throw new CommandLineError( |
229 "The Mojo SDK directory must be specified with the --mojo-sdk flag or" | 259 "The Mojo SDK directory must be specified with the --mojo-sdk flag or" |
230 "the MOJO_SDK environment variable."); | 260 "the MOJO_SDK environment variable."); |
231 } | 261 } |
232 mojoSdk = new Directory(mojoSdkPath); | 262 mojoSdk = new Directory(mojoSdkPath); |
233 if (verbose) print("Mojo SDK = $mojoSdk"); | 263 if (verbose) print("Mojo SDK = $mojoSdk"); |
234 if (!(await mojoSdk.exists())) { | 264 if (!(await mojoSdk.exists())) { |
235 throw new CommandLineError( | 265 throw new CommandLineError( |
236 "The specified Mojo SDK directory $mojoSdk must exist."); | 266 "The specified Mojo SDK directory $mojoSdk must exist."); |
237 } | 267 } |
238 } | 268 } |
239 | 269 |
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 = | 270 final additionalDirs = |
247 await validateAdditionalDirs(result['additional-mojom-dir']); | 271 await validateAdditionalDirs(result['additional-mojom-dir']); |
248 final data = new GenerateIterData(mojoSdk, mojomPackage); | 272 |
249 for (var mojomDir in additionalDirs) { | 273 return new GenerateOptions( |
274 packages, mojomPackage, mojoSdk, additionalDirs, download, generate); | |
275 } | |
276 | |
277 main(List<String> arguments) async { | |
278 var options = await parseArguments(arguments); | |
279 | |
280 // Copy any pregenerated files form packages. | |
281 await mojomDirIter( | |
282 options.packages, | |
283 new PackageIterData(options.mojomPackage), | |
284 copyAction); | |
285 | |
286 // Download .mojom files. These will be picked up by the generation step | |
287 // below. | |
288 if (options.download) { | |
289 await packageDirIter(options.packages, null, downloadAction); | |
290 } | |
291 | |
292 // Generate mojom files. | |
293 if (options.generate) { | |
294 await mojomDirIter( | |
295 options.packages, | |
296 new GenerateIterData(options.mojoSdk, options.mojomPackage), | |
297 generateAction); | |
298 } | |
299 | |
300 // Copy pregenerated files from specified external directories. | |
301 final data = new GenerateIterData(options.mojoSdk, options.mojomPackage); | |
302 for (var mojomDir in options.additionalDirs) { | |
250 await copyAction(data, mojomDir); | 303 await copyAction(data, mojomDir); |
251 if (generate) { | 304 if (options.generate) { |
252 await generateAction(data, mojomDir); | 305 await generateAction(data, mojomDir); |
253 } | 306 } |
254 } | 307 } |
255 } | 308 } |
OLD | NEW |