OLD | NEW |
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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 file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 | 6 |
| 7 import 'package:front_end/compilation_error.dart'; |
7 import 'package:front_end/compiler_options.dart'; | 8 import 'package:front_end/compiler_options.dart'; |
8 import 'package:front_end/file_system.dart'; | 9 import 'package:front_end/file_system.dart'; |
| 10 import 'package:front_end/src/base/performace_logger.dart'; |
| 11 import 'package:front_end/src/fasta/ticker.dart'; |
9 import 'package:front_end/src/fasta/translate_uri.dart'; | 12 import 'package:front_end/src/fasta/translate_uri.dart'; |
10 import 'package:front_end/src/base/performace_logger.dart'; | |
11 import 'package:front_end/src/incremental/byte_store.dart'; | 13 import 'package:front_end/src/incremental/byte_store.dart'; |
12 import 'package:front_end/src/simple_error.dart'; | 14 import 'package:kernel/kernel.dart' |
| 15 show Program, loadProgramFromBytes, CanonicalName; |
| 16 import 'package:kernel/target/targets.dart'; |
| 17 import 'package:kernel/target/vm_fasta.dart'; |
13 import 'package:package_config/packages_file.dart' as package_config; | 18 import 'package:package_config/packages_file.dart' as package_config; |
14 import 'package:kernel/kernel.dart' show Program, loadProgramFromBytes; | 19 import 'package:source_span/source_span.dart' show SourceSpan; |
15 | 20 |
16 /// Wrapper around [CompilerOptions] which exposes the options in a form useful | 21 /// All options needed for the front end implementation. |
17 /// to the front end implementation. | 22 /// |
| 23 /// This includes: all of [CompilerOptions] in a form useful to the |
| 24 /// implementation, default values for options that were not provided, |
| 25 /// and information derived from how the compiler was invoked (like the |
| 26 /// entry-points given to the compiler and whether a modular or whole-program |
| 27 /// API was used). |
18 /// | 28 /// |
19 /// The intent is that the front end should immediately wrap any incoming | 29 /// The intent is that the front end should immediately wrap any incoming |
20 /// [CompilerOptions] object in this class before doing further processing, and | 30 /// [CompilerOptions] object in this class before doing further processing, and |
21 /// should thereafter access all options via the wrapper. This ensures that | 31 /// should thereafter access all options via the wrapper. This ensures that |
22 /// options are interpreted in a consistent way and that data derived from | 32 /// options are interpreted in a consistent way and that data derived from |
23 /// options is not unnecessarily recomputed. | 33 /// options is not unnecessarily recomputed. |
24 class ProcessedOptions { | 34 class ProcessedOptions { |
25 /// The raw [CompilerOptions] which this class wraps. | 35 /// The raw [CompilerOptions] which this class wraps. |
26 final CompilerOptions _raw; | 36 final CompilerOptions _raw; |
27 | 37 |
28 /// The package map derived from the options, or `null` if the package map has | 38 /// The package map derived from the options, or `null` if the package map has |
29 /// not been computed yet. | 39 /// not been computed yet. |
30 Map<String, Uri> _packages; | 40 Map<String, Uri> _packages; |
31 | 41 |
32 /// The object that knows how to resolve "package:" and "dart:" URIs, | 42 /// The object that knows how to resolve "package:" and "dart:" URIs, |
33 /// or `null` if it has not been computed yet. | 43 /// or `null` if it has not been computed yet. |
34 TranslateUri _uriTranslator; | 44 TranslateUri _uriTranslator; |
35 | 45 |
36 /// The SDK summary, or `null` if it has not been read yet. | 46 /// The SDK summary, or `null` if it has not been read yet. |
| 47 /// |
| 48 /// A summary, also referred to as "outline" internally, is a [Program] where |
| 49 /// all method bodies are left out. In essence, it contains just API |
| 50 /// signatures and constants. When strong-mode is enabled, the summary already |
| 51 /// includes inferred types. |
37 Program _sdkSummaryProgram; | 52 Program _sdkSummaryProgram; |
38 | 53 |
39 /// The summary for each uri in `options.inputSummaries`. | 54 /// The summary for each uri in `options.inputSummaries`. |
| 55 /// |
| 56 /// A summary, also referred to as "outline" internally, is a [Program] where |
| 57 /// all method bodies are left out. In essence, it contains just API |
| 58 /// signatures and constants. When strong-mode is enabled, the summary already |
| 59 /// includes inferred types. |
40 List<Program> _inputSummariesPrograms; | 60 List<Program> _inputSummariesPrograms; |
41 | 61 |
| 62 /// Other programs that are meant to be linked and compiled with the input |
| 63 /// sources. |
| 64 List<Program> _linkedDependencies; |
| 65 |
42 /// The location of the SDK, or `null` if the location hasn't been determined | 66 /// The location of the SDK, or `null` if the location hasn't been determined |
43 /// yet. | 67 /// yet. |
44 Uri _sdkRoot; | 68 Uri _sdkRoot; |
45 | |
46 Uri get sdkRoot => _sdkRoot ??= _normalizeSdkRoot(); | 69 Uri get sdkRoot => _sdkRoot ??= _normalizeSdkRoot(); |
47 | 70 |
| 71 Uri _sdkSummary; |
| 72 Uri get sdkSummary => _sdkSummary ??= _computeSdkSummaryUri(); |
| 73 |
| 74 Ticker ticker; |
| 75 |
| 76 bool get verbose => _raw.verbose; |
| 77 |
| 78 bool get verify => _raw.verify; |
| 79 |
| 80 bool get debugDump => _raw.debugDump; |
| 81 |
| 82 /// Like [CompilerOptions.chaseDependencies] but with the appropriate default |
| 83 /// value filled in. |
| 84 bool get chaseDependencies => _raw.chaseDependencies ?? !_modularApi; |
| 85 |
| 86 /// Whether the compiler was invoked with a modular API. |
| 87 /// |
| 88 /// Used to determine the default behavior for [chaseDependencies]. |
| 89 final bool _modularApi; |
| 90 |
| 91 /// The entry-points provided to the compiler. |
| 92 final List<Uri> inputs; |
| 93 |
48 /// Initializes a [ProcessedOptions] object wrapping the given [rawOptions]. | 94 /// Initializes a [ProcessedOptions] object wrapping the given [rawOptions]. |
49 ProcessedOptions(CompilerOptions rawOptions) : this._raw = rawOptions; | 95 ProcessedOptions(CompilerOptions rawOptions, |
| 96 [this._modularApi = false, this.inputs = const []]) |
| 97 : this._raw = rawOptions, |
| 98 ticker = new Ticker(isVerbose: rawOptions.verbose); |
50 | 99 |
51 /// The logger to report compilation progress. | 100 /// The logger to report compilation progress. |
52 PerformanceLog get logger { | 101 PerformanceLog get logger { |
53 return _raw.logger; | 102 return _raw.logger; |
54 } | 103 } |
55 | 104 |
56 /// The byte storage to get and put serialized data. | 105 /// The byte storage to get and put serialized data. |
57 ByteStore get byteStore { | 106 ByteStore get byteStore { |
58 return _raw.byteStore; | 107 return _raw.byteStore; |
59 } | 108 } |
60 | 109 |
| 110 // TODO(sigmund): delete. We should use messages with error codes directly |
| 111 // instead. |
| 112 void reportError(String message) { |
| 113 _raw.onError(new _CompilationError(message)); |
| 114 } |
| 115 |
61 /// Runs various validations checks on the input options. For instance, | 116 /// Runs various validations checks on the input options. For instance, |
62 /// if an option is a path to a file, it checks that the file exists. | 117 /// if an option is a path to a file, it checks that the file exists. |
63 Future<bool> validateOptions() async { | 118 Future<bool> validateOptions() async { |
64 var fs = _raw.fileSystem; | 119 for (var source in inputs) { |
65 var root = _raw.sdkRoot; | 120 if (source.scheme == 'file' && |
| 121 !await fileSystem.entityForUri(source).exists()) { |
| 122 reportError("Entry-point file not found: $source"); |
| 123 return false; |
| 124 } |
| 125 } |
66 | 126 |
67 bool _report(String msg) { | 127 if (_raw.sdkRoot != null && |
68 _raw.onError(new SimpleError(msg)); | 128 !await fileSystem.entityForUri(sdkRoot).exists()) { |
| 129 reportError("SDK root directory not found: ${sdkRoot}"); |
69 return false; | 130 return false; |
70 } | 131 } |
71 | 132 |
72 if (root != null && !await fs.entityForUri(root).exists()) { | 133 var summary = sdkSummary; |
73 return _report("SDK root directory not found: ${_raw.sdkRoot}"); | 134 if (summary != null && !await fileSystem.entityForUri(summary).exists()) { |
| 135 reportError("SDK summary not found: ${summary}"); |
| 136 return false; |
74 } | 137 } |
75 | 138 |
76 var summary = _raw.sdkSummary; | 139 if (compileSdk && summary != null) { |
77 if (summary != null && !await fs.entityForUri(summary).exists()) { | 140 reportError( |
78 return _report("SDK summary not found: ${_raw.sdkSummary}"); | 141 "The compileSdk and sdkSummary options are mutually exclusive"); |
| 142 return false; |
79 } | 143 } |
80 | |
81 // TODO(sigmund): add checks for options that are meant to be disjoint (like | |
82 // sdkRoot and sdkSummary). | |
83 return true; | 144 return true; |
84 } | 145 } |
85 | 146 |
86 /// Determine whether to generate code for the SDK when compiling a | 147 /// Determine whether to generate code for the SDK when compiling a |
87 /// whole-program. | 148 /// whole-program. |
88 bool get compileSdk => _raw.compileSdk; | 149 bool get compileSdk => _raw.compileSdk; |
89 | 150 |
| 151 FileSystem _fileSystem; |
| 152 |
90 /// Get the [FileSystem] which should be used by the front end to access | 153 /// Get the [FileSystem] which should be used by the front end to access |
91 /// files. | 154 /// files. |
92 /// | 155 /// |
93 /// If the client supplied roots using [CompilerOptions.multiRoots], the | 156 /// If the client supplied roots using [CompilerOptions.multiRoots], the |
94 /// returned [FileSystem] will automatically perform the appropriate mapping. | 157 /// returned [FileSystem] will automatically perform the appropriate mapping. |
95 FileSystem get fileSystem { | 158 FileSystem get fileSystem => _fileSystem ??= _createFileSystem(); |
96 // TODO(paulberry): support multiRoots. | |
97 assert(_raw.multiRoots.isEmpty); | |
98 return _raw.fileSystem; | |
99 } | |
100 | 159 |
101 /// Whether to interpret Dart sources in strong-mode. | 160 /// Whether to interpret Dart sources in strong-mode. |
102 bool get strongMode => _raw.strongMode; | 161 bool get strongMode => _raw.strongMode; |
103 | 162 |
104 /// Get an outline program that summarizes the SDK. | 163 Target _target; |
105 Future<Program> get sdkSummaryProgram async { | 164 Target get target => _target ??= |
| 165 _raw.target ?? new VmFastaTarget(new TargetFlags(strongMode: strongMode)); |
| 166 |
| 167 /// Get an outline program that summarizes the SDK, if any. |
| 168 // TODO(sigmund): move, this doesn't feel like an "option". |
| 169 Future<Program> loadSdkSummary(CanonicalName nameRoot) async { |
106 if (_sdkSummaryProgram == null) { | 170 if (_sdkSummaryProgram == null) { |
107 if (_raw.sdkSummary == null) return null; | 171 if (sdkSummary == null) return null; |
108 _sdkSummaryProgram = await _loadProgram(_raw.sdkSummary); | 172 var bytes = await fileSystem.entityForUri(sdkSummary).readAsBytes(); |
| 173 _sdkSummaryProgram = loadProgram(bytes, nameRoot); |
109 } | 174 } |
110 return _sdkSummaryProgram; | 175 return _sdkSummaryProgram; |
111 } | 176 } |
112 | 177 |
113 /// Get the summary programs for each of the underlying `inputSummaries` | 178 /// Get the summary programs for each of the underlying `inputSummaries` |
114 /// provided via [CompilerOptions]. | 179 /// provided via [CompilerOptions]. |
115 Future<List<Program>> get inputSummariesPrograms async { | 180 // TODO(sigmund): move, this doesn't feel like an "option". |
| 181 Future<List<Program>> loadInputSummaries(CanonicalName nameRoot) async { |
116 if (_inputSummariesPrograms == null) { | 182 if (_inputSummariesPrograms == null) { |
117 var uris = _raw.inputSummaries; | 183 var uris = _raw.inputSummaries; |
118 if (uris == null || uris.isEmpty) return const <Program>[]; | 184 if (uris == null || uris.isEmpty) return const <Program>[]; |
119 _inputSummariesPrograms = await Future.wait(uris.map(_loadProgram)); | 185 // TODO(sigmund): throttle # of concurrent opreations. |
| 186 var allBytes = await Future |
| 187 .wait(uris.map((uri) => fileSystem.entityForUri(uri).readAsBytes())); |
| 188 _inputSummariesPrograms = |
| 189 allBytes.map((bytes) => loadProgram(bytes, nameRoot)).toList(); |
120 } | 190 } |
121 return _inputSummariesPrograms; | 191 return _inputSummariesPrograms; |
122 } | 192 } |
123 | 193 |
124 Future<Program> _loadProgram(Uri uri) async { | 194 /// Load each of the [CompilerOptions.linkedDependencies] programs. |
125 var bytes = await fileSystem.entityForUri(uri).readAsBytes(); | 195 // TODO(sigmund): move, this doesn't feel like an "option". |
126 return loadProgramFromBytes(bytes)..unbindCanonicalNames(); | 196 Future<List<Program>> loadLinkDependencies(CanonicalName nameRoot) async { |
| 197 if (_linkedDependencies == null) { |
| 198 var uris = _raw.linkedDependencies; |
| 199 if (uris == null || uris.isEmpty) return const <Program>[]; |
| 200 // TODO(sigmund): throttle # of concurrent opreations. |
| 201 var allBytes = await Future |
| 202 .wait(uris.map((uri) => fileSystem.entityForUri(uri).readAsBytes())); |
| 203 _linkedDependencies = |
| 204 allBytes.map((bytes) => loadProgram(bytes, nameRoot)).toList(); |
| 205 } |
| 206 return _linkedDependencies; |
| 207 } |
| 208 |
| 209 /// Helper to load a .dill file from [uri] using the existing [nameRoot]. |
| 210 Program loadProgram(List<int> bytes, CanonicalName nameRoot) { |
| 211 return loadProgramFromBytes(bytes, new Program(nameRoot: nameRoot)); |
127 } | 212 } |
128 | 213 |
129 /// Get the [TranslateUri] which resolves "package:" and "dart:" URIs. | 214 /// Get the [TranslateUri] which resolves "package:" and "dart:" URIs. |
130 /// | 215 /// |
131 /// This is an asynchronous method since file system operations may be | 216 /// This is an asynchronous method since file system operations may be |
132 /// required to locate/read the packages file as well as SDK metadata. | 217 /// required to locate/read the packages file as well as SDK metadata. |
133 Future<TranslateUri> getUriTranslator() async { | 218 Future<TranslateUri> getUriTranslator() async { |
134 if (_uriTranslator == null) { | 219 if (_uriTranslator == null) { |
135 await _getPackages(); | 220 await _getPackages(); |
136 // TODO(scheglov) Load SDK libraries from whatever format we decide. | 221 // TODO(scheglov) Load SDK libraries from whatever format we decide. |
137 // TODO(scheglov) Remove the field "_raw.dartLibraries". | 222 // TODO(scheglov) Remove the field "_raw.dartLibraries". |
138 _uriTranslator = new TranslateUri( | 223 var libraries = _raw.dartLibraries ?? await _parseLibraries(); |
139 _packages, _raw.dartLibraries, const <String, List<Uri>>{}); | 224 _uriTranslator = |
140 _uriTranslator.dartLibraries.addAll(_raw.dartLibraries); | 225 new TranslateUri(_packages, libraries, const <String, List<Uri>>{}); |
| 226 ticker.logMs("Read packages file"); |
141 } | 227 } |
142 return _uriTranslator; | 228 return _uriTranslator; |
143 } | 229 } |
144 | 230 |
| 231 Future<Map<String, Uri>> _parseLibraries() async { |
| 232 Uri librariesJson = _raw.sdkRoot?.resolve("lib/libraries.json"); |
| 233 return await computeLibraries(fileSystem, librariesJson); |
| 234 } |
| 235 |
145 /// Get the package map which maps package names to URIs. | 236 /// Get the package map which maps package names to URIs. |
146 /// | 237 /// |
147 /// This is an asynchronous getter since file system operations may be | 238 /// This is an asynchronous getter since file system operations may be |
148 /// required to locate/read the packages file. | 239 /// required to locate/read the packages file. |
149 Future<Map<String, Uri>> _getPackages() async { | 240 Future<Map<String, Uri>> _getPackages() async { |
150 if (_packages == null) { | 241 if (_packages == null) { |
151 if (_raw.packagesFileUri == null) { | 242 if (_raw.packagesFileUri == null) { |
152 throw new UnimplementedError(); // TODO(paulberry): search for .packages | 243 // TODO(sigmund,paulberry): implement |
| 244 throw new UnimplementedError('search for .packages'); |
153 } else if (_raw.packagesFileUri.path.isEmpty) { | 245 } else if (_raw.packagesFileUri.path.isEmpty) { |
154 _packages = {}; | 246 _packages = {}; |
155 } else { | 247 } else { |
156 var contents = | 248 var contents = |
157 await fileSystem.entityForUri(_raw.packagesFileUri).readAsBytes(); | 249 await fileSystem.entityForUri(_raw.packagesFileUri).readAsBytes(); |
158 _packages = package_config.parse(contents, _raw.packagesFileUri); | 250 _packages = package_config.parse(contents, _raw.packagesFileUri); |
159 } | 251 } |
160 } | 252 } |
161 return _packages; | 253 return _packages; |
162 } | 254 } |
163 | 255 |
164 /// Get the location of the SDK. | 256 /// Get the location of the SDK. |
165 /// | |
166 /// This is an asynchronous getter since file system operations may be | |
167 /// required to locate the SDK. | |
168 Uri _normalizeSdkRoot() { | 257 Uri _normalizeSdkRoot() { |
169 // If an SDK summary location was provided, the SDK itself should not be | 258 // If an SDK summary location was provided, the SDK itself should not be |
170 // needed. | 259 // needed. |
171 assert(_raw.sdkSummary == null); | 260 assert(_raw.sdkSummary == null); |
172 if (_raw.sdkRoot == null) { | 261 if (_raw.sdkRoot == null) { |
173 // TODO(paulberry): implement the algorithm for finding the SDK | 262 // TODO(paulberry): implement the algorithm for finding the SDK |
174 // automagically. | 263 // automagically. |
175 throw new UnimplementedError(); | 264 throw new UnimplementedError('infer the default sdk location'); |
176 } | 265 } |
177 var root = _raw.sdkRoot; | 266 var root = _raw.sdkRoot; |
178 if (!root.path.endsWith('/')) { | 267 if (!root.path.endsWith('/')) { |
179 root = root.replace(path: _sdkRoot.path + '/'); | 268 root = root.replace(path: root.path + '/'); |
180 } | 269 } |
181 return root; | 270 return root; |
182 } | 271 } |
| 272 |
| 273 /// Get or infer the location of the SDK summary. |
| 274 Uri _computeSdkSummaryUri() { |
| 275 if (_raw.sdkSummary != null) return _raw.sdkSummary; |
| 276 |
| 277 // Infer based on the sdkRoot, but only when `compileSdk` is false, |
| 278 // otherwise the default intent was to compile the sdk from sources and not |
| 279 // to load an sdk summary file. |
| 280 if (_raw.compileSdk) return null; |
| 281 return sdkRoot.resolve('outline.dill'); |
| 282 } |
| 283 |
| 284 /// Create a [FileSystem] specific to the current options. |
| 285 /// |
| 286 /// If [chaseDependencies] is false, the resulting file system will be |
| 287 /// hermetic. |
| 288 FileSystem _createFileSystem() { |
| 289 var result = _raw.fileSystem; |
| 290 if (!chaseDependencies) { |
| 291 var allInputs = inputs.toSet(); |
| 292 allInputs.addAll(_raw.inputSummaries); |
| 293 allInputs.addAll(_raw.linkedDependencies); |
| 294 |
| 295 if (sdkSummary != null) allInputs.add(sdkSummary); |
| 296 |
| 297 if (_raw.sdkRoot != null) { |
| 298 // TODO(sigmund): refine this, we should be more explicit about when |
| 299 // sdkRoot and libraries.json are allowed to be used. |
| 300 allInputs.add(sdkRoot); |
| 301 allInputs.add(sdkRoot.resolve("lib/libraries.json")); |
| 302 } |
| 303 |
| 304 /// Note: Searching the file-system for the package-config is not |
| 305 /// supported in hermetic builds. |
| 306 if (_raw.packagesFileUri != null) allInputs.add(_raw.packagesFileUri); |
| 307 result = new HermeticFileSystem(allInputs, result); |
| 308 } |
| 309 // TODO(paulberry): support multiRoots. |
| 310 assert(_raw.multiRoots.isEmpty); |
| 311 return result; |
| 312 } |
183 } | 313 } |
| 314 |
| 315 /// A [FileSystem] that only allows access to files that have been explicitly |
| 316 /// whitelisted. |
| 317 class HermeticFileSystem implements FileSystem { |
| 318 final Set<Uri> includedFiles; |
| 319 final FileSystem _realFileSystem; |
| 320 |
| 321 HermeticFileSystem(this.includedFiles, this._realFileSystem); |
| 322 |
| 323 FileSystemEntity entityForUri(Uri uri) { |
| 324 if (includedFiles.contains(uri)) return _realFileSystem.entityForUri(uri); |
| 325 throw new HermeticAccessException(uri); |
| 326 } |
| 327 } |
| 328 |
| 329 class HermeticAccessException extends FileSystemException { |
| 330 HermeticAccessException(Uri uri) |
| 331 : super( |
| 332 uri, |
| 333 'Invalid access to $uri: ' |
| 334 'the file is accessed in a modular hermetic build, ' |
| 335 'but it was not explicitly listed as an input.'); |
| 336 |
| 337 @override |
| 338 String toString() => message; |
| 339 } |
| 340 |
| 341 /// An error that only contains a message and no error location. |
| 342 class _CompilationError implements CompilationError { |
| 343 String get correction => null; |
| 344 SourceSpan get span => null; |
| 345 final String message; |
| 346 _CompilationError(this.message); |
| 347 |
| 348 String toString() => message; |
| 349 } |
OLD | NEW |