blob: a85e49239ccdd784606bc19baea6bf06ae16f9b8 [file] [log] [blame] [edit]
// 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.
/// Defines the VM-specific translation of Dart source code to kernel binaries.
library;
import 'dart:async';
import 'dart:io' show File, IOSink;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:build_integration/file_system/multi_root.dart'
show MultiRootFileSystem, MultiRootFileSystemEntity;
import 'package:crypto/crypto.dart';
import 'package:front_end/src/api_prototype/macros.dart' as macros
show isMacroLibraryUri;
import 'package:front_end/src/api_unstable/vm.dart'
show
CompilerContext,
CompilerOptions,
CompilerResult,
InvocationMode,
DiagnosticMessage,
DiagnosticMessageHandler,
FileSystem,
FileSystemEntity,
ProcessedOptions,
Severity,
StandardFileSystem,
Verbosity,
getMessageUri,
kernelForModule,
kernelForProgram,
parseExperimentalArguments,
parseExperimentalFlags,
printDiagnosticMessage,
resolveInputUri;
import 'package:kernel/ast.dart'
show Component, Library, NonNullableByDefaultCompiledMode;
import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/kernel.dart' show loadComponentFromBinary;
import 'package:kernel/target/targets.dart' show Target, TargetFlags, getTarget;
import 'package:package_config/package_config.dart' show loadPackageConfigUri;
import 'http_filesystem.dart' show HttpAwareFileSystem;
import 'modular/target/install.dart' show installAdditionalTargets;
import 'modular/transformations/call_site_annotator.dart'
as call_site_annotator;
import 'native_assets/synthesizer.dart';
import 'target_os.dart';
import 'transformations/deferred_loading.dart' as deferred_loading;
import 'transformations/devirtualization.dart' as devirtualization
show transformComponent;
import 'transformations/dynamic_interface_annotator.dart'
as dynamic_interface_annotator show annotateComponent;
import 'transformations/mixin_deduplication.dart' as mixin_deduplication
show transformComponent;
import 'transformations/no_dynamic_invocations_annotator.dart'
as no_dynamic_invocations_annotator show transformComponent;
import 'transformations/obfuscation_prohibitions_annotator.dart'
as obfuscationProhibitions;
import 'transformations/record_use/record_use.dart' as record_use;
import 'transformations/to_string_transformer.dart' as to_string_transformer;
import 'transformations/type_flow/transformer.dart' as globalTypeFlow
show transformComponent;
import 'transformations/unreachable_code_elimination.dart'
as unreachable_code_elimination;
import 'transformations/vm_constant_evaluator.dart' as vm_constant_evaluator;
/// Declare options consumed by [runCompiler].
void declareCompilerOptions(ArgParser args) {
args.addOption('platform',
help: 'Path to vm_platform_strong.dill file', defaultsTo: null);
args.addOption('packages',
help: 'Path to .dart_tool/package_config.json file', defaultsTo: null);
args.addOption('output',
abbr: 'o', help: 'Path to resulting dill file', defaultsTo: null);
args.addFlag('aot',
help:
'Produce kernel file for AOT compilation (enables global transformations).',
defaultsTo: false);
args.addFlag('support-mirrors',
help: 'Whether dart:mirrors is supported. By default dart:mirrors is '
'supported when --aot and --minimal-kernel are not used.',
defaultsTo: null);
args.addFlag('compact-async', help: 'Obsolete, ignored.', hide: true);
args.addOption('depfile', help: 'Path to output Ninja depfile');
args.addOption(
'depfile-target',
help: 'Override the target in the generated depfile',
hide: true,
);
args.addOption('from-dill',
help: 'Read existing dill file instead of compiling from sources',
defaultsTo: null);
args.addFlag('link-platform',
help: 'Include platform into resulting kernel file.', defaultsTo: true);
args.addFlag('minimal-kernel',
help: 'Produce minimal tree-shaken kernel file.', defaultsTo: false);
args.addFlag('embed-sources',
help: 'Embed source files in the generated kernel component',
defaultsTo: true);
args.addMultiOption('filesystem-root',
help: 'A base path for the multi-root virtual file system.'
' If multi-root file system is used, the input script and .dart_tool/package_config.json file should be specified using URI.');
args.addOption('filesystem-scheme',
help: 'The URI scheme for the multi-root virtual filesystem.');
args.addMultiOption('source',
help: 'List additional source files to include into compilation.',
defaultsTo: const <String>[]);
args.addOption('native-assets',
help:
'Provide the native-assets mapping for @Native external functions.');
args.addOption('recorded-usages-file',
help: 'The path to store the recorded usages.');
args.addOption('target',
help: 'Target model that determines what core libraries are available',
allowed: <String>['vm', 'flutter', 'flutter_runner', 'dart_runner'],
defaultsTo: 'vm');
args.addFlag('tfa',
help:
'Enable global type flow analysis and related transformations in AOT mode.',
defaultsTo: true);
args.addOption('target-os',
help: 'Compile for a specific target operating system when in AOT mode.',
allowed: TargetOS.names);
args.addFlag('rta',
help: 'Use rapid type analysis for faster compilation in AOT mode.',
defaultsTo: true);
args.addFlag('tree-shake-write-only-fields',
help: 'Enable tree shaking of fields which are only written in AOT mode.',
defaultsTo: true);
args.addFlag('protobuf-tree-shaker-v2',
help: 'Enable protobuf tree shaker v2 in AOT mode.', defaultsTo: false);
args.addMultiOption('define',
abbr: 'D',
help: 'The values for the environment constants (e.g. -Dkey=value).');
args.addFlag('enable-asserts',
help: 'Whether asserts will be enabled.', defaultsTo: false);
args.addFlag('sound-null-safety',
help: 'Respect the nullability of types at runtime.',
defaultsTo: true,
hide: true);
args.addFlag('split-output-by-packages',
help:
'Split resulting kernel file into multiple files (one per package).',
defaultsTo: false);
args.addOption('component-name',
help: 'Name of the Fuchsia component', defaultsTo: null);
args.addOption('data-dir',
help: 'Name of the subdirectory of //data for output files');
args.addOption('dynamic-interface',
help: 'Path to dynamic module interface yaml file.');
args.addOption('manifest', help: 'Path to output Fuchsia package manifest');
args.addMultiOption('enable-experiment',
help: 'Comma separated list of experimental features to enable.');
args.addFlag('help',
abbr: 'h', negatable: false, help: 'Print this help message.');
args.addFlag('track-widget-creation',
help: 'Run a kernel transformer to track creation locations for widgets.',
defaultsTo: false);
args.addMultiOption(
'delete-tostring-package-uri',
help: 'Replaces implementations of `toString` with `super.toString()` for '
'specified package',
valueHelp: 'dart:ui',
defaultsTo: const <String>[],
);
args.addMultiOption(
'keep-class-names-implementing',
help: 'Prevents obfuscation of the class names of any class implementing '
'the given class.',
defaultsTo: const <String>[],
);
args.addOption('invocation-modes',
help: 'Provides information to the front end about how it is invoked.',
defaultsTo: '');
args.addOption('verbosity',
help: 'Sets the verbosity level used for filtering messages during '
'compilation.',
defaultsTo: Verbosity.defaultValue);
}
/// Create ArgParser and populate it with options consumed by [runCompiler].
ArgParser createCompilerArgParser() {
final ArgParser argParser = new ArgParser(allowTrailingOptions: true);
declareCompilerOptions(argParser);
return argParser;
}
const int successExitCode = 0;
const int badUsageExitCode = 1;
const int compileTimeErrorExitCode = 254;
/// Run kernel compiler tool with given [options] and [usage]
/// and return exit code.
Future<int> runCompiler(ArgResults options, String usage) async {
final String? platformKernel = options['platform'];
if (options['help']) {
print(usage);
return successExitCode;
}
final String? nativeAssetsPath = options['native-assets'];
final String? recordedUsagesFile = options['recorded-usages-file'];
final bool splitOutputByPackages = options['split-output-by-packages'];
final String? input = options.rest.singleOrNull;
if ((input == null && (nativeAssetsPath == null || splitOutputByPackages)) ||
(platformKernel == null)) {
print(usage);
return badUsageExitCode;
}
final String outputFileName = options['output'] ?? "$input.dill";
final String? packages = options['packages'];
final String targetName = options['target'];
final String? fileSystemScheme = options['filesystem-scheme'];
final String? depfile = options['depfile'];
final String? depfileTarget = options['depfile-target'];
final String? fromDillFile = options['from-dill'];
final List<String>? fileSystemRoots = options['filesystem-root'];
final String? targetOS = options['target-os'];
final bool aot = options['aot'];
final bool tfa = options['tfa'];
final bool rta = options['rta'];
final bool linkPlatform = options['link-platform'];
final bool embedSources = options['embed-sources'];
final bool enableAsserts = options['enable-asserts'];
final bool useProtobufTreeShakerV2 = options['protobuf-tree-shaker-v2'];
final String? manifestFilename = options['manifest'];
final String? dataDir = options['component-name'] ?? options['data-dir'];
final bool? supportMirrors = options['support-mirrors'];
final bool minimalKernel = options['minimal-kernel'];
final bool treeShakeWriteOnlyFields = options['tree-shake-write-only-fields'];
final List<String>? experimentalFlags = options['enable-experiment'];
final Map<String, String> environmentDefines = {};
final List<String> sources = options['source'];
if (!parseCommandLineDefines(options['define'], environmentDefines, usage)) {
return badUsageExitCode;
}
final bool soundNullSafety = options['sound-null-safety'];
if (!soundNullSafety) {
print('Error: --no-sound-null-safety is not supported.');
return badUsageExitCode;
}
if (aot) {
if (!linkPlatform) {
print('Error: --no-link-platform option cannot be used with --aot');
return badUsageExitCode;
}
if (splitOutputByPackages) {
print(
'Error: --split-output-by-packages option cannot be used with --aot');
return badUsageExitCode;
}
}
if (supportMirrors == true) {
if (aot) {
print('Error: --support-mirrors option cannot be used with --aot');
return badUsageExitCode;
}
if (minimalKernel) {
print('Error: --support-mirrors option cannot be used with '
'--minimal-kernel');
return badUsageExitCode;
}
}
final fileSystem =
createFrontEndFileSystem(fileSystemScheme, fileSystemRoots);
final Uri? packagesUri = packages != null ? resolveInputUri(packages) : null;
final platformKernelUri = Uri.base.resolveUri(new Uri.file(platformKernel));
final List<Uri> additionalDills = <Uri>[];
if (aot || linkPlatform) {
additionalDills.add(platformKernelUri);
}
final verbosity = Verbosity.parseArgument(options['verbosity']);
final errorPrinter = new ErrorPrinter(verbosity);
final errorDetector =
new ErrorDetector(previousErrorHandler: errorPrinter.call);
final Uri? nativeAssetsUri =
nativeAssetsPath == null ? null : resolveInputUri(nativeAssetsPath);
final Uri? recordedUsagesUri =
recordedUsagesFile == null ? null : resolveInputUri(recordedUsagesFile);
final String? dynamicInterfaceFilePath = options['dynamic-interface'];
final Uri? dynamicInterfaceUri = dynamicInterfaceFilePath == null
? null
: resolveInputUri(dynamicInterfaceFilePath);
Uri? mainUri;
if (input != null) {
mainUri = resolveInputUri(input);
if (packagesUri != null) {
mainUri = await convertToPackageUri(fileSystem, mainUri, packagesUri);
}
}
final List<Uri> additionalSources = sources.map(resolveInputUri).toList();
final CompilerOptions compilerOptions = new CompilerOptions()
..sdkSummary = platformKernelUri
..fileSystem = fileSystem
..additionalDills = additionalDills
..packagesFileUri = packagesUri
..explicitExperimentalFlags = parseExperimentalFlags(
parseExperimentalArguments(experimentalFlags),
onError: print)
..onDiagnostic = (DiagnosticMessage m) {
errorDetector(m);
}
..embedSourceText = embedSources
..invocationModes =
InvocationMode.parseArguments(options['invocation-modes'])
..verbosity = verbosity;
compilerOptions.target = createFrontEndTarget(targetName,
trackWidgetCreation: options['track-widget-creation'],
supportMirrors: supportMirrors ?? !(aot || minimalKernel));
if (compilerOptions.target == null) {
print('Failed to create front-end target $targetName.');
return badUsageExitCode;
}
final results = await compileToKernel(KernelCompilationArguments(
source: mainUri,
options: compilerOptions,
additionalSources: additionalSources,
nativeAssets: nativeAssetsUri,
recordedUsages: recordedUsagesUri,
includePlatform: additionalDills.isNotEmpty,
deleteToStringPackageUris: options['delete-tostring-package-uri'],
keepClassNamesImplementing: options['keep-class-names-implementing'],
dynamicInterface: dynamicInterfaceUri,
aot: aot,
useGlobalTypeFlowAnalysis: tfa,
useRapidTypeAnalysis: rta,
environmentDefines: environmentDefines,
enableAsserts: enableAsserts,
useProtobufTreeShakerV2: useProtobufTreeShakerV2,
minimalKernel: minimalKernel,
treeShakeWriteOnlyFields: treeShakeWriteOnlyFields,
targetOS: targetOS,
fromDillFile: fromDillFile));
errorPrinter.printCompilationMessages();
final Component? component = results.component;
final Library? nativeAssetsLibrary = results.nativeAssetsLibrary;
if (errorDetector.hasCompilationErrors ||
(component == null && nativeAssetsLibrary == null)) {
return compileTimeErrorExitCode;
}
final IOSink sink = new File(outputFileName).openWrite();
if (component != null) {
final BinaryPrinter printer = new BinaryPrinter(sink,
libraryFilter: (lib) => !results.loadedLibraries.contains(lib));
if (aot && nativeAssetsLibrary != null) {
// If Dart component in AOT, write the vm:native-assets library _inside_
// the Dart component.
// TODO(https://dartbug.com/50152): Support AOT dill concatenation.
component.libraries.add(nativeAssetsLibrary);
nativeAssetsLibrary.parent = component;
}
printer.writeComponentFile(component);
}
if ((nativeAssetsLibrary != null && (!aot || component == null))) {
// If no Dart component, write as separate dill.
// If Dart component in JIT, write as concatenated dill, to not mess with
// the incremental compiler.
final BinaryPrinter printer = new BinaryPrinter(sink);
printer.writeComponentFile(Component(
libraries: [nativeAssetsLibrary],
mode: NonNullableByDefaultCompiledMode.Strong,
));
}
await sink.close();
if (depfile != null) {
await writeDepfile(
fileSystem,
results.compiledSources!,
depfileTarget ?? outputFileName,
depfile,
);
}
if (splitOutputByPackages) {
await writeOutputSplitByPackages(
mainUri!,
compilerOptions,
results,
outputFileName,
);
}
if (manifestFilename != null) {
await createFarManifest(outputFileName, dataDir, manifestFilename);
}
return successExitCode;
}
/// Results of [compileToKernel]: generated kernel [Component] and
/// collection of compiled sources.
class KernelCompilationResults {
final Component? component;
final Library? nativeAssetsLibrary;
/// Set of libraries loaded from .dill, with or without the SDK depending on
/// the compilation settings.
final Set<Library> loadedLibraries;
final ClassHierarchy? classHierarchy;
final CoreTypes? coreTypes;
final Iterable<Uri>? compiledSources;
KernelCompilationResults(this.component, this.loadedLibraries,
this.classHierarchy, this.coreTypes, this.compiledSources)
: nativeAssetsLibrary = null;
KernelCompilationResults.named({
this.component,
this.loadedLibraries = const {},
this.classHierarchy,
this.coreTypes,
this.compiledSources,
this.nativeAssetsLibrary,
});
}
// Arguments for [compileToKernel].
class KernelCompilationArguments {
final Uri? source;
final CompilerOptions? options;
final List<Uri> additionalSources;
final Uri? nativeAssets;
final Uri? recordedUsages;
final bool requireMain;
final bool includePlatform;
final List<String> deleteToStringPackageUris;
final List<String> keepClassNamesImplementing;
final bool aot;
final Uri? dynamicInterface;
final Map<String, String> environmentDefines; // Should be mutable.
final bool enableAsserts;
final bool useGlobalTypeFlowAnalysis;
final bool useRapidTypeAnalysis;
final bool treeShakeWriteOnlyFields;
final bool useProtobufTreeShakerV2;
final bool minimalKernel;
final String? targetOS;
final String? fromDillFile;
KernelCompilationArguments({
this.source,
this.options,
this.additionalSources = const <Uri>[],
this.nativeAssets,
this.recordedUsages,
this.requireMain = true,
this.includePlatform = false,
this.deleteToStringPackageUris = const <String>[],
this.keepClassNamesImplementing = const <String>[],
this.aot = false,
this.dynamicInterface,
Map<String, String>? environmentDefines,
this.enableAsserts = true,
this.useGlobalTypeFlowAnalysis = false,
this.useRapidTypeAnalysis = true,
this.treeShakeWriteOnlyFields = false,
this.useProtobufTreeShakerV2 = false,
this.minimalKernel = false,
this.targetOS,
this.fromDillFile,
}) : environmentDefines = environmentDefines ?? {};
}
/// Generates a kernel representation of the program whose main library is in
/// the given [args.source]. Intended for whole program (non-modular) compilation.
///
/// VM-specific replacement of [kernelForProgram].
///
/// Either [arg.source], or [args.nativeAssets], or both must be non-null.
Future<KernelCompilationResults> compileToKernel(
KernelCompilationArguments args) async {
final options = args.options!;
// Replace error handler to detect if there are compilation errors.
final errorDetector =
new ErrorDetector(previousErrorHandler: options.onDiagnostic);
options.onDiagnostic = errorDetector.call;
final nativeAssetsLibrary =
await NativeAssetsSynthesizer.synthesizeLibraryFromYamlFile(
args.nativeAssets, errorDetector);
if (args.source == null) {
return KernelCompilationResults.named(
nativeAssetsLibrary: nativeAssetsLibrary,
);
}
final target = options.target!;
options.environmentDefines =
target.updateEnvironmentDefines(args.environmentDefines);
CompilerResult? compilerResult;
final fromDillFile = args.fromDillFile;
if (fromDillFile != null) {
compilerResult =
await loadKernel(options.fileSystem, resolveInputUri(fromDillFile));
} else {
compilerResult = args.requireMain
? await kernelForProgram(args.source!, options,
additionalSources: args.additionalSources)
: await kernelForModule(
[args.source!, ...args.additionalSources], options);
}
final Component? component = compilerResult?.component;
// TODO(https://dartbug.com/55246): track macro deps when available.
Iterable<Uri>? compiledSources = component?.uriToSource.keys
.where((uri) => !macros.isMacroLibraryUri(uri));
Set<Library> loadedLibraries = createLoadedLibrariesSet(
compilerResult?.loadedComponents, compilerResult?.sdkComponent,
includePlatform: args.includePlatform);
if (args.deleteToStringPackageUris.isNotEmpty && component != null) {
to_string_transformer.transformComponent(
component, args.deleteToStringPackageUris);
}
// Run global transformations only if component is correct.
if ((args.aot || args.minimalKernel) && component != null) {
await runGlobalTransformations(target, component, errorDetector, args);
if (args.minimalKernel) {
// compiledSources is component.uriToSource.keys.
// Make a copy of compiledSources to detach it from
// component.uriToSource which is cleared below.
compiledSources = compiledSources!.toList();
component.metadata.clear();
component.uriToSource.clear();
}
}
// Restore error handler (in case 'options' are reused).
options.onDiagnostic = errorDetector.previousErrorHandler;
return KernelCompilationResults.named(
component: component,
nativeAssetsLibrary: nativeAssetsLibrary,
loadedLibraries: loadedLibraries,
classHierarchy: compilerResult?.classHierarchy,
coreTypes: compilerResult?.coreTypes,
compiledSources: compiledSources,
);
}
Set<Library> createLoadedLibrariesSet(
List<Component>? loadedComponents, Component? sdkComponent,
{bool includePlatform = false}) {
final Set<Library> loadedLibraries = {};
if (loadedComponents != null) {
for (Component c in loadedComponents) {
for (Library lib in c.libraries) {
loadedLibraries.add(lib);
}
}
}
if (sdkComponent != null) {
if (includePlatform) {
for (Library lib in sdkComponent.libraries) {
loadedLibraries.remove(lib);
}
} else {
for (Library lib in sdkComponent.libraries) {
loadedLibraries.add(lib);
}
}
}
return loadedLibraries;
}
Future runGlobalTransformations(Target target, Component component,
ErrorDetector errorDetector, KernelCompilationArguments args) async {
assert(!target.flags.supportMirrors);
if (errorDetector.hasCompilationErrors) return;
final coreTypes = new CoreTypes(component);
final dynamicInterface = args.dynamicInterface;
if (dynamicInterface != null) {
final fileUri = await asFileUri(args.options!.fileSystem, dynamicInterface);
dynamic_interface_annotator.annotateComponent(
File(fileUri.toFilePath()).readAsStringSync(),
dynamicInterface,
component,
coreTypes);
}
// TODO(alexmarkov,cstefantsova): Consider doing canonicalization of
// identical mixin applications when creating mixin applications in frontend,
// so all backends (and all transformation passes from the very beginning)
// can benefit from mixin de-duplication.
// At least, in addition to VM/AOT case we should run this transformation
// when building a platform dill file for VM/JIT case.
mixin_deduplication.transformComponent(component);
// Perform unreachable code elimination, which should be performed before
// type flow analysis so TFA won't take unreachable code into account.
final targetOS = args.targetOS;
final os = targetOS != null ? TargetOS.fromString(targetOS)! : null;
final evaluator = vm_constant_evaluator.VMConstantEvaluator.create(
target, component, os,
enableAsserts: args.enableAsserts,
environmentDefines: args.environmentDefines,
coreTypes: coreTypes);
unreachable_code_elimination.transformComponent(
target, component, evaluator, args.enableAsserts);
if (args.useGlobalTypeFlowAnalysis) {
globalTypeFlow.transformComponent(target, coreTypes, component,
treeShakeSignatures: !args.minimalKernel,
treeShakeWriteOnlyFields: args.treeShakeWriteOnlyFields,
treeShakeProtobufs: args.useProtobufTreeShakerV2,
useRapidTypeAnalysis: args.useRapidTypeAnalysis);
} else {
devirtualization.transformComponent(coreTypes, component);
no_dynamic_invocations_annotator.transformComponent(component);
}
// TODO(35069): avoid recomputing CSA by reading it from the platform files.
void ignoreAmbiguousSupertypes(cls, a, b) {}
final hierarchy = new ClassHierarchy(component, coreTypes,
onAmbiguousSupertypes: ignoreAmbiguousSupertypes);
call_site_annotator.transformLibraries(
component, component.libraries, coreTypes, hierarchy);
// We don't know yet whether gen_snapshot will want to do obfuscation, but if
// it does it will need the obfuscation prohibitions.
obfuscationProhibitions.transformComponent(
component, coreTypes, target, hierarchy, args.keepClassNamesImplementing);
deferred_loading.transformComponent(component, coreTypes, target);
final recordedUsagesFile = args.recordedUsages;
if (recordedUsagesFile != null) {
assert(args.source != null);
record_use.transformComponent(component, recordedUsagesFile, args.source!);
}
}
/// Runs given [action] with [CompilerContext]. This is needed to
/// be able to report compile-time errors.
Future<T> runWithFrontEndCompilerContext<T>(
Uri source,
CompilerOptions compilerOptions,
Component component,
Future<T> action()) async {
final processedOptions =
new ProcessedOptions(options: compilerOptions, inputs: [source]);
// Run within the context, so we have uri source tokens...
return await CompilerContext.runWithOptions(processedOptions,
(CompilerContext context) async {
// To make the fileUri/fileOffset -> line/column mapping, we need to
// pre-fill the map.
context.uriToSource.addAll(component.uriToSource);
return action();
});
}
class ErrorDetector {
final DiagnosticMessageHandler? previousErrorHandler;
bool hasCompilationErrors = false;
ErrorDetector({this.previousErrorHandler});
void call(DiagnosticMessage message) {
if (message.severity == Severity.error) {
hasCompilationErrors = true;
}
previousErrorHandler?.call(message);
}
}
class ErrorPrinter {
final Verbosity verbosity;
final DiagnosticMessageHandler? previousErrorHandler;
final Map<Uri?, List<DiagnosticMessage>> compilationMessages =
<Uri?, List<DiagnosticMessage>>{};
ErrorPrinter(this.verbosity, {this.previousErrorHandler});
void call(DiagnosticMessage message) {
final sourceUri = getMessageUri(message);
(compilationMessages[sourceUri] ??= <DiagnosticMessage>[]).add(message);
previousErrorHandler?.call(message);
}
void printCompilationMessages() {
final sortedUris = compilationMessages.keys.toList()
..sort((a, b) {
// Sort messages without a corresponding uri before the location based
// messages, since these related to the whole compilation.
if (a != null && b != null) {
return '$a'.compareTo('$b');
} else if (a != null) {
return 1;
} else if (b != null) {
return -1;
}
return 0;
});
for (final Uri? sourceUri in sortedUris) {
for (final DiagnosticMessage message in compilationMessages[sourceUri]!) {
if (Verbosity.shouldPrint(verbosity, message)) {
printDiagnosticMessage(message, print);
}
}
}
}
}
bool parseCommandLineDefines(
List<String> dFlags, Map<String, String> environmentDefines, String usage) {
for (final String dflag in dFlags) {
final equalsSignIndex = dflag.indexOf('=');
if (equalsSignIndex < 0) {
// Ignored.
} else if (equalsSignIndex > 0) {
final key = dflag.substring(0, equalsSignIndex);
final value = dflag.substring(equalsSignIndex + 1);
environmentDefines[key] = value;
} else {
print('The environment constant options must have a key (was: "$dflag")');
print(usage);
return false;
}
}
return true;
}
/// Create front-end target with given name.
Target? createFrontEndTarget(String targetName,
{bool trackWidgetCreation = false, bool supportMirrors = true}) {
// Make sure VM-specific targets are available.
installAdditionalTargets();
final TargetFlags targetFlags = new TargetFlags(
trackWidgetCreation: trackWidgetCreation, supportMirrors: supportMirrors);
return getTarget(targetName, targetFlags);
}
/// Create a front-end file system.
///
/// If requested, create a virtual multi-root file system and/or an http aware
/// file system.
FileSystem createFrontEndFileSystem(
String? multiRootFileSystemScheme, List<String>? multiRootFileSystemRoots,
{bool allowHttp = false}) {
FileSystem fileSystem = StandardFileSystem.instance;
if (allowHttp) {
fileSystem = HttpAwareFileSystem(fileSystem);
}
if (multiRootFileSystemRoots != null &&
multiRootFileSystemRoots.isNotEmpty &&
multiRootFileSystemScheme != null) {
final rootUris = <Uri>[];
for (String root in multiRootFileSystemRoots) {
rootUris.add(resolveInputUri(root));
}
fileSystem = new MultiRootFileSystem(
multiRootFileSystemScheme, rootUris, fileSystem);
}
return fileSystem;
}
/// Convert a URI which may use virtual file system schema to a real file URI.
Future<Uri> asFileUri(FileSystem fileSystem, Uri uri) async {
FileSystemEntity fse = fileSystem.entityForUri(uri);
if (fse is MultiRootFileSystemEntity) {
fse = await fse.delegate;
}
return fse.uri;
}
/// Convert URI to a package URI if it is inside one of the packages.
/// TODO(alexmarkov) Remove this conversion after Fuchsia build rules are fixed.
Future<Uri> convertToPackageUri(
FileSystem fileSystem, Uri uri, Uri packagesUri) async {
if (uri.scheme == 'package') {
return uri;
}
// Convert virtual URI to a real file URI.
final Uri fileUri = await asFileUri(fileSystem, uri);
try {
final packageConfig =
await loadPackageConfigUri(await asFileUri(fileSystem, packagesUri));
return packageConfig.toPackageUri(fileUri) ?? uri;
} catch (_) {
// Can't read packages file - silently give up.
return uri;
}
}
/// Write a separate kernel binary for each package. The name of the
/// output kernel binary is '[outputFileName]-$package.dilp'.
/// The list of package names is written into a file '[outputFileName]-packages'.
Future writeOutputSplitByPackages(Uri source, CompilerOptions compilerOptions,
KernelCompilationResults compilationResults, String outputFileName) async {
final packages = <String>[];
final Component component = compilationResults.component!;
await runWithFrontEndCompilerContext(source, compilerOptions, component,
() async {
// When loading a kernel file list, flutter_runner and dart_runner expect
// 'main' to be last.
await forEachPackage(compilationResults,
(String package, List<Library> libraries) async {
packages.add(package);
final String filename = '$outputFileName-$package.dilp';
final IOSink sink = new File(filename).openWrite();
final BinaryPrinter printer = new BinaryPrinter(sink,
libraryFilter: (lib) =>
packageFor(lib, compilationResults.loadedLibraries) == package);
printer.writeComponentFile(component);
await sink.close();
}, mainFirst: false);
});
final IOSink packagesList = new File('$outputFileName-packages').openWrite();
for (String package in packages) {
packagesList.writeln(package);
}
await packagesList.close();
}
String? packageFor(Library lib, Set<Library> loadedLibraries) {
// Core libraries are not written into any package kernel binaries.
if (loadedLibraries.contains(lib)) return null;
// Packages are written into their own kernel binaries.
Uri uri = lib.importUri;
if (uri.scheme == 'package') return uri.pathSegments.first;
// Everything else (e.g., file: or data: imports) is lumped into the main
// kernel binary.
return 'main';
}
/// Sort the libraries etc in the component. Helps packages to produce identical
/// output when their parts are imported in different orders in different
/// contexts.
void sortComponent(Component component) {
component.libraries.sort((Library a, Library b) {
return a.importUri.toString().compareTo(b.importUri.toString());
});
component.computeCanonicalNames();
for (Library lib in component.libraries) {
lib.additionalExports.sort();
}
}
Future<void> forEachPackage(KernelCompilationResults results,
Future<void> action(String package, List<Library> libraries),
{required bool mainFirst}) async {
final Component component = results.component!;
final Set<Library> loadedLibraries = results.loadedLibraries;
sortComponent(component);
final Map<String, List<Library>> packages = <String, List<Library>>{};
packages['main'] = <Library>[]; // Always create 'main'.
for (Library lib in component.libraries) {
final String? package = packageFor(lib, loadedLibraries);
// Ignore external libraries.
if (package == null) {
continue;
}
packages.putIfAbsent(package, () => <Library>[]).add(lib);
}
final mainLibraries = packages.remove('main')!;
if (mainFirst) {
await action('main', mainLibraries);
}
final mainMethod = component.mainMethod;
final problemsAsJson = component.problemsAsJson;
final compilationMode = component.mode;
component.setMainMethodAndMode(null, true, compilationMode);
component.problemsAsJson = null;
for (String package in packages.keys) {
await action(package, packages[package]!);
}
component.setMainMethodAndMode(mainMethod?.reference, true, compilationMode);
component.problemsAsJson = problemsAsJson;
if (!mainFirst) {
await action('main', mainLibraries);
}
}
String _escapePath(String path) {
return path.replaceAll('\\', '\\\\').replaceAll(' ', '\\ ');
}
/// Create ninja dependencies file, as described in
/// https://ninja-build.org/manual.html#_depfile
Future<void> writeDepfile(FileSystem fileSystem, Iterable<Uri> compiledSources,
String output, String depfile) async {
final IOSink file = new File(depfile).openWrite();
file.write(_escapePath(output));
file.write(':');
// TODO(https://dartbug.com/55246): track macro deps when available.
for (Uri dep in compiledSources) {
// Skip corelib dependencies.
if (dep.scheme == 'org-dartlang-sdk') continue;
Uri uri = await asFileUri(fileSystem, dep);
file.write(' ');
file.write(_escapePath(uri.toFilePath()));
}
file.write('\n');
await file.close();
}
Future<void> createFarManifest(
String output, String? dataDir, String packageManifestFilename) async {
List<String> packages = await File('$output-packages').readAsLines();
// Make sure the 'main' package is the last (convention with package loader).
packages.remove('main');
packages.add('main');
final IOSink packageManifest = File(packageManifestFilename).openWrite();
final String kernelListFilename = '$packageManifestFilename.dilplist';
final IOSink kernelList = File(kernelListFilename).openWrite();
for (String package in packages) {
final String filenameInPackage = '$package.dilp';
final String filenameInBuild = '$output-$package.dilp';
packageManifest
.write('data/$dataDir/$filenameInPackage=$filenameInBuild\n');
kernelList.write('$filenameInPackage\n');
}
await kernelList.close();
final String frameworkVersionFilename =
'$packageManifestFilename.frameworkversion';
final IOSink frameworkVersion = File(frameworkVersionFilename).openWrite();
for (String package in [
'collection',
'flutter',
'meta',
'typed_data',
'vector_math'
]) {
Digest? digest;
if (packages.contains(package)) {
final filenameInBuild = '$output-$package.dilp';
final bytes = await File(filenameInBuild).readAsBytes();
digest = sha256.convert(bytes);
}
frameworkVersion.write('$package=$digest\n');
}
await frameworkVersion.close();
packageManifest.write('data/$dataDir/app.dilplist=$kernelListFilename\n');
packageManifest
.write('data/$dataDir/app.frameworkversion=$frameworkVersionFilename\n');
await packageManifest.close();
}
class CompilerResultLoadedFromKernel implements CompilerResult {
final Component component;
final Component sdkComponent = Component();
CompilerResultLoadedFromKernel(this.component);
@override
List<int>? get summary => null;
@override
List<Component> get loadedComponents => const <Component>[];
@override
CoreTypes? get coreTypes => null;
@override
ClassHierarchy? get classHierarchy => null;
}
Future<CompilerResult> loadKernel(
FileSystem fileSystem, Uri dillFileUri) async {
final component = loadComponentFromBinary(
(await asFileUri(fileSystem, dillFileUri)).toFilePath());
return CompilerResultLoadedFromKernel(component);
}
// Used by kernel_front_end_test.dart
main() {}