blob: a017336a8f3b4f693a9ac3a5ddc3b5ed150469c3 [file] [log] [blame]
// Copyright (c) 2024, 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';
import 'dart:io' as io show exitCode, File, IOSink;
import 'package:args/args.dart' show ArgParser, ArgResults;
import 'package:front_end/src/api_unstable/vm.dart'
show
CompilerOptions,
InvocationMode,
DiagnosticMessage,
Verbosity,
parseExperimentalArguments,
parseExperimentalFlags,
resolveInputUri;
import 'package:kernel/ast.dart' show Component;
import 'package:vm/kernel_front_end.dart'
show
badUsageExitCode,
compileTimeErrorExitCode,
compileToKernel,
convertToPackageUri,
createFrontEndFileSystem,
createFrontEndTarget,
ErrorDetector,
ErrorPrinter,
KernelCompilationArguments,
parseCommandLineDefines,
successExitCode,
writeDepfile;
import 'bytecode_serialization.dart' show BytecodeSizeStatistics;
import 'bytecode_generator.dart' show generateBytecode;
import 'options.dart' show BytecodeOptions;
final ArgParser _argParser = ArgParser(allowTrailingOptions: true)
..addOption('platform',
help: 'Path to vm_platform.dill file', defaultsTo: null)
..addOption('packages',
help: 'Path to .dart_tool/package_config.json file', defaultsTo: null)
..addOption('output',
abbr: 'o', help: 'Path to resulting bytecode file', defaultsTo: null)
..addOption('depfile', help: 'Path to output Ninja depfile')
..addOption(
'depfile-target',
help: 'Override the target in the generated depfile',
hide: true,
)
..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.')
..addOption('filesystem-scheme',
help: 'The URI scheme for the multi-root virtual filesystem.')
..addOption('target',
help: 'Target model that determines what core libraries are available',
allowed: <String>['vm', 'flutter', 'flutter_runner', 'dart_runner'],
defaultsTo: 'vm')
..addMultiOption('define',
abbr: 'D',
help: 'The values for the environment constants (e.g. -Dkey=value).')
..addOption('import-dill',
help: 'Import libraries from existing dill file', defaultsTo: null)
..addFlag('enable-asserts',
help: 'Whether asserts will be enabled.', defaultsTo: false)
..addMultiOption('bytecode-options',
help: 'Specify options for bytecode generation:',
valueHelp: 'opt1,opt2,...',
allowed: BytecodeOptions.commandLineFlags.keys,
allowedHelp: BytecodeOptions.commandLineFlags)
..addMultiOption('enable-experiment',
help: 'Comma separated list of experimental features to enable.')
..addFlag('help',
abbr: 'h', negatable: false, help: 'Print this help message.')
..addFlag('track-widget-creation',
help: 'Run a kernel transformer to track creation locations for widgets.',
defaultsTo: false)
..addOption('invocation-modes',
help: 'Provides information to the front end about how it is invoked.',
defaultsTo: '')
..addOption('validate',
help:
'Validate dynamic module against specified dynamic interface YAML file',
defaultsTo: null)
..addOption('verbosity',
help: 'Sets the verbosity level used for filtering messages during '
'compilation.',
defaultsTo: Verbosity.defaultValue);
final String _usage = '''
Usage: dart2bytecode --platform vm_platform.dill [--import-dill host_app.dill] [--validate dynamic_interface.yaml] [options] input.dart
Compiles Dart sources to Dart bytecode.
Options:
${_argParser.usage}
''';
Future<void> main(List<String> arguments) async {
io.exitCode = await runCompilerWithCommandLineArguments(arguments);
}
/// Run bytecode compiler tool with given [arguments]
/// and return exit code (0 on success, non-zero on failure).
Future<int> runCompilerWithCommandLineArguments(List<String> arguments) async {
final ArgResults options = _argParser.parse(arguments);
final String? platformKernel = options['platform'];
if (options['help']) {
print(_usage);
return successExitCode;
}
final String? input = options.rest.singleOrNull;
if (input == null || platformKernel == null) {
print(_usage);
return badUsageExitCode;
}
final String outputFileName = options['output'] ?? "$input.bytecode";
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 List<String>? fileSystemRoots = options['filesystem-root'];
final bool enableAsserts = options['enable-asserts'];
final List<String>? experimentalFlags = options['enable-experiment'];
final Map<String, String> environmentDefines = {};
if (!parseCommandLineDefines(options['define'], environmentDefines, _usage)) {
return badUsageExitCode;
}
final String? importDill = options['import-dill'];
final String? validateDynamicInterface = options['validate'];
final String messageVerbosity = options['verbosity'];
final String cfeInvocationModes = options['invocation-modes'];
final bool trackWidgetCreation = options['track-widget-creation'];
final List<String>? bytecodeGeneratorOptions = options['bytecode-options'];
return await runCompilerWithOptions(
input: input,
platformKernel: platformKernel,
outputFileName: outputFileName,
targetName: targetName,
packages: packages,
importDill: importDill,
validateDynamicInterface: validateDynamicInterface,
enableAsserts: enableAsserts,
experimentalFlags: experimentalFlags,
environmentDefines: environmentDefines,
fileSystemScheme: fileSystemScheme,
fileSystemRoots: fileSystemRoots,
messageVerbosity: messageVerbosity,
cfeInvocationModes: cfeInvocationModes,
trackWidgetCreation: trackWidgetCreation,
bytecodeGeneratorOptions: bytecodeGeneratorOptions,
depfile: depfile,
depfileTarget: depfileTarget,
);
}
/// Run bytecode compiler tool with given options
/// and return exit code (0 on success, non-zero on failure).
Future<int> runCompilerWithOptions({
required String input,
required String platformKernel,
required String outputFileName,
required String targetName,
String? packages,
String? importDill,
String? validateDynamicInterface,
bool enableAsserts = false,
List<String>? experimentalFlags,
Map<String, String> environmentDefines = const {},
String? fileSystemScheme,
List<String>? fileSystemRoots,
String messageVerbosity = Verbosity.defaultValue,
void Function(String) printMessage = print,
String cfeInvocationModes = '',
bool trackWidgetCreation = false,
List<String>? bytecodeGeneratorOptions,
String? depfile,
String? depfileTarget,
}) async {
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 (importDill != null) {
additionalDills.add(Uri.base.resolveUri(new Uri.file(importDill)));
}
final Uri? dynamicInterfaceSpecificationUri =
(validateDynamicInterface != null)
? resolveInputUri(validateDynamicInterface)
: null;
final verbosity = Verbosity.parseArgument(messageVerbosity);
final errorPrinter = ErrorPrinter(verbosity, println: printMessage);
final errorDetector = ErrorDetector(previousErrorHandler: errorPrinter.call);
Uri mainUri = resolveInputUri(input);
if (packagesUri != null) {
mainUri = await convertToPackageUri(fileSystem, mainUri, packagesUri);
}
final CompilerOptions compilerOptions = CompilerOptions()
..sdkSummary = platformKernelUri
..fileSystem = fileSystem
..additionalDills = additionalDills
..packagesFileUri = packagesUri
..dynamicInterfaceSpecificationUri = dynamicInterfaceSpecificationUri
..explicitExperimentalFlags = parseExperimentalFlags(
parseExperimentalArguments(experimentalFlags),
onError: printMessage)
..onDiagnostic = (DiagnosticMessage m) {
errorDetector(m);
}
..embedSourceText = false
..invocationModes = InvocationMode.parseArguments(cfeInvocationModes)
..verbosity = verbosity;
compilerOptions.target = createFrontEndTarget(targetName,
trackWidgetCreation: trackWidgetCreation, supportMirrors: false);
if (compilerOptions.target == null) {
printMessage('Failed to create front-end target $targetName.');
return badUsageExitCode;
}
final results = await compileToKernel(KernelCompilationArguments(
source: mainUri,
options: compilerOptions,
requireMain: false,
includePlatform: false,
environmentDefines: Map.of(environmentDefines),
enableAsserts: enableAsserts));
errorPrinter.printCompilationMessages();
final Component? component = results.component;
if (errorDetector.hasCompilationErrors || component == null) {
return compileTimeErrorExitCode;
}
final BytecodeOptions bytecodeOptions =
BytecodeOptions(enableAsserts: enableAsserts)
..parseCommandLineFlags(bytecodeGeneratorOptions);
if (bytecodeOptions.showBytecodeSizeStatistics) {
BytecodeSizeStatistics.reset();
}
final io.IOSink sink = io.File(outputFileName).openWrite();
generateBytecode(component, sink,
libraries: component.libraries
.where((lib) => !results.loadedLibraries.contains(lib))
.toList(),
hierarchy: results.classHierarchy!,
coreTypes: results.coreTypes!,
options: bytecodeOptions,
target: compilerOptions.target!);
await sink.close();
if (bytecodeOptions.showBytecodeSizeStatistics) {
BytecodeSizeStatistics.dump();
}
if (depfile != null) {
await writeDepfile(
fileSystem,
results.compiledSources!,
depfileTarget ?? outputFileName,
depfile,
);
}
return successExitCode;
}