blob: cbe7ebcb1145af8382fdbae87334436477f29750 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'dart:async' show Future;
import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;
import 'package:kernel/kernel.dart' show CanonicalName, Component, Location;
import 'package:kernel/target/targets.dart' show Target, TargetFlags;
import 'package:kernel/target/vm.dart' show VmTarget;
import 'package:package_config/packages.dart' show Packages;
import 'package:package_config/packages_file.dart' as package_config;
import 'package:package_config/src/packages_impl.dart'
show NonFilePackagesDirectoryPackages, MapPackages;
import 'package:source_span/source_span.dart' show SourceSpan, SourceLocation;
import '../api_prototype/byte_store.dart' show ByteStore;
import '../api_prototype/compilation_message.dart' show CompilationMessage;
import '../api_prototype/compiler_options.dart' show CompilerOptions;
import '../api_prototype/file_system.dart'
show FileSystem, FileSystemEntity, FileSystemException;
import '../base/performance_logger.dart' show PerformanceLog;
import '../fasta/command_line_reporting.dart' as command_line_reporting;
import '../fasta/deprecated_problems.dart' show deprecated_InputError;
import '../fasta/fasta_codes.dart'
show
LocatedMessage,
Message,
messageCantInferPackagesFromManyInputs,
messageCantInferPackagesFromPackageUri,
messageInternalProblemProvidedBothCompileSdkAndSdkSummary,
messageMissingInput,
noLength,
templateCannotReadPackagesFile,
templateCannotReadSdkSpecification,
templateInputFileNotFound,
templateInternalProblemUnsupported,
templateSdkRootNotFound,
templateSdkSpecificationNotFound,
templateSdkSummaryNotFound;
import '../fasta/messages.dart' show getLocation;
import '../fasta/problems.dart' show unimplemented;
import '../fasta/severity.dart' show Severity;
import '../fasta/ticker.dart' show Ticker;
import '../fasta/uri_translator.dart' show UriTranslator;
import '../fasta/uri_translator_impl.dart' show UriTranslatorImpl;
import 'libraries_specification.dart'
show
LibrariesSpecification,
LibrariesSpecificationException,
TargetLibrariesSpecification;
/// All options needed for the front end implementation.
///
/// This includes: all of [CompilerOptions] in a form useful to the
/// implementation, default values for options that were not provided,
/// and information derived from how the compiler was invoked (like the
/// entry-points given to the compiler and whether a modular or whole-program
/// API was used).
///
/// The intent is that the front end should immediately wrap any incoming
/// [CompilerOptions] object in this class before doing further processing, and
/// should thereafter access all options via the wrapper. This ensures that
/// options are interpreted in a consistent way and that data derived from
/// options is not unnecessarily recomputed.
class ProcessedOptions {
/// The raw [CompilerOptions] which this class wraps.
final CompilerOptions _raw;
/// The package map derived from the options, or `null` if the package map has
/// not been computed yet.
Packages _packages;
/// The object that knows how to resolve "package:" and "dart:" URIs,
/// or `null` if it has not been computed yet.
UriTranslatorImpl _uriTranslator;
/// The SDK summary, or `null` if it has not been read yet.
///
/// A summary, also referred to as "outline" internally, is a [Component] where
/// all method bodies are left out. In essence, it contains just API
/// signatures and constants. When strong-mode is enabled, the summary already
/// includes inferred types.
Component _sdkSummaryProgram;
/// The summary for each uri in `options.inputSummaries`.
///
/// A summary, also referred to as "outline" internally, is a [Component] where
/// all method bodies are left out. In essence, it contains just API
/// signatures and constants. When strong-mode is enabled, the summary already
/// includes inferred types.
List<Component> _inputSummariesPrograms;
/// Other programs that are meant to be linked and compiled with the input
/// sources.
List<Component> _linkedDependencies;
/// The location of the SDK, or `null` if the location hasn't been determined
/// yet.
Uri _sdkRoot;
Uri get sdkRoot {
_ensureSdkDefaults();
return _sdkRoot;
}
Uri _sdkSummary;
Uri get sdkSummary {
_ensureSdkDefaults();
return _sdkSummary;
}
List<int> _sdkSummaryBytes;
/// Get the bytes of the SDK outline, if any.
Future<List<int>> loadSdkSummaryBytes() async {
if (_sdkSummaryBytes == null) {
if (sdkSummary == null) return null;
var entry = fileSystem.entityForUri(sdkSummary);
_sdkSummaryBytes = await entry.readAsBytes();
}
return _sdkSummaryBytes;
}
Uri _librariesSpecificationUri;
Uri get librariesSpecificationUri {
_ensureSdkDefaults();
return _librariesSpecificationUri;
}
Ticker ticker;
bool get verbose => _raw.verbose;
bool get verify => _raw.verify;
bool get debugDump => _raw.debugDump;
bool get setExitCodeOnProblem => _raw.setExitCodeOnProblem;
bool get embedSourceText => _raw.embedSourceText;
bool get throwOnErrorsForDebugging => _raw.throwOnErrorsForDebugging;
bool get throwOnWarningsForDebugging => _raw.throwOnWarningsForDebugging;
bool get throwOnNitsForDebugging => _raw.throwOnNitsForDebugging;
/// Like [CompilerOptions.chaseDependencies] but with the appropriate default
/// value filled in.
bool get chaseDependencies => _raw.chaseDependencies ?? !_modularApi;
/// Whether the compiler was invoked with a modular API.
///
/// Used to determine the default behavior for [chaseDependencies].
final bool _modularApi;
/// The entry-points provided to the compiler.
final List<Uri> inputs;
/// The Uri where output is generated, may be null.
final Uri output;
/// Initializes a [ProcessedOptions] object wrapping the given [rawOptions].
ProcessedOptions(CompilerOptions rawOptions,
[this._modularApi = false, this.inputs = const [], this.output])
: this._raw = rawOptions,
// TODO(sigmund, ahe): create ticker even earlier or pass in a stopwatch
// collecting time since the start of the VM.
ticker = new Ticker(isVerbose: rawOptions.verbose);
/// The logger to report compilation progress.
PerformanceLog get logger {
return _raw.logger;
}
/// The byte storage to get and put serialized data.
ByteStore get byteStore {
return _raw.byteStore;
}
bool get _reportMessages {
return _raw.onProblem == null &&
(_raw.reportMessages ?? (_raw.onError == null));
}
void report(LocatedMessage message, Severity severity) {
if (_raw.onProblem != null) {
int offset = message.charOffset;
Uri uri = message.uri;
Location location = offset == -1 ? null : getLocation(uri, offset);
_raw.onProblem(
message,
severity,
command_line_reporting.format(message, severity, location: location),
location?.line ?? -1,
location?.column ?? -1);
if (command_line_reporting.shouldThrowOn(severity)) {
if (verbose) print(StackTrace.current);
throw new deprecated_InputError(
uri,
offset,
"Compilation aborted due to fatal "
"${command_line_reporting.severityName(severity)}.");
}
return;
}
if (_raw.onError != null) {
_raw.onError(new _CompilationMessage(message, severity));
}
if (_reportMessages) command_line_reporting.report(message, severity);
}
// TODO(askesc): Remove this and direct callers directly to report.
void reportWithoutLocation(Message message, Severity severity) {
report(message.withoutLocation(), severity);
}
/// Runs various validations checks on the input options. For instance,
/// if an option is a path to a file, it checks that the file exists.
Future<bool> validateOptions() async {
if (verbose) print(debugString());
if (inputs.isEmpty) {
reportWithoutLocation(messageMissingInput, Severity.error);
return false;
}
for (var source in inputs) {
// Note: we don't translate Uris at this point because some of the
// validation further below must be done before we even construct an
// UriTranslator
// TODO(sigmund): consider validating dart/packages uri right after we
// build the uri translator.
if (source.scheme != 'dart' &&
source.scheme != 'package' &&
!await fileSystem.entityForUri(source).exists()) {
reportWithoutLocation(
templateInputFileNotFound.withArguments(source), Severity.error);
return false;
}
}
if (_raw.sdkRoot != null &&
!await fileSystem.entityForUri(sdkRoot).exists()) {
reportWithoutLocation(
templateSdkRootNotFound.withArguments(sdkRoot), Severity.error);
return false;
}
var summary = sdkSummary;
if (summary != null && !await fileSystem.entityForUri(summary).exists()) {
reportWithoutLocation(
templateSdkSummaryNotFound.withArguments(summary), Severity.error);
return false;
}
if (compileSdk && summary != null) {
reportWithoutLocation(
messageInternalProblemProvidedBothCompileSdkAndSdkSummary,
Severity.internalProblem);
return false;
}
return true;
}
/// Determine whether to generate code for the SDK when compiling a
/// whole-program.
bool get compileSdk => _raw.compileSdk;
FileSystem _fileSystem;
/// Get the [FileSystem] which should be used by the front end to access
/// files.
FileSystem get fileSystem => _fileSystem ??= _createFileSystem();
/// Clear the file system so any CompilerOptions fileSystem change will have
/// effect.
void clearFileSystemCache() => _fileSystem = null;
/// Whether to interpret Dart sources in strong-mode.
bool get strongMode => _raw.strongMode;
Target _target;
Target get target => _target ??=
_raw.target ?? new VmTarget(new TargetFlags(strongMode: strongMode));
/// Get an outline component that summarizes the SDK, if any.
// TODO(sigmund): move, this doesn't feel like an "option".
Future<Component> loadSdkSummary(CanonicalName nameRoot) async {
if (_sdkSummaryProgram == null) {
if (sdkSummary == null) return null;
var bytes = await loadSdkSummaryBytes();
_sdkSummaryProgram = loadComponent(bytes, nameRoot);
}
return _sdkSummaryProgram;
}
void set sdkSummaryComponent(Component platform) {
if (_sdkSummaryProgram != null) {
throw new StateError("sdkSummary already loaded.");
}
_sdkSummaryProgram = platform;
}
/// Get the summary programs for each of the underlying `inputSummaries`
/// provided via [CompilerOptions].
// TODO(sigmund): move, this doesn't feel like an "option".
Future<List<Component>> loadInputSummaries(CanonicalName nameRoot) async {
if (_inputSummariesPrograms == null) {
var uris = _raw.inputSummaries;
if (uris == null || uris.isEmpty) return const <Component>[];
// TODO(sigmund): throttle # of concurrent opreations.
var allBytes = await Future
.wait(uris.map((uri) => fileSystem.entityForUri(uri).readAsBytes()));
_inputSummariesPrograms =
allBytes.map((bytes) => loadComponent(bytes, nameRoot)).toList();
}
return _inputSummariesPrograms;
}
/// Load each of the [CompilerOptions.linkedDependencies] programs.
// TODO(sigmund): move, this doesn't feel like an "option".
Future<List<Component>> loadLinkDependencies(CanonicalName nameRoot) async {
if (_linkedDependencies == null) {
var uris = _raw.linkedDependencies;
if (uris == null || uris.isEmpty) return const <Component>[];
// TODO(sigmund): throttle # of concurrent opreations.
var allBytes = await Future
.wait(uris.map((uri) => fileSystem.entityForUri(uri).readAsBytes()));
_linkedDependencies =
allBytes.map((bytes) => loadComponent(bytes, nameRoot)).toList();
}
return _linkedDependencies;
}
/// Helper to load a .dill file from [uri] using the existing [nameRoot].
Component loadComponent(List<int> bytes, CanonicalName nameRoot) {
Component component = new Component(nameRoot: nameRoot);
// TODO(ahe): Pass file name to BinaryBuilder.
// TODO(ahe): Control lazy loading via an option.
new BinaryBuilder(bytes, filename: null, disableLazyReading: false)
.readComponent(component);
return component;
}
/// Get the [UriTranslator] which resolves "package:" and "dart:" URIs.
///
/// This is an asynchronous method since file system operations may be
/// required to locate/read the packages file as well as SDK metadata.
Future<UriTranslatorImpl> getUriTranslator() async {
if (_uriTranslator == null) {
ticker.logMs("Started building UriTranslator");
var libraries = await _computeLibrarySpecification();
ticker.logMs("Read libraries file");
var packages = await _getPackages();
ticker.logMs("Read packages file");
_uriTranslator = new UriTranslatorImpl(libraries, packages);
}
return _uriTranslator;
}
Future<TargetLibrariesSpecification> _computeLibrarySpecification() async {
var name = target.name;
// TODO(sigmund): Eek! We should get to the point where there is no
// fasta-specific targets and the target names are meaningful.
if (name.endsWith('_fasta')) name = name.substring(0, name.length - 6);
if (librariesSpecificationUri == null ||
!await fileSystem.entityForUri(librariesSpecificationUri).exists()) {
if (compileSdk) {
reportWithoutLocation(
templateSdkSpecificationNotFound
.withArguments(librariesSpecificationUri),
Severity.error);
}
return new TargetLibrariesSpecification(name);
}
var json =
await fileSystem.entityForUri(librariesSpecificationUri).readAsString();
try {
var spec =
await LibrariesSpecification.parse(librariesSpecificationUri, json);
return spec.specificationFor(name);
} on LibrariesSpecificationException catch (e) {
reportWithoutLocation(
templateCannotReadSdkSpecification.withArguments('${e.error}'),
Severity.error);
return new TargetLibrariesSpecification(name);
}
}
/// Get the package map which maps package names to URIs.
///
/// This is an asynchronous getter since file system operations may be
/// required to locate/read the packages file.
Future<Packages> _getPackages() async {
if (_packages != null) return _packages;
if (_raw.packagesFileUri != null) {
return _packages = await createPackagesFromFile(_raw.packagesFileUri);
}
if (inputs.length > 1) {
// TODO(sigmund): consider not reporting an error if we would infer
// the same .packages file from all of the inputs.
reportWithoutLocation(
messageCantInferPackagesFromManyInputs, Severity.error);
return _packages = Packages.noPackages;
}
var input = inputs.first;
// When compiling the SDK the input files are normaly `dart:` URIs.
if (input.scheme == 'dart') return _packages = Packages.noPackages;
if (input.scheme == 'packages') {
report(
messageCantInferPackagesFromPackageUri.withLocation(
input, -1, noLength),
Severity.error);
return _packages = Packages.noPackages;
}
return _packages = await _findPackages(inputs.first);
}
/// Create a [Packages] given the Uri to a `.packages` file.
Future<Packages> createPackagesFromFile(Uri file) async {
try {
List<int> contents = await fileSystem.entityForUri(file).readAsBytes();
Map<String, Uri> map = package_config.parse(contents, file);
return new MapPackages(map);
} catch (e) {
report(
templateCannotReadPackagesFile
.withArguments("$e")
.withLocation(file, -1, noLength),
Severity.error);
return Packages.noPackages;
}
}
/// Finds a package resolution strategy using a [FileSystem].
///
/// The [scriptUri] points to a Dart script with a valid scheme accepted by
/// the [FileSystem].
///
/// This function first tries to locate a `.packages` file in the `scriptUri`
/// directory. If that is not found, it instead checks for the presence of a
/// `packages/` directory in the same place. If that also fails, it starts
/// checking parent directories for a `.packages` file, and stops if it finds
/// it. Otherwise it gives up and returns [Packages.noPackages].
///
/// Note: this is a fork from `package:package_config/discovery.dart` to adapt
/// it to use [FileSystem]. The logic here is a mix of the logic in the
/// `findPackagesFromFile` and `findPackagesFromNonFile`:
///
/// * Like `findPackagesFromFile` resolution searches for parent
/// directories
///
/// * Like `findPackagesFromNonFile` if we resolve packages as the
/// `packages/` directory, we can't provide a list of packages that are
/// visible.
Future<Packages> _findPackages(Uri scriptUri) async {
var dir = scriptUri.resolve('.');
if (!dir.isAbsolute) {
reportWithoutLocation(
templateInternalProblemUnsupported
.withArguments("Expected input Uri to be absolute: $scriptUri."),
Severity.internalProblem);
return Packages.noPackages;
}
Future<Uri> checkInDir(Uri dir) async {
Uri candidate = dir.resolve('.packages');
if (await fileSystem.entityForUri(candidate).exists()) return candidate;
return null;
}
// Check for $cwd/.packages
var candidate = await checkInDir(dir);
if (candidate != null) return createPackagesFromFile(candidate);
// Check for $cwd/packages/
var packagesDir = dir.resolve("packages/");
if (await fileSystem.entityForUri(packagesDir).exists()) {
return new NonFilePackagesDirectoryPackages(packagesDir);
}
// Check for cwd(/..)+/.packages
var parentDir = dir.resolve('..');
while (parentDir.path != dir.path) {
candidate = await checkInDir(parentDir);
if (candidate != null) break;
dir = parentDir;
parentDir = dir.resolve('..');
}
if (candidate != null) return createPackagesFromFile(candidate);
return Packages.noPackages;
}
bool _computedSdkDefaults = false;
/// Ensure [_sdkRoot], [_sdkSummary] and [_librarySpecUri] are initialized.
///
/// If they are not set explicitly, they are infered based on the default
/// behavior described in [CompilerOptions].
void _ensureSdkDefaults() {
if (_computedSdkDefaults) return;
_computedSdkDefaults = true;
var root = _raw.sdkRoot;
if (root != null) {
// Normalize to always end in '/'
if (!root.path.endsWith('/')) {
root = root.replace(path: root.path + '/');
}
_sdkRoot = root;
} else if (compileSdk) {
// TODO(paulberry): implement the algorithm for finding the SDK
// automagically.
unimplemented('infer the default sdk location', -1, null);
}
if (_raw.sdkSummary != null) {
_sdkSummary = _raw.sdkSummary;
} else if (!compileSdk) {
// Infer based on the sdkRoot, but only when `compileSdk` is false,
// otherwise the default intent was to compile the sdk from sources and
// not to load an sdk summary file.
_sdkSummary = root?.resolve("vm_platform.dill");
}
if (_raw.librariesSpecificationUri != null) {
_librariesSpecificationUri = _raw.librariesSpecificationUri;
} else if (compileSdk) {
_librariesSpecificationUri = sdkRoot.resolve('lib/libraries.json');
}
}
/// Create a [FileSystem] specific to the current options.
///
/// If [chaseDependencies] is false, the resulting file system will be
/// hermetic.
FileSystem _createFileSystem() {
var result = _raw.fileSystem;
if (!chaseDependencies) {
var allInputs = inputs.toSet();
allInputs.addAll(_raw.inputSummaries);
allInputs.addAll(_raw.linkedDependencies);
if (sdkSummary != null) allInputs.add(sdkSummary);
if (_raw.sdkRoot != null) {
// TODO(sigmund): refine this, we should be more explicit about when
// sdkRoot and libraries.json are allowed to be used.
allInputs.add(sdkRoot);
allInputs.add(sdkRoot.resolve("lib/libraries.json"));
}
/// Note: Searching the file-system for the package-config is not
/// supported in hermetic builds.
if (_raw.packagesFileUri != null) allInputs.add(_raw.packagesFileUri);
result = new HermeticFileSystem(allInputs, result);
}
return result;
}
String debugString() {
var sb = new StringBuffer();
writeList(String name, List elements) {
if (elements.isEmpty) {
sb.writeln('$name: <empty>');
return;
}
sb.writeln('$name:');
elements.forEach((s) {
sb.writeln(' - $s');
});
}
sb.writeln('Inputs: ${inputs}');
sb.writeln('Output: ${output}');
sb.writeln('Was error handler provided: '
'${_raw.onError == null ? "no" : "yes"}');
sb.writeln('FileSystem: ${_fileSystem.runtimeType} '
'(provided: ${_raw.fileSystem.runtimeType})');
writeList('Input Summaries', _raw.inputSummaries);
writeList('Linked Dependencies', _raw.linkedDependencies);
sb.writeln('Modular: ${_modularApi}');
sb.writeln('Hermetic: ${!chaseDependencies} (provided: '
'${_raw.chaseDependencies == null ? null : !_raw.chaseDependencies})');
sb.writeln('Packages uri: ${_raw.packagesFileUri}');
sb.writeln('Packages: ${_packages}');
sb.writeln('Compile SDK: ${compileSdk}');
sb.writeln('SDK root: ${_sdkRoot} (provided: ${_raw.sdkRoot})');
sb.writeln('SDK specification: ${_librariesSpecificationUri} '
'(provided: ${_raw.librariesSpecificationUri})');
sb.writeln('SDK summary: ${_sdkSummary} (provided: ${_raw.sdkSummary})');
sb.writeln('Strong: ${strongMode}');
sb.writeln('Target: ${_target?.name} (provided: ${_raw.target?.name})');
sb.writeln('throwOnErrorsForDebugging: ${throwOnErrorsForDebugging}');
sb.writeln('throwOnWarningsForDebugging: ${throwOnWarningsForDebugging}');
sb.writeln('throwOnNitsForDebugging: ${throwOnNitsForDebugging}');
sb.writeln('exit on problem: ${setExitCodeOnProblem}');
sb.writeln('Embed sources: ${embedSourceText}');
sb.writeln('debugDump: ${debugDump}');
sb.writeln('verbose: ${verbose}');
sb.writeln('verify: ${verify}');
return '$sb';
}
}
/// A [FileSystem] that only allows access to files that have been explicitly
/// whitelisted.
class HermeticFileSystem implements FileSystem {
final Set<Uri> includedFiles;
final FileSystem _realFileSystem;
HermeticFileSystem(this.includedFiles, this._realFileSystem);
FileSystemEntity entityForUri(Uri uri) {
if (includedFiles.contains(uri)) return _realFileSystem.entityForUri(uri);
throw new HermeticAccessException(uri);
}
}
class HermeticAccessException extends FileSystemException {
HermeticAccessException(Uri uri)
: super(
uri,
'Invalid access to $uri: '
'the file is accessed in a modular hermetic build, '
'but it was not explicitly listed as an input.');
@override
String toString() => message;
}
/// Wraps a [LocatedMessage] to implement the public [CompilationMessage] API.
class _CompilationMessage implements CompilationMessage {
final LocatedMessage _original;
final Severity severity;
String get message => _original.message;
String get tip => _original.tip;
String get code => _original.code.name;
String get analyzerCode => _original.code.analyzerCode;
String get dart2jsCode => _original.code.dart2jsCode;
SourceSpan get span {
if (_original.charOffset == -1) {
if (_original.uri == null) return null;
return new SourceLocation(0, sourceUrl: _original.uri).pointSpan();
}
return new SourceLocation(_original.charOffset, sourceUrl: _original.uri)
.pointSpan();
}
_CompilationMessage(this._original, this.severity);
String toString() => message;
}