OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, 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 library source_file_provider; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:convert'; |
| 9 import 'dart:io'; |
| 10 import 'dart:math' as math; |
| 11 import 'dart:typed_data'; |
| 12 |
| 13 import '../compiler.dart' as api show Diagnostic; |
| 14 import '../compiler_new.dart' as api; |
| 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 |
| 22 abstract class SourceFileProvider implements CompilerInput { |
| 23 bool isWindows = (Platform.operatingSystem == 'windows'); |
| 24 Uri cwd = currentDirectory; |
| 25 Map<Uri, SourceFile> sourceFiles = <Uri, SourceFile>{}; |
| 26 int dartCharactersRead = 0; |
| 27 |
| 28 Future<String> readStringFromUri(Uri resourceUri) { |
| 29 return readUtf8BytesFromUri(resourceUri).then(UTF8.decode); |
| 30 } |
| 31 |
| 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 } |
| 99 |
| 100 List<int> readAll(String filename) { |
| 101 var file = (new File(filename)).openSync(); |
| 102 var length = file.lengthSync(); |
| 103 // +1 to have a 0 terminated list, see [Scanner]. |
| 104 var buffer = new Uint8List(length + 1); |
| 105 file.readIntoSync(buffer, 0, length); |
| 106 file.closeSync(); |
| 107 return buffer; |
| 108 } |
| 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 |