Index: pkg/dart_scanner/lib/io.dart |
diff --git a/pkg/dart_scanner/lib/io.dart b/pkg/dart_scanner/lib/io.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..94052cb6bc0f8448f4a12af6715bf4b610b448ac |
--- /dev/null |
+++ b/pkg/dart_scanner/lib/io.dart |
@@ -0,0 +1,408 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library source_file_provider; |
+ |
+import 'dart:async'; |
+import 'dart:convert'; |
+import 'dart:io'; |
+import 'dart:math' as math; |
+import 'dart:typed_data'; |
+ |
+import '../compiler.dart' as api show Diagnostic; |
+import '../compiler_new.dart' as api; |
+import '../compiler_new.dart'; |
+import 'colors.dart' as colors; |
+import 'dart2js.dart' show AbortLeg; |
+import 'filenames.dart'; |
+import 'io/source_file.dart'; |
+import 'util/uri_extras.dart'; |
+ |
+abstract class SourceFileProvider implements CompilerInput { |
+ bool isWindows = (Platform.operatingSystem == 'windows'); |
+ Uri cwd = currentDirectory; |
+ Map<Uri, SourceFile> sourceFiles = <Uri, SourceFile>{}; |
+ int dartCharactersRead = 0; |
+ |
+ Future<String> readStringFromUri(Uri resourceUri) { |
+ return readUtf8BytesFromUri(resourceUri).then(UTF8.decode); |
+ } |
+ |
+ Future<List<int>> readUtf8BytesFromUri(Uri resourceUri) { |
+ if (resourceUri.scheme == 'file') { |
+ return _readFromFile(resourceUri); |
+ } else if (resourceUri.scheme == 'http' || resourceUri.scheme == 'https') { |
+ return _readFromHttp(resourceUri); |
+ } else { |
+ throw new ArgumentError("Unknown scheme in uri '$resourceUri'"); |
+ } |
+ } |
+ |
+ Future<List<int>> _readFromFile(Uri resourceUri) { |
+ assert(resourceUri.scheme == 'file'); |
+ List<int> source; |
+ try { |
+ source = readAll(resourceUri.toFilePath()); |
+ } on FileSystemException catch (ex) { |
+ String message = ex.osError?.message; |
+ String detail = message != null ? ' ($message)' : ''; |
+ return new Future.error( |
+ "Error reading '${relativizeUri(resourceUri)}' $detail"); |
+ } |
+ dartCharactersRead += source.length; |
+ sourceFiles[resourceUri] = new CachingUtf8BytesSourceFile( |
+ resourceUri, relativizeUri(resourceUri), source); |
+ return new Future.value(source); |
+ } |
+ |
+ Future<List<int>> _readFromHttp(Uri resourceUri) { |
+ assert(resourceUri.scheme == 'http'); |
+ HttpClient client = new HttpClient(); |
+ return client |
+ .getUrl(resourceUri) |
+ .then((HttpClientRequest request) => request.close()) |
+ .then((HttpClientResponse response) { |
+ if (response.statusCode != HttpStatus.OK) { |
+ String msg = 'Failure getting $resourceUri: ' |
+ '${response.statusCode} ${response.reasonPhrase}'; |
+ throw msg; |
+ } |
+ return response.toList(); |
+ }).then((List<List<int>> splitContent) { |
+ int totalLength = splitContent.fold(0, (int old, List list) { |
+ return old + list.length; |
+ }); |
+ Uint8List result = new Uint8List(totalLength); |
+ int offset = 0; |
+ for (List<int> contentPart in splitContent) { |
+ result.setRange(offset, offset + contentPart.length, contentPart); |
+ offset += contentPart.length; |
+ } |
+ dartCharactersRead += totalLength; |
+ sourceFiles[resourceUri] = new CachingUtf8BytesSourceFile( |
+ resourceUri, resourceUri.toString(), result); |
+ return result; |
+ }); |
+ } |
+ |
+ // TODO(johnniwinther): Remove this when no longer needed for the old compiler |
+ // API. |
+ Future/*<List<int> | String>*/ call(Uri resourceUri) => throw "unimplemented"; |
+ |
+ relativizeUri(Uri uri) => relativize(cwd, uri, isWindows); |
+ |
+ SourceFile getSourceFile(Uri resourceUri) { |
+ return sourceFiles[resourceUri]; |
+ } |
+} |
+ |
+List<int> readAll(String filename) { |
+ var file = (new File(filename)).openSync(); |
+ var length = file.lengthSync(); |
+ // +1 to have a 0 terminated list, see [Scanner]. |
+ var buffer = new Uint8List(length + 1); |
+ file.readIntoSync(buffer, 0, length); |
+ file.closeSync(); |
+ return buffer; |
+} |
+ |
+class CompilerSourceFileProvider extends SourceFileProvider { |
+ // TODO(johnniwinther): Remove this when no longer needed for the old compiler |
+ // API. |
+ Future<List<int>> call(Uri resourceUri) => readFromUri(resourceUri); |
+ |
+ @override |
+ Future readFromUri(Uri uri) => readUtf8BytesFromUri(uri); |
+} |
+ |
+class FormattingDiagnosticHandler implements CompilerDiagnostics { |
+ final SourceFileProvider provider; |
+ bool showWarnings = true; |
+ bool showHints = true; |
+ bool verbose = false; |
+ bool isAborting = false; |
+ bool enableColors = false; |
+ bool throwOnError = false; |
+ int throwOnErrorCount = 0; |
+ api.Diagnostic lastKind = null; |
+ int fatalCount = 0; |
+ |
+ final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal; |
+ final int INFO = |
+ api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal; |
+ |
+ FormattingDiagnosticHandler([SourceFileProvider provider]) |
+ : this.provider = |
+ (provider == null) ? new CompilerSourceFileProvider() : provider; |
+ |
+ void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) { |
+ if (!verbose && kind == api.Diagnostic.VERBOSE_INFO) return; |
+ if (enableColors) { |
+ print('${colors.green("Info:")} $message'); |
+ } else { |
+ print('Info: $message'); |
+ } |
+ } |
+ |
+ /// Adds [kind] specific prefix to [message]. |
+ String prefixMessage(String message, api.Diagnostic kind) { |
+ switch (kind) { |
+ case api.Diagnostic.ERROR: |
+ return 'Error: $message'; |
+ case api.Diagnostic.WARNING: |
+ return 'Warning: $message'; |
+ case api.Diagnostic.HINT: |
+ return 'Hint: $message'; |
+ case api.Diagnostic.CRASH: |
+ return 'Internal Error: $message'; |
+ case api.Diagnostic.INFO: |
+ case api.Diagnostic.VERBOSE_INFO: |
+ return 'Info: $message'; |
+ } |
+ throw 'Unexpected diagnostic kind: $kind (${kind.ordinal})'; |
+ } |
+ |
+ @override |
+ void report(var code, Uri uri, int begin, int end, String message, |
+ api.Diagnostic kind) { |
+ if (isAborting) return; |
+ isAborting = (kind == api.Diagnostic.CRASH); |
+ |
+ bool fatal = (kind.ordinal & FATAL) != 0; |
+ bool isInfo = (kind.ordinal & INFO) != 0; |
+ if (isInfo && uri == null && kind != api.Diagnostic.INFO) { |
+ info(message, kind); |
+ return; |
+ } |
+ |
+ message = prefixMessage(message, kind); |
+ |
+ // [lastKind] records the previous non-INFO kind we saw. |
+ // This is used to suppress info about a warning when warnings are |
+ // suppressed, and similar for hints. |
+ if (kind != api.Diagnostic.INFO) { |
+ lastKind = kind; |
+ } |
+ var color; |
+ if (kind == api.Diagnostic.ERROR) { |
+ color = colors.red; |
+ } else if (kind == api.Diagnostic.WARNING) { |
+ if (!showWarnings) return; |
+ color = colors.magenta; |
+ } else if (kind == api.Diagnostic.HINT) { |
+ if (!showHints) return; |
+ color = colors.cyan; |
+ } else if (kind == api.Diagnostic.CRASH) { |
+ color = colors.red; |
+ } else if (kind == api.Diagnostic.INFO) { |
+ if (lastKind == api.Diagnostic.WARNING && !showWarnings) return; |
+ if (lastKind == api.Diagnostic.HINT && !showHints) return; |
+ color = colors.green; |
+ } else { |
+ throw 'Unknown kind: $kind (${kind.ordinal})'; |
+ } |
+ if (!enableColors) { |
+ color = (x) => x; |
+ } |
+ if (uri == null) { |
+ print('${color(message)}'); |
+ } else { |
+ SourceFile file = provider.sourceFiles[uri]; |
+ if (file != null) { |
+ print(file.getLocationMessage(color(message), begin, end, |
+ colorize: color)); |
+ } else { |
+ String position = end - begin > 0 ? '@$begin+${end - begin}' : ''; |
+ print('${provider.relativizeUri(uri)}$position:\n' |
+ '${color(message)}'); |
+ } |
+ } |
+ if (fatal && ++fatalCount >= throwOnErrorCount && throwOnError) { |
+ isAborting = true; |
+ throw new AbortLeg(message); |
+ } |
+ } |
+ |
+ // TODO(johnniwinther): Remove this when no longer needed for the old compiler |
+ // API. |
+ void call(Uri uri, int begin, int end, String message, api.Diagnostic kind) { |
+ return report(null, uri, begin, end, message, kind); |
+ } |
+} |
+ |
+typedef void MessageCallback(String message); |
+ |
+class RandomAccessFileOutputProvider implements CompilerOutput { |
+ final Uri out; |
+ final Uri sourceMapOut; |
+ final Uri resolutionOutput; |
+ final MessageCallback onInfo; |
+ final MessageCallback onFailure; |
+ |
+ int totalCharactersWritten = 0; |
+ List<String> allOutputFiles = new List<String>(); |
+ |
+ RandomAccessFileOutputProvider(this.out, this.sourceMapOut, |
+ {this.onInfo, this.onFailure, this.resolutionOutput}); |
+ |
+ static Uri computePrecompiledUri(Uri out) { |
+ String extension = 'precompiled.js'; |
+ String outPath = out.path; |
+ if (outPath.endsWith('.js')) { |
+ outPath = outPath.substring(0, outPath.length - 3); |
+ return out.resolve('$outPath.$extension'); |
+ } else { |
+ return out.resolve(extension); |
+ } |
+ } |
+ |
+ EventSink<String> call(String name, String extension) { |
+ return createEventSink(name, extension); |
+ } |
+ |
+ EventSink<String> createEventSink(String name, String extension) { |
+ Uri uri; |
+ bool isPrimaryOutput = false; |
+ // TODO (johnniwinther, sigurdm): Make a better interface for |
+ // output-providers. |
+ if (extension == "deferred_map") { |
+ uri = out.resolve(name); |
+ } else if (name == '') { |
+ if (extension == 'js' || extension == 'dart') { |
+ isPrimaryOutput = true; |
+ uri = out; |
+ } else if (extension == 'precompiled.js') { |
+ uri = computePrecompiledUri(out); |
+ onInfo("File ($uri) is compatible with header" |
+ " \"Content-Security-Policy: script-src 'self'\""); |
+ } else if (extension == 'js.map' || extension == 'dart.map') { |
+ uri = sourceMapOut; |
+ } else if (extension == 'info.json') { |
+ String outName = out.path.substring(out.path.lastIndexOf('/') + 1); |
+ uri = out.resolve('$outName.$extension'); |
+ } else if (extension == 'data') { |
+ if (resolutionOutput == null) { |
+ onFailure('Serialization target unspecified.'); |
+ } |
+ uri = resolutionOutput; |
+ } else { |
+ onFailure('Unknown extension: $extension'); |
+ } |
+ } else { |
+ uri = out.resolve('$name.$extension'); |
+ } |
+ |
+ if (uri.scheme != 'file') { |
+ onFailure('Unhandled scheme ${uri.scheme} in $uri.'); |
+ } |
+ |
+ RandomAccessFile output; |
+ try { |
+ output = new File(uri.toFilePath()).openSync(mode: FileMode.WRITE); |
+ } on FileSystemException catch (e) { |
+ onFailure('$e'); |
+ } |
+ |
+ allOutputFiles.add(relativize(currentDirectory, uri, Platform.isWindows)); |
+ |
+ int charactersWritten = 0; |
+ |
+ writeStringSync(String data) { |
+ // Write the data in chunks of 8kb, otherwise we risk running OOM. |
+ int chunkSize = 8 * 1024; |
+ |
+ int offset = 0; |
+ while (offset < data.length) { |
+ output.writeStringSync( |
+ data.substring(offset, math.min(offset + chunkSize, data.length))); |
+ offset += chunkSize; |
+ } |
+ charactersWritten += data.length; |
+ } |
+ |
+ onDone() { |
+ output.closeSync(); |
+ if (isPrimaryOutput) { |
+ totalCharactersWritten += charactersWritten; |
+ } |
+ } |
+ |
+ return new _EventSinkWrapper(writeStringSync, onDone); |
+ } |
+} |
+ |
+class _EventSinkWrapper extends EventSink<String> { |
+ var onAdd, onClose; |
+ |
+ _EventSinkWrapper(this.onAdd, this.onClose); |
+ |
+ void add(String data) => onAdd(data); |
+ |
+ void addError(error, [StackTrace stackTrace]) => throw error; |
+ |
+ void close() => onClose(); |
+} |
+ |
+/// Adapter to integrate dart2js in bazel. |
+/// |
+/// To handle bazel's special layout: |
+/// |
+/// * We specify a .packages configuration file that expands packages to their |
+/// corresponding bazel location. This way there is no need to create a pub |
+/// cache prior to invoking dart2js. |
+/// |
+/// * We provide an implicit mapping that can make all urls relative to the |
+/// bazel root. |
+/// To the compiler, URIs look like: |
+/// file:///bazel-root/a/b/c.dart |
+/// |
+/// even though in the file system the file is located at: |
+/// file:///path/to/the/actual/bazel/root/a/b/c.dart |
+/// |
+/// This mapping serves two purposes: |
+/// - It makes compiler results independent of the machine layout, which |
+/// enables us to share results across bazel runs and across machines. |
+/// |
+/// - It hides the distinction between generated and source files. That way |
+/// we can use the standard package-resolution mechanism and ignore the |
+/// internals of how files are organized within bazel. |
+/// |
+/// When invoking the compiler, bazel will use `package:` and |
+/// `file:///bazel-root/` URIs to specify entrypoints. |
+/// |
+/// The mapping is specified using search paths relative to the current |
+/// directory. When this provider looks up a file, the bazel-root folder is |
+/// replaced by the first directory in the search path containing the file, if |
+/// any. For example, given the search path ".,bazel-bin/", and a URL |
+/// of the form `file:///bazel-root/a/b.dart`, this provider will check if the |
+/// file exists under "./a/b.dart", then check under "bazel-bin/a/b.dart". If |
+/// none of the paths matches, it will attempt to load the file from |
+/// `/bazel-root/a/b.dart` which will likely fail. |
+class BazelInputProvider extends SourceFileProvider { |
+ final List<Uri> dirs; |
+ |
+ BazelInputProvider(List<String> searchPaths) |
+ : dirs = searchPaths.map(_resolve).toList(); |
+ |
+ static _resolve(String path) => currentDirectory.resolve(path); |
+ |
+ @override |
+ Future readFromUri(Uri uri) async { |
+ var resolvedUri = uri; |
+ var path = uri.path; |
+ if (path.startsWith('/bazel-root')) { |
+ path = path.substring('/bazel-root/'.length); |
+ for (var dir in dirs) { |
+ var file = dir.resolve(path); |
+ if (await new File.fromUri(file).exists()) { |
+ resolvedUri = file; |
+ break; |
+ } |
+ } |
+ } |
+ var result = await readUtf8BytesFromUri(resolvedUri); |
+ sourceFiles[uri] = sourceFiles[resolvedUri]; |
+ return result; |
+ } |
+} |