OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE.md file. |
4 | 4 |
5 library source_file_provider; | 5 import 'dart:async' show |
| 6 Future; |
6 | 7 |
7 import 'dart:async'; | 8 import 'dart:io' show |
8 import 'dart:convert'; | 9 File, |
9 import 'dart:io'; | 10 RandomAccessFile; |
10 import 'dart:math' as math; | |
11 import 'dart:typed_data'; | |
12 | 11 |
13 import '../compiler.dart' as api show Diagnostic; | 12 import 'dart:typed_data' show |
14 import '../compiler_new.dart' as api; | 13 Uint8List; |
15 import '../compiler_new.dart'; | |
16 import 'colors.dart' as colors; | |
17 import 'dart2js.dart' show AbortLeg; | |
18 import 'filenames.dart'; | |
19 import 'io/source_file.dart'; | |
20 import 'util/uri_extras.dart'; | |
21 | 14 |
22 abstract class SourceFileProvider implements CompilerInput { | 15 List<int> readBytesFromFileSync(Uri uri) { |
23 bool isWindows = (Platform.operatingSystem == 'windows'); | 16 RandomAccessFile file = new File.fromUri(uri).openSync(); |
24 Uri cwd = currentDirectory; | 17 Uint8List list; |
25 Map<Uri, SourceFile> sourceFiles = <Uri, SourceFile>{}; | 18 try { |
26 int dartCharactersRead = 0; | 19 int length = file.lengthSync(); |
27 | 20 // +1 to have a 0 terminated list, see [Scanner]. |
28 Future<String> readStringFromUri(Uri resourceUri) { | 21 list = new Uint8List(length + 1); |
29 return readUtf8BytesFromUri(resourceUri).then(UTF8.decode); | 22 file.readIntoSync(list, 0, length); |
| 23 } finally { |
| 24 file.closeSync(); |
30 } | 25 } |
31 | 26 return list; |
32 Future<List<int>> readUtf8BytesFromUri(Uri resourceUri) { | |
33 if (resourceUri.scheme == 'file') { | |
34 return _readFromFile(resourceUri); | |
35 } else if (resourceUri.scheme == 'http' || resourceUri.scheme == 'https') { | |
36 return _readFromHttp(resourceUri); | |
37 } else { | |
38 throw new ArgumentError("Unknown scheme in uri '$resourceUri'"); | |
39 } | |
40 } | |
41 | |
42 Future<List<int>> _readFromFile(Uri resourceUri) { | |
43 assert(resourceUri.scheme == 'file'); | |
44 List<int> source; | |
45 try { | |
46 source = readAll(resourceUri.toFilePath()); | |
47 } on FileSystemException catch (ex) { | |
48 String message = ex.osError?.message; | |
49 String detail = message != null ? ' ($message)' : ''; | |
50 return new Future.error( | |
51 "Error reading '${relativizeUri(resourceUri)}' $detail"); | |
52 } | |
53 dartCharactersRead += source.length; | |
54 sourceFiles[resourceUri] = new CachingUtf8BytesSourceFile( | |
55 resourceUri, relativizeUri(resourceUri), source); | |
56 return new Future.value(source); | |
57 } | |
58 | |
59 Future<List<int>> _readFromHttp(Uri resourceUri) { | |
60 assert(resourceUri.scheme == 'http'); | |
61 HttpClient client = new HttpClient(); | |
62 return client | |
63 .getUrl(resourceUri) | |
64 .then((HttpClientRequest request) => request.close()) | |
65 .then((HttpClientResponse response) { | |
66 if (response.statusCode != HttpStatus.OK) { | |
67 String msg = 'Failure getting $resourceUri: ' | |
68 '${response.statusCode} ${response.reasonPhrase}'; | |
69 throw msg; | |
70 } | |
71 return response.toList(); | |
72 }).then((List<List<int>> splitContent) { | |
73 int totalLength = splitContent.fold(0, (int old, List list) { | |
74 return old + list.length; | |
75 }); | |
76 Uint8List result = new Uint8List(totalLength); | |
77 int offset = 0; | |
78 for (List<int> contentPart in splitContent) { | |
79 result.setRange(offset, offset + contentPart.length, contentPart); | |
80 offset += contentPart.length; | |
81 } | |
82 dartCharactersRead += totalLength; | |
83 sourceFiles[resourceUri] = new CachingUtf8BytesSourceFile( | |
84 resourceUri, resourceUri.toString(), result); | |
85 return result; | |
86 }); | |
87 } | |
88 | |
89 // TODO(johnniwinther): Remove this when no longer needed for the old compiler | |
90 // API. | |
91 Future/*<List<int> | String>*/ call(Uri resourceUri) => throw "unimplemented"; | |
92 | |
93 relativizeUri(Uri uri) => relativize(cwd, uri, isWindows); | |
94 | |
95 SourceFile getSourceFile(Uri resourceUri) { | |
96 return sourceFiles[resourceUri]; | |
97 } | |
98 } | 27 } |
99 | 28 |
100 List<int> readAll(String filename) { | 29 Future<List<int>> readBytesFromFile(Uri uri) async { |
101 var file = (new File(filename)).openSync(); | 30 RandomAccessFile file = await new File.fromUri(uri).open(); |
102 var length = file.lengthSync(); | 31 Uint8List list; |
103 // +1 to have a 0 terminated list, see [Scanner]. | 32 try { |
104 var buffer = new Uint8List(length + 1); | 33 int length = await file.length(); |
105 file.readIntoSync(buffer, 0, length); | 34 // +1 to have a 0 terminated list, see [Scanner]. |
106 file.closeSync(); | 35 list = new Uint8List(length + 1); |
107 return buffer; | 36 int read = await file.readInto(list); |
| 37 if (read != length) { |
| 38 throw "Error reading file: ${uri}"; |
| 39 } |
| 40 } finally { |
| 41 await file.close(); |
| 42 } |
| 43 return list; |
108 } | 44 } |
109 | |
110 class CompilerSourceFileProvider extends SourceFileProvider { | |
111 // TODO(johnniwinther): Remove this when no longer needed for the old compiler | |
112 // API. | |
113 Future<List<int>> call(Uri resourceUri) => readFromUri(resourceUri); | |
114 | |
115 @override | |
116 Future readFromUri(Uri uri) => readUtf8BytesFromUri(uri); | |
117 } | |
118 | |
119 class FormattingDiagnosticHandler implements CompilerDiagnostics { | |
120 final SourceFileProvider provider; | |
121 bool showWarnings = true; | |
122 bool showHints = true; | |
123 bool verbose = false; | |
124 bool isAborting = false; | |
125 bool enableColors = false; | |
126 bool throwOnError = false; | |
127 int throwOnErrorCount = 0; | |
128 api.Diagnostic lastKind = null; | |
129 int fatalCount = 0; | |
130 | |
131 final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal; | |
132 final int INFO = | |
133 api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal; | |
134 | |
135 FormattingDiagnosticHandler([SourceFileProvider provider]) | |
136 : this.provider = | |
137 (provider == null) ? new CompilerSourceFileProvider() : provider; | |
138 | |
139 void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) { | |
140 if (!verbose && kind == api.Diagnostic.VERBOSE_INFO) return; | |
141 if (enableColors) { | |
142 print('${colors.green("Info:")} $message'); | |
143 } else { | |
144 print('Info: $message'); | |
145 } | |
146 } | |
147 | |
148 /// Adds [kind] specific prefix to [message]. | |
149 String prefixMessage(String message, api.Diagnostic kind) { | |
150 switch (kind) { | |
151 case api.Diagnostic.ERROR: | |
152 return 'Error: $message'; | |
153 case api.Diagnostic.WARNING: | |
154 return 'Warning: $message'; | |
155 case api.Diagnostic.HINT: | |
156 return 'Hint: $message'; | |
157 case api.Diagnostic.CRASH: | |
158 return 'Internal Error: $message'; | |
159 case api.Diagnostic.INFO: | |
160 case api.Diagnostic.VERBOSE_INFO: | |
161 return 'Info: $message'; | |
162 } | |
163 throw 'Unexpected diagnostic kind: $kind (${kind.ordinal})'; | |
164 } | |
165 | |
166 @override | |
167 void report(var code, Uri uri, int begin, int end, String message, | |
168 api.Diagnostic kind) { | |
169 if (isAborting) return; | |
170 isAborting = (kind == api.Diagnostic.CRASH); | |
171 | |
172 bool fatal = (kind.ordinal & FATAL) != 0; | |
173 bool isInfo = (kind.ordinal & INFO) != 0; | |
174 if (isInfo && uri == null && kind != api.Diagnostic.INFO) { | |
175 info(message, kind); | |
176 return; | |
177 } | |
178 | |
179 message = prefixMessage(message, kind); | |
180 | |
181 // [lastKind] records the previous non-INFO kind we saw. | |
182 // This is used to suppress info about a warning when warnings are | |
183 // suppressed, and similar for hints. | |
184 if (kind != api.Diagnostic.INFO) { | |
185 lastKind = kind; | |
186 } | |
187 var color; | |
188 if (kind == api.Diagnostic.ERROR) { | |
189 color = colors.red; | |
190 } else if (kind == api.Diagnostic.WARNING) { | |
191 if (!showWarnings) return; | |
192 color = colors.magenta; | |
193 } else if (kind == api.Diagnostic.HINT) { | |
194 if (!showHints) return; | |
195 color = colors.cyan; | |
196 } else if (kind == api.Diagnostic.CRASH) { | |
197 color = colors.red; | |
198 } else if (kind == api.Diagnostic.INFO) { | |
199 if (lastKind == api.Diagnostic.WARNING && !showWarnings) return; | |
200 if (lastKind == api.Diagnostic.HINT && !showHints) return; | |
201 color = colors.green; | |
202 } else { | |
203 throw 'Unknown kind: $kind (${kind.ordinal})'; | |
204 } | |
205 if (!enableColors) { | |
206 color = (x) => x; | |
207 } | |
208 if (uri == null) { | |
209 print('${color(message)}'); | |
210 } else { | |
211 SourceFile file = provider.sourceFiles[uri]; | |
212 if (file != null) { | |
213 print(file.getLocationMessage(color(message), begin, end, | |
214 colorize: color)); | |
215 } else { | |
216 String position = end - begin > 0 ? '@$begin+${end - begin}' : ''; | |
217 print('${provider.relativizeUri(uri)}$position:\n' | |
218 '${color(message)}'); | |
219 } | |
220 } | |
221 if (fatal && ++fatalCount >= throwOnErrorCount && throwOnError) { | |
222 isAborting = true; | |
223 throw new AbortLeg(message); | |
224 } | |
225 } | |
226 | |
227 // TODO(johnniwinther): Remove this when no longer needed for the old compiler | |
228 // API. | |
229 void call(Uri uri, int begin, int end, String message, api.Diagnostic kind) { | |
230 return report(null, uri, begin, end, message, kind); | |
231 } | |
232 } | |
233 | |
234 typedef void MessageCallback(String message); | |
235 | |
236 class RandomAccessFileOutputProvider implements CompilerOutput { | |
237 final Uri out; | |
238 final Uri sourceMapOut; | |
239 final Uri resolutionOutput; | |
240 final MessageCallback onInfo; | |
241 final MessageCallback onFailure; | |
242 | |
243 int totalCharactersWritten = 0; | |
244 List<String> allOutputFiles = new List<String>(); | |
245 | |
246 RandomAccessFileOutputProvider(this.out, this.sourceMapOut, | |
247 {this.onInfo, this.onFailure, this.resolutionOutput}); | |
248 | |
249 static Uri computePrecompiledUri(Uri out) { | |
250 String extension = 'precompiled.js'; | |
251 String outPath = out.path; | |
252 if (outPath.endsWith('.js')) { | |
253 outPath = outPath.substring(0, outPath.length - 3); | |
254 return out.resolve('$outPath.$extension'); | |
255 } else { | |
256 return out.resolve(extension); | |
257 } | |
258 } | |
259 | |
260 EventSink<String> call(String name, String extension) { | |
261 return createEventSink(name, extension); | |
262 } | |
263 | |
264 EventSink<String> createEventSink(String name, String extension) { | |
265 Uri uri; | |
266 bool isPrimaryOutput = false; | |
267 // TODO (johnniwinther, sigurdm): Make a better interface for | |
268 // output-providers. | |
269 if (extension == "deferred_map") { | |
270 uri = out.resolve(name); | |
271 } else if (name == '') { | |
272 if (extension == 'js' || extension == 'dart') { | |
273 isPrimaryOutput = true; | |
274 uri = out; | |
275 } else if (extension == 'precompiled.js') { | |
276 uri = computePrecompiledUri(out); | |
277 onInfo("File ($uri) is compatible with header" | |
278 " \"Content-Security-Policy: script-src 'self'\""); | |
279 } else if (extension == 'js.map' || extension == 'dart.map') { | |
280 uri = sourceMapOut; | |
281 } else if (extension == 'info.json') { | |
282 String outName = out.path.substring(out.path.lastIndexOf('/') + 1); | |
283 uri = out.resolve('$outName.$extension'); | |
284 } else if (extension == 'data') { | |
285 if (resolutionOutput == null) { | |
286 onFailure('Serialization target unspecified.'); | |
287 } | |
288 uri = resolutionOutput; | |
289 } else { | |
290 onFailure('Unknown extension: $extension'); | |
291 } | |
292 } else { | |
293 uri = out.resolve('$name.$extension'); | |
294 } | |
295 | |
296 if (uri.scheme != 'file') { | |
297 onFailure('Unhandled scheme ${uri.scheme} in $uri.'); | |
298 } | |
299 | |
300 RandomAccessFile output; | |
301 try { | |
302 output = new File(uri.toFilePath()).openSync(mode: FileMode.WRITE); | |
303 } on FileSystemException catch (e) { | |
304 onFailure('$e'); | |
305 } | |
306 | |
307 allOutputFiles.add(relativize(currentDirectory, uri, Platform.isWindows)); | |
308 | |
309 int charactersWritten = 0; | |
310 | |
311 writeStringSync(String data) { | |
312 // Write the data in chunks of 8kb, otherwise we risk running OOM. | |
313 int chunkSize = 8 * 1024; | |
314 | |
315 int offset = 0; | |
316 while (offset < data.length) { | |
317 output.writeStringSync( | |
318 data.substring(offset, math.min(offset + chunkSize, data.length))); | |
319 offset += chunkSize; | |
320 } | |
321 charactersWritten += data.length; | |
322 } | |
323 | |
324 onDone() { | |
325 output.closeSync(); | |
326 if (isPrimaryOutput) { | |
327 totalCharactersWritten += charactersWritten; | |
328 } | |
329 } | |
330 | |
331 return new _EventSinkWrapper(writeStringSync, onDone); | |
332 } | |
333 } | |
334 | |
335 class _EventSinkWrapper extends EventSink<String> { | |
336 var onAdd, onClose; | |
337 | |
338 _EventSinkWrapper(this.onAdd, this.onClose); | |
339 | |
340 void add(String data) => onAdd(data); | |
341 | |
342 void addError(error, [StackTrace stackTrace]) => throw error; | |
343 | |
344 void close() => onClose(); | |
345 } | |
346 | |
347 /// Adapter to integrate dart2js in bazel. | |
348 /// | |
349 /// To handle bazel's special layout: | |
350 /// | |
351 /// * We specify a .packages configuration file that expands packages to their | |
352 /// corresponding bazel location. This way there is no need to create a pub | |
353 /// cache prior to invoking dart2js. | |
354 /// | |
355 /// * We provide an implicit mapping that can make all urls relative to the | |
356 /// bazel root. | |
357 /// To the compiler, URIs look like: | |
358 /// file:///bazel-root/a/b/c.dart | |
359 /// | |
360 /// even though in the file system the file is located at: | |
361 /// file:///path/to/the/actual/bazel/root/a/b/c.dart | |
362 /// | |
363 /// This mapping serves two purposes: | |
364 /// - It makes compiler results independent of the machine layout, which | |
365 /// enables us to share results across bazel runs and across machines. | |
366 /// | |
367 /// - It hides the distinction between generated and source files. That way | |
368 /// we can use the standard package-resolution mechanism and ignore the | |
369 /// internals of how files are organized within bazel. | |
370 /// | |
371 /// When invoking the compiler, bazel will use `package:` and | |
372 /// `file:///bazel-root/` URIs to specify entrypoints. | |
373 /// | |
374 /// The mapping is specified using search paths relative to the current | |
375 /// directory. When this provider looks up a file, the bazel-root folder is | |
376 /// replaced by the first directory in the search path containing the file, if | |
377 /// any. For example, given the search path ".,bazel-bin/", and a URL | |
378 /// of the form `file:///bazel-root/a/b.dart`, this provider will check if the | |
379 /// file exists under "./a/b.dart", then check under "bazel-bin/a/b.dart". If | |
380 /// none of the paths matches, it will attempt to load the file from | |
381 /// `/bazel-root/a/b.dart` which will likely fail. | |
382 class BazelInputProvider extends SourceFileProvider { | |
383 final List<Uri> dirs; | |
384 | |
385 BazelInputProvider(List<String> searchPaths) | |
386 : dirs = searchPaths.map(_resolve).toList(); | |
387 | |
388 static _resolve(String path) => currentDirectory.resolve(path); | |
389 | |
390 @override | |
391 Future readFromUri(Uri uri) async { | |
392 var resolvedUri = uri; | |
393 var path = uri.path; | |
394 if (path.startsWith('/bazel-root')) { | |
395 path = path.substring('/bazel-root/'.length); | |
396 for (var dir in dirs) { | |
397 var file = dir.resolve(path); | |
398 if (await new File.fromUri(file).exists()) { | |
399 resolvedUri = file; | |
400 break; | |
401 } | |
402 } | |
403 } | |
404 var result = await readUtf8BytesFromUri(resolvedUri); | |
405 sourceFiles[uri] = sourceFiles[resolvedUri]; | |
406 return result; | |
407 } | |
408 } | |
OLD | NEW |