OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:io'; | |
6 | |
7 import 'package:args/args.dart'; | |
8 import 'package:analyzer/src/services/formatter_impl.dart'; | |
9 import 'package:async_await/async_await.dart' as async_await; | |
10 import 'package:stack_trace/stack_trace.dart'; | |
11 import 'package:path/path.dart' as p; | |
12 | |
13 /// The path to pub's root directory (sdk/lib/_internal/pub) in the Dart repo. | |
14 /// | |
15 /// This assumes this script is itself being run from within the repo. | |
16 final sourceDir = p.dirname(p.dirname(p.fromUri(Platform.script))); | |
17 | |
18 /// The [sourceDir] as a URL, for use in import strings. | |
19 final sourceUrl = p.toUri(sourceDir).toString(); | |
20 | |
21 /// The directory that compiler output should be written to. | |
22 String generatedDir; | |
23 | |
24 /// `true` if any file failed to compile. | |
25 bool hadFailure = false; | |
26 | |
27 bool verbose = false; | |
28 | |
29 /// Prefix for imports in pub that import dart2js libraries. | |
30 final _compilerPattern = new RegExp(r"import '(\.\./)+compiler"); | |
31 | |
32 /// Matches the Git commit hash of the compiler stored in the README.md file. | |
33 /// | |
34 /// This is used both to find the current commit and replace it with the new | |
35 /// one. | |
36 final _commitPattern = new RegExp(r"[a-f0-9]{40}"); | |
37 | |
38 /// The template for the README that's added to the generated source. | |
39 /// | |
40 /// This is used to store the current commit of the async_await compiler. | |
41 const _README = """ | |
42 Pub is currently dogfooding the new Dart async/await syntax. Since the Dart VM | |
43 doesn't natively support it yet, we are using the [async-await][] compiler | |
44 package. | |
45 | |
46 [async-await]: https://github.com/dart-lang/async_await | |
47 | |
48 We run that to compile pub-using-await from sdk/lib/_internal/pub down to | |
49 vanilla Dart code which is what you see here. To interoperate more easily with | |
50 the rest of the repositry, we check in that generated code. | |
51 | |
52 When bug #104 is fixed, we can remove this entirely. | |
53 | |
54 The code here was compiled using the async-await compiler at commit: | |
55 | |
56 <<COMMIT>> | |
57 | |
58 (Note: this file is also parsed by a tool to update the above commit, so be | |
59 careful not to reformat it.) | |
60 """; | |
61 | |
62 /// This runs the async/await compiler on all of the pub source code. | |
63 /// | |
64 /// It reads from the repo and writes the compiled output into the given build | |
65 /// directory (using the same file names and relative layout). Does not | |
66 /// compile files that haven't changed since the last time they were compiled. | |
67 // TODO(rnystrom): Remove this when #104 is fixed. | |
68 void main(List<String> arguments) { | |
69 var parser = new ArgParser(allowTrailingOptions: true); | |
70 | |
71 parser.addFlag("verbose", callback: (value) => verbose = value); | |
72 | |
73 var force = false; | |
74 parser.addFlag("force", callback: (value) => force = value); | |
75 | |
76 var buildDir; | |
77 parser.addOption("snapshot-build-dir", callback: (value) => buildDir = value); | |
78 | |
79 try { | |
80 var rest = parser.parse(arguments).rest; | |
81 if (rest.isEmpty) { | |
82 throw new FormatException('Missing generated directory.'); | |
83 } else if (rest.length > 1) { | |
84 throw new FormatException( | |
85 'Unexpected arguments: ${rest.skip(1).join(" ")}.'); | |
86 } | |
87 | |
88 generatedDir = rest.first; | |
89 } on FormatException catch (ex) { | |
90 stderr.writeln(ex); | |
91 stderr.writeln(); | |
92 stderr.writeln( | |
93 "Usage: dart async_compile.dart [--verbose] [--force] " | |
94 "[--snapshot-build-dir <dir>] <generated dir>"); | |
95 exit(64); | |
96 } | |
97 | |
98 // See what version (i.e. Git commit) of the async-await compiler we | |
99 // currently have. If this is different from the version that was used to | |
100 // compile the sources, recompile everything. | |
101 var currentCommit = _getCurrentCommit(); | |
102 | |
103 var readmePath = p.join(generatedDir, "README.md"); | |
104 var lastCommit; | |
105 try { | |
106 var readme = new File(readmePath).readAsStringSync(); | |
107 var match = _commitPattern.firstMatch(readme); | |
108 if (match == null) { | |
109 stderr.writeln("Could not find compiler commit hash in README.md."); | |
110 exit(1); | |
111 } | |
112 | |
113 lastCommit = match[0]; | |
114 } on IOException catch (error, stackTrace) { | |
115 if (verbose) { | |
116 stderr.writeln( | |
117 "Failed to load $readmePath: $error\n" "${new Trace.from(stackTrace)}"
); | |
118 } | |
119 } | |
120 | |
121 var numFiles = 0; | |
122 var numCompiled = 0; | |
123 | |
124 // Compile any modified or missing files. | |
125 var sources = new Set(); | |
126 for (var entry in new Directory(sourceDir).listSync(recursive: true)) { | |
127 if (p.extension(entry.path) != ".dart") continue; | |
128 | |
129 numFiles++; | |
130 var relative = p.relative(entry.path, from: sourceDir); | |
131 sources.add(relative); | |
132 | |
133 var sourceFile = entry as File; | |
134 var destPath = p.join(generatedDir, relative); | |
135 var destFile = new File(destPath); | |
136 if (force || | |
137 currentCommit != lastCommit || | |
138 !destFile.existsSync() || | |
139 entry.lastModifiedSync().isAfter(destFile.lastModifiedSync())) { | |
140 _compile(sourceFile.path, sourceFile.readAsStringSync(), destPath); | |
141 numCompiled++; | |
142 if (verbose) print("Compiled $relative"); | |
143 } | |
144 } | |
145 | |
146 // Delete any previously compiled files whose source no longer exists. | |
147 for (var entry in new Directory(generatedDir).listSync(recursive: true)) { | |
148 if (p.extension(entry.path) != ".dart") continue; | |
149 | |
150 var relative = p.relative(entry.path, from: generatedDir); | |
151 | |
152 if (!sources.contains(relative)) { | |
153 _deleteFile(entry.path); | |
154 if (verbose) print("Deleted $relative"); | |
155 } | |
156 } | |
157 | |
158 // Update the README. | |
159 if (currentCommit != lastCommit) { | |
160 _writeFile(readmePath, _README.replaceAll("<<COMMIT>>", currentCommit)); | |
161 if (verbose) print("Updated README.md"); | |
162 } | |
163 | |
164 if (numCompiled > 0 && buildDir != null) _generateSnapshot(buildDir); | |
165 | |
166 if (verbose) print("Compiled $numCompiled out of $numFiles files"); | |
167 | |
168 if (hadFailure) exit(1); | |
169 } | |
170 | |
171 String _getCurrentCommit() { | |
172 var command = "git"; | |
173 var args = ["rev-parse", "HEAD"]; | |
174 | |
175 // Spawning a process on Windows will not look for the executable in the | |
176 // system path so spawn git through a shell to find it. | |
177 if (Platform.operatingSystem == "windows") { | |
178 command = "cmd"; | |
179 args = ["/c", "git"]..addAll(args); | |
180 } | |
181 | |
182 var result = Process.runSync( | |
183 command, | |
184 args, | |
185 workingDirectory: p.join(sourceDir, "../../../../third_party/pkg/async_awa
it")); | |
186 if (result.exitCode != 0) { | |
187 stderr.writeln("Could not get Git revision of async_await compiler."); | |
188 exit(1); | |
189 } | |
190 | |
191 return result.stdout.trim(); | |
192 } | |
193 | |
194 void _compile(String sourcePath, String source, String destPath) { | |
195 var destDir = new Directory(p.dirname(destPath)); | |
196 destDir.createSync(recursive: true); | |
197 | |
198 source = _translateAsyncAwait(sourcePath, source); | |
199 if (source != null) source = _fixDart2jsImports(sourcePath, source, destPath); | |
200 | |
201 if (source == null) { | |
202 // If the async compile fails, delete the file so that we don't try to | |
203 // run the stale previous output and so that we try to recompile it later. | |
204 _deleteFile(destPath); | |
205 } else { | |
206 _writeFile(destPath, source); | |
207 } | |
208 } | |
209 | |
210 /// Runs the async/await compiler on [source]. | |
211 /// | |
212 /// Returns the translated Dart code or `null` if the compiler failed. | |
213 String _translateAsyncAwait(String sourcePath, String source) { | |
214 if (p.isWithin(p.join(sourceDir, "asset"), sourcePath)) { | |
215 // Don't run the async compiler on the special "asset" source files. These | |
216 // have preprocessor comments that get discarded by the compiler. | |
217 return source; | |
218 } | |
219 | |
220 try { | |
221 source = async_await.compile(source); | |
222 | |
223 // Reformat the result since the compiler ditches all whitespace. | |
224 // TODO(rnystrom): Remove when this is fixed: | |
225 // https://github.com/dart-lang/async_await/issues/12 | |
226 var result = new CodeFormatter().format(CodeKind.COMPILATION_UNIT, source); | |
227 return result.source; | |
228 } catch (ex) { | |
229 stderr.writeln("Async compile failed on $sourcePath:\n$ex"); | |
230 hadFailure = true; | |
231 return null; | |
232 } | |
233 } | |
234 | |
235 /// Fix relative imports to dart2js libraries. | |
236 /// | |
237 /// Pub imports dart2js using relative imports that reach outside of pub's | |
238 /// source tree. Since the build directory is in a different location, we need | |
239 /// to fix those to be valid relative imports from the build directory. | |
240 String _fixDart2jsImports(String sourcePath, String source, String destPath) { | |
241 var compilerDir = p.url.join(sourceUrl, "../compiler"); | |
242 var relative = | |
243 p.url.relative(compilerDir, from: p.url.dirname(p.toUri(destPath).toString
())); | |
244 return source.replaceAll(_compilerPattern, "import '$relative"); | |
245 } | |
246 | |
247 /// Regenerate the pub snapshot from the async/await-compiled output. We do | |
248 /// this here since the tests need it and it's faster than doing a full SDK | |
249 /// build. | |
250 void _generateSnapshot(String buildDir) { | |
251 buildDir = p.normalize(buildDir); | |
252 new Directory(buildDir).createSync(recursive: true); | |
253 | |
254 var entrypoint = p.join(generatedDir, 'bin/pub.dart'); | |
255 var packageRoot = p.join(buildDir, 'packages'); | |
256 var snapshot = p.join(buildDir, 'dart-sdk/bin/snapshots/pub.dart.snapshot'); | |
257 | |
258 var result = Process.runSync( | |
259 Platform.executable, | |
260 ["--package-root=$packageRoot", "--snapshot=$snapshot", entrypoint]); | |
261 | |
262 if (result.exitCode != 0) { | |
263 stderr.writeln("Failed to generate snapshot:"); | |
264 if (result.stderr.trim().isNotEmpty) stderr.writeln(result.stderr); | |
265 if (result.stdout.trim().isNotEmpty) stderr.writeln(result.stdout); | |
266 exit(result.exitCode); | |
267 } | |
268 | |
269 if (verbose) print("Created pub snapshot"); | |
270 } | |
271 | |
272 /// Deletes the file at [path], ignoring any IO errors that occur. | |
273 /// | |
274 /// This swallows errors to accommodate multiple compilers running concurrently. | |
275 /// Since they will produce the same output anyway, a failure of one is fine. | |
276 void _deleteFile(String path) { | |
277 try { | |
278 new File(path).deleteSync(); | |
279 } on IOException catch (ex) { | |
280 // Do nothing. | |
281 } | |
282 } | |
283 | |
284 /// Writes [contents] to [path], ignoring any IO errors that occur. | |
285 /// | |
286 /// This swallows errors to accommodate multiple compilers running concurrently. | |
287 /// Since they will produce the same output anyway, a failure of one is fine. | |
288 void _writeFile(String path, String contents) { | |
289 try { | |
290 new File(path).writeAsStringSync(contents); | |
291 } on IOException catch (ex) { | |
292 // Do nothing. | |
293 } | |
294 } | |
OLD | NEW |