blob: d983cbef6f821efbb571d2b4ee199ed16f36e15b [file] [log] [blame]
// Copyright (c) 2019, 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';
import 'package:args/args.dart';
import 'package:code_assets/code_assets.dart' show Architecture, OS;
import 'package:dart2native/generate.dart';
import 'package:dartdev/src/unified_analytics.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart'
show Verbosity;
import 'package:hooks_runner/hooks_runner.dart' show Target;
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:vm/target_os.dart';
import '../core.dart';
import '../experiments.dart';
import '../native_assets.dart';
import '../sdk.dart';
import '../sdk_cache.dart';
import '../utils.dart';
import '../vm_interop_handler.dart';
const int genericErrorExitCode = 255;
const int compileErrorExitCode = 254;
class Option {
final String flag;
final String help;
final String? abbr;
final String? defaultsTo;
final bool? flagDefaultsTo;
final String? valueHelp;
final List<String>? allowed;
final Map<String, String>? allowedHelp;
const Option({
required this.flag,
required this.help,
this.abbr,
this.defaultsTo,
this.flagDefaultsTo,
this.valueHelp,
this.allowed,
this.allowedHelp,
});
}
bool checkFile(String sourcePath) {
if (!FileSystemEntity.isFileSync(sourcePath)) {
stderr.writeln('"$sourcePath" file not found.');
stderr.flush();
return false;
}
return true;
}
/// Checks to see if [destPath] is a file path that can be written to.
bool checkFileWriteable(String destPath) {
final file = File(destPath);
final exists = file.existsSync();
try {
file.writeAsStringSync(
'',
mode: FileMode.append,
flush: true,
);
// Don't leave empty files around.
if (!exists) {
file.deleteSync();
}
return true;
} on FileSystemException {
return false;
}
}
class CompileJSCommand extends CompileSubcommandCommand {
static const String cmdName = 'js';
/// Accept all flags so we can delegate arg parsing to dart2js internally.
@override
final ArgParser argParser = ArgParser.allowAnything();
CompileJSCommand({bool verbose = false})
: super(cmdName, 'Compile Dart to JavaScript.', verbose);
@override
String get invocation => '${super.invocation} <dart entry point>';
@override
FutureOr<int> run() async {
if (!Sdk.checkArtifactExists(sdk.librariesJson)) {
return genericErrorExitCode;
}
final args = argResults!;
var snapshot = sdk.dart2jsAotSnapshot;
var script = sdk.dartAotRuntime;
var useExecProcess = true;
if (!Sdk.checkArtifactExists(snapshot, logError: false)) {
// AOT snapshots cannot be generated on IA32, so we need this fallback
// branch until support for IA32 is dropped (https://dartbug.com/49969).
script = sdk.dart2jsSnapshot;
if (!Sdk.checkArtifactExists(script)) {
return genericErrorExitCode;
}
useExecProcess = false;
}
final dart2jsCommand = [
if (useExecProcess) snapshot,
'--libraries-spec=${sdk.librariesJson}',
'--cfe-invocation-modes=compile',
'--invoker=dart_cli',
// Add the remaining arguments.
if (args.rest.isNotEmpty) ...args.rest.sublist(0),
];
try {
VmInteropHandler.run(
script,
dart2jsCommand,
packageConfigOverride: null,
useExecProcess: useExecProcess,
);
return 0;
} catch (e, st) {
log.stderr('Error: JS compilation failed');
log.stderr(e.toString());
if (verbose) {
log.stderr(st.toString());
}
return compileErrorExitCode;
}
}
}
class CompileDDCCommand extends CompileSubcommandCommand {
static const String cmdName = 'js-dev';
/// Accept all flags so we can delegate arg parsing to ddc internally.
@override
final ArgParser argParser = ArgParser.allowAnything();
// This command is an internal developer command used by tools and is
// hidden in the help message.
CompileDDCCommand({bool verbose = false})
: super(
cmdName,
'Compile Dart to JavaScript using ddc.',
verbose,
hidden: true,
);
@override
String get invocation => '${super.invocation} <dart entry point>';
@override
FutureOr<int> run() async {
if (!Sdk.checkArtifactExists(sdk.librariesJson)) {
return genericErrorExitCode;
}
final args = argResults!;
var snapshot = sdk.ddcAotSnapshot;
var script = sdk.dartAotRuntime;
var useExecProcess = true;
if (!Sdk.checkArtifactExists(snapshot, logError: false)) {
// AOT snapshots cannot be generated on IA32, so we need this fallback
// branch until support for IA32 is dropped (https://dartbug.com/49969).
script = sdk.ddcSnapshot;
if (!Sdk.checkArtifactExists(script)) {
return genericErrorExitCode;
}
useExecProcess = false;
}
final ddcCommand = <String>[
if (useExecProcess) snapshot,
// Add the remaining arguments.
if (args.rest.isNotEmpty) ...args.rest.sublist(0),
];
try {
VmInteropHandler.run(
script,
ddcCommand,
packageConfigOverride: null,
useExecProcess: useExecProcess,
);
return 0;
} catch (e, st) {
log.stderr('Error: JS compilation failed');
log.stderr(e.toString());
if (verbose) {
log.stderr(st.toString());
}
return compileErrorExitCode;
}
}
}
class CompileKernelSnapshotCommand extends CompileSubcommandCommand {
static const commandName = 'kernel';
static const help = 'Compile Dart to a kernel snapshot.\n'
'To run the snapshot use: dart run <kernel file>';
CompileKernelSnapshotCommand({
bool verbose = false,
}) : super(commandName, help, verbose) {
argParser
..addOption(
outputFileOption.flag,
help: outputFileOption.help,
abbr: outputFileOption.abbr,
)
..addOption(
verbosityOption.flag,
help: verbosityOption.help,
abbr: verbosityOption.abbr,
defaultsTo: verbosityOption.defaultsTo,
allowed: verbosityOption.allowed,
allowedHelp: verbosityOption.allowedHelp,
)
..addOption(
packagesOption.flag,
abbr: packagesOption.abbr,
valueHelp: packagesOption.valueHelp,
help: packagesOption.help,
)
..addFlag(
'link-platform',
help: 'Includes the platform kernel in the resulting kernel file. '
"Required for use with 'dart compile exe' or 'dart compile aot-snapshot'.",
defaultsTo: true,
)
..addFlag(
'embed-sources',
help: 'Embed source files in the generated kernel component.',
defaultsTo: true,
)
..addMultiOption(
defineOption.flag,
help: defineOption.help,
abbr: defineOption.abbr,
valueHelp: defineOption.valueHelp,
)
..addFlag(
soundNullSafetyOption.flag,
help: soundNullSafetyOption.help,
defaultsTo: soundNullSafetyOption.flagDefaultsTo,
hide: true,
)
..addOption(
'depfile',
valueHelp: 'path',
help: 'Path to output Ninja depfile',
)
..addMultiOption(
'extra-gen-kernel-options',
help: 'Pass additional options to gen_kernel.',
hide: true,
valueHelp: 'opt1,opt2,...',
)
..addExperimentalFlags(verbose: verbose);
}
@override
FutureOr<int> run() async {
final args = argResults!;
if (args.rest.isEmpty) {
// This throws.
usageException('Missing Dart entry point.');
} else if (args.rest.length > 1) {
usageException('Unexpected arguments after Dart entry point.');
}
final String sourcePath = args.rest[0];
if (!checkFile(sourcePath)) {
return genericErrorExitCode;
}
// Determine output file name.
String? outputFile = args.option(outputFileOption.flag);
if (outputFile == null) {
final inputWithoutDart = sourcePath.endsWith('.dart')
? sourcePath.substring(0, sourcePath.length - 5)
: sourcePath;
outputFile = '$inputWithoutDart.dill';
}
log.stdout('Compiling $sourcePath to kernel file $outputFile.');
if (!checkFileWriteable(outputFile)) {
log.stderr('Unable to open file $outputFile for writing snapshot.');
return genericErrorExitCode;
}
final bool soundNullSafety = args.flag('sound-null-safety');
if (!soundNullSafety) {
log.stdout(
'Error: the flag --no-sound-null-safety is not supported in Dart 3.');
return compileErrorExitCode;
}
try {
await generateKernel(
sourceFile: sourcePath,
outputFile: outputFile,
defines: args.multiOption('define'),
packages: args.option('packages'),
enableExperiment: args.enabledExperiments.join(','),
linkPlatform: args.flag('link-platform'),
depFile: args.option('depfile'),
extraOptions: args.multiOption('extra-gen-kernel-options'),
embedSources: args.flag('embed-sources'),
verbose: verbose,
verbosity: args.option('verbosity')!,
);
return 0;
} catch (e, st) {
log.stderr(e.toString());
if (verbose) {
log.stderr(st.toString());
}
return compileErrorExitCode;
}
}
}
class CompileJitSnapshotCommand extends CompileSubcommandCommand {
static const help = 'Compile Dart to a JIT snapshot.\n'
'The executable will be run once to snapshot a warm JIT.\n'
'To run the snapshot use: dart run <JIT file>';
CompileJitSnapshotCommand({
bool verbose = false,
}) : super('jit-snapshot', help, verbose) {
argParser
..addOption(
outputFileOption.flag,
help: outputFileOption.help,
abbr: outputFileOption.abbr,
)
..addOption(
verbosityOption.flag,
help: verbosityOption.help,
abbr: verbosityOption.abbr,
defaultsTo: verbosityOption.defaultsTo,
allowed: verbosityOption.allowed,
allowedHelp: verbosityOption.allowedHelp,
)
..addOption(
packagesOption.flag,
abbr: packagesOption.abbr,
valueHelp: packagesOption.valueHelp,
help: packagesOption.help,
)
..addMultiOption(
defineOption.flag,
help: defineOption.help,
abbr: defineOption.abbr,
valueHelp: defineOption.valueHelp,
)
..addFlag(
enableAssertsOption.flag,
negatable: false,
help: enableAssertsOption.help,
)
..addFlag(soundNullSafetyOption.flag,
help: soundNullSafetyOption.help,
defaultsTo: soundNullSafetyOption.flagDefaultsTo,
hide: true)
..addExperimentalFlags(verbose: verbose);
}
@override
String get invocation =>
'${super.invocation} <dart entry point> [<training arguments>]';
@override
ArgParser createArgParser() {
return ArgParser(
// Don't parse the training arguments for JIT snapshots.
allowTrailingOptions: false,
usageLineLength: dartdevUsageLineLength,
);
}
@override
FutureOr<int> run() async {
final args = argResults!;
if (args.rest.isEmpty) {
// This throws.
usageException('Missing Dart entry point.');
}
final String sourcePath = args.rest[0];
if (!checkFile(sourcePath)) {
return genericErrorExitCode;
}
// Determine output file name.
String? outputFile = args.option(outputFileOption.flag);
if (outputFile == null) {
final inputWithoutDart = sourcePath.endsWith('.dart')
? sourcePath.substring(0, sourcePath.length - 5)
: sourcePath;
outputFile = '$inputWithoutDart.jit';
}
if (!checkFileWriteable(outputFile)) {
log.stderr('Unable to open file $outputFile for writing snapshot.');
return genericErrorExitCode;
}
final enabledExperiments = args.enabledExperiments;
final defines = args.multiOption(defineOption.flag);
final enableAsserts = args.flag(enableAssertsOption.flag);
// Build arguments.
final buildArgs = <String>[];
buildArgs.add('--snapshot-kind=app-jit');
buildArgs.add('--snapshot=${path.canonicalize(outputFile)}');
final bool soundNullSafety = args.flag('sound-null-safety');
if (!soundNullSafety) {
log.stdout(
'Error: the flag --no-sound-null-safety is not supported in Dart 3.');
return compileErrorExitCode;
}
final String? packages = args.option(packagesOption.flag);
if (packages != null) {
buildArgs.add('--packages=$packages');
}
final String verbosity = args.option(verbosityOption.flag)!;
buildArgs.add('--verbosity=$verbosity');
if (enabledExperiments.isNotEmpty) {
buildArgs.add("--enable-experiment=${enabledExperiments.join(',')}");
}
if (verbose) {
buildArgs.add('-v');
}
for (final define in defines) {
buildArgs.add('-D$define');
}
if (enableAsserts) {
buildArgs.add('--${enableAssertsOption.flag}');
}
buildArgs.add(path.canonicalize(sourcePath));
// Add the training arguments.
if (args.rest.length > 1) {
buildArgs.addAll(args.rest.sublist(1));
}
log.stdout('Compiling $sourcePath to jit-snapshot file $outputFile.');
// TODO(bkonyi): perform compilation in same process.
return await runProcess([sdk.dart, ...buildArgs]);
}
}
class CompileNativeCommand extends CompileSubcommandCommand {
static const String exeCmdName = 'exe';
static const String aotSnapshotCmdName = 'aot-snapshot';
static final supportedTargetPlatforms = <Target>{
Target.linuxArm64,
Target.linuxX64
};
final String commandName;
final Kind format;
final String help;
final bool nativeAssetsExperimentEnabled;
CompileNativeCommand({
required this.commandName,
required this.format,
required this.help,
bool verbose = false,
this.nativeAssetsExperimentEnabled = false,
}) : super(commandName, 'Compile Dart $help', verbose) {
argParser
..addOption(
outputFileOption.flag,
help: outputFileOption.help,
abbr: outputFileOption.abbr,
)
..addOption(
verbosityOption.flag,
help: verbosityOption.help,
abbr: verbosityOption.abbr,
defaultsTo: verbosityOption.defaultsTo,
allowed: verbosityOption.allowed,
allowedHelp: verbosityOption.allowedHelp,
)
..addMultiOption(
defineOption.flag,
help: defineOption.help,
abbr: defineOption.abbr,
valueHelp: defineOption.valueHelp,
)
..addFlag(
enableAssertsOption.flag,
negatable: false,
help: enableAssertsOption.help,
)
..addOption(
packagesOption.flag,
abbr: packagesOption.abbr,
valueHelp: packagesOption.valueHelp,
help: packagesOption.help,
)
..addFlag(soundNullSafetyOption.flag,
help: soundNullSafetyOption.help,
defaultsTo: soundNullSafetyOption.flagDefaultsTo,
hide: true)
..addOption(
'save-debugging-info',
abbr: 'S',
valueHelp: 'path',
help: '''
Remove debugging information from the output and save it separately to the specified file.
<path> can be relative or absolute.''',
)
..addOption(
'depfile',
valueHelp: 'path',
help: 'Path to output Ninja depfile',
)
..addMultiOption(
'extra-gen-snapshot-options',
help: 'Pass additional options to gen_snapshot.',
hide: true,
valueHelp: 'opt1,opt2,...',
)
..addMultiOption(
'extra-gen-kernel-options',
help: 'Pass additional options to gen_kernel.',
hide: true,
valueHelp: 'opt1,opt2,...',
)
..addOption('target-os',
help: 'Compile to a specific target operating system.',
allowed: TargetOS.names)
..addOption('target-arch',
help: 'Compile to a specific target architecture.',
allowed: Architecture.values.map((v) => v.name).toList())
..addExperimentalFlags(verbose: verbose);
}
@override
String get invocation => '${super.invocation} <dart entry point>';
@override
FutureOr<int> run() async {
// AOT compilation isn't supported on ia32. Currently, generating an
// executable only supports AOT runtimes, so these commands are disabled.
if (Platform.version.contains('ia32')) {
stderr.write(
"'dart compile $commandName' is not supported on x86 architectures.\n");
return 64;
}
if (!Sdk.checkArtifactExists(genKernel)) {
return 255;
}
final args = argResults!;
// We expect a single rest argument; the dart entry point.
if (args.rest.length != 1) {
// This throws.
usageException('Missing Dart entry point.');
}
final String sourcePath = args.rest[0];
if (!checkFile(sourcePath)) {
return genericErrorExitCode;
}
if (!args.flag('sound-null-safety')) {
log.stdout(
'Error: the flag --no-sound-null-safety is not supported in Dart 3.');
return compileErrorExitCode;
}
var genSnapshotBinary = genSnapshotHost;
var dartAotRuntimeBinary = hostDartAotRuntime;
final target = crossCompilationTarget(args);
if (target != null) {
if (!supportedTargetPlatforms.contains(target)) {
stderr.writeln('Unsupported target platform $target.');
stderr.writeln('Supported target platforms: '
'${supportedTargetPlatforms.join(', ')}');
return 128;
}
var cacheDir = getDartStorageDirectory();
if (cacheDir == null) {
cacheDir = Directory.systemTemp.createTempSync();
log.stdout('Cannot get dart storage directory. '
'Using temp dir ${cacheDir.path}');
}
final httpClient = http.Client();
try {
final cache = SdkCache(
directory: cacheDir.path, verbose: verbose, httpClient: httpClient);
final archiveFolder = await cache.resolveVersion(
version: Runtime.runtime.version,
revision: sdk.revision ?? '',
channelName: Runtime.runtime.channel ?? 'unknown');
genSnapshotBinary = await cache.ensureGenSnapshot(
archiveFolder: archiveFolder, target: target);
dartAotRuntimeBinary = await cache.ensureDartAotRuntime(
archiveFolder: archiveFolder, target: target);
} finally {
httpClient.close();
}
}
final packageConfigUri = await DartNativeAssetsBuilder.ensurePackageConfig(
Directory.current.uri,
);
if (packageConfigUri != null) {
final packageConfig =
await DartNativeAssetsBuilder.loadPackageConfig(packageConfigUri);
if (packageConfig == null) {
return compileErrorExitCode;
}
final runPackageName = await DartNativeAssetsBuilder.findRootPackageName(
Directory.current.uri,
);
if (runPackageName != null) {
final pubspecUri = await DartNativeAssetsBuilder.findWorkspacePubspec(
packageConfigUri);
final builder = DartNativeAssetsBuilder(
pubspecUri: pubspecUri,
packageConfigUri: packageConfigUri,
packageConfig: packageConfig,
runPackageName: runPackageName,
includeDevDependencies: false,
verbose: verbose,
target: target);
if (!nativeAssetsExperimentEnabled) {
if (await builder.warnOnNativeAssets()) {
return 255;
}
} else {
final assets = await builder.compileNativeAssetsJit();
if (assets == null) {
stderr.writeln('Native assets build failed.');
return 255;
}
if (assets.isNotEmpty) {
stderr.writeln(
"'dart compile' does currently not support native assets.");
return 255;
}
}
}
}
final tempDir = Directory.systemTemp.createTempSync();
try {
final kernelGenerator = KernelGenerator(
genSnapshot: genSnapshotBinary,
targetDartAotRuntime: dartAotRuntimeBinary,
kind: format,
sourceFile: sourcePath,
outputFile: args.option('output'),
defines: args.multiOption(defineOption.flag),
packages: args.option('packages'),
enableExperiment: args.enabledExperiments.join(','),
enableAsserts: args.flag(enableAssertsOption.flag),
debugFile: args.option('save-debugging-info'),
verbose: verbose,
verbosity: args.option('verbosity')!,
targetOS: target?.os ?? OS.current,
tempDir: tempDir,
depFile: args.option('depfile'),
);
final snapshotGenerator = await kernelGenerator.generate(
extraOptions: args.multiOption('extra-gen-kernel-options'),
);
await snapshotGenerator.generate(
extraOptions: args.multiOption('extra-gen-snapshot-options'),
);
return 0;
} catch (e, st) {
log.stderr('Error: AOT compilation failed');
log.stderr(e.toString());
if (verbose) {
log.stderr(st.toString());
}
return compileErrorExitCode;
} finally {
await tempDir.delete(recursive: true);
}
}
/// Returns target platform for cross compilation.
///
/// If cross compilation is not needed, returns null.
Target? crossCompilationTarget(ArgResults args) {
final String? targetOS = args.option('target-os');
final String? targetArch = args.option('target-arch');
if (targetOS == null && targetArch == null) {
return null;
}
// If one of the target options is explicitly specified,
// resolving full host and target platforms to check for
// cross compilation.
final host = Target.current;
final target = Target.fromArchitectureAndOS(
targetArch == null
? host.architecture
: Architecture.fromString(targetArch),
targetOS == null ? host.os : OS.fromString(targetOS),
);
// Platforms match, no need to cross compile.
if (host == target) {
return null;
}
return target;
}
}
class CompileWasmCommand extends CompileSubcommandCommand {
static const String commandName = 'wasm';
static const String help = 'Compile Dart to a WebAssembly/WasmGC module.';
// The unique place where we store various flags for dart2wasm & binaryen.
//
// Other uses (e.g. pkg/dart2wasm/tool/compile_benchmark) will grep in this
// file for the flags. So please keep the formatting.
final List<String> binaryenFlags = _flagList('''
--all-features
--closed-world
--traps-never-happen
--type-unfinalizing
-Os
--type-ssa
--gufa
-Os
--type-merging
-Os
--type-finalizing
--minimize-rec-groups
'''); // end of binaryenFlags
final List<String> optimizationLevel0Flags = _flagList('''
--no-inlining
--no-minify
'''); // end of optimizationLevel0Flags
final List<String> optimizationLevel1Flags = _flagList('''
--inlining
--no-minify
'''); // end of optimizationLevel1Flags
final List<String> optimizationLevel2Flags = _flagList('''
--inlining
--minify
'''); // end of optimizationLevel2Flags
final List<String> optimizationLevel3Flags = _flagList('''
--inlining
--minify
--omit-implicit-checks
'''); // end of optimizationLevel3Flags
final List<String> optimizationLevel4Flags = _flagList('''
--inlining
--minify
--omit-implicit-checks
--omit-bounds-checks
'''); // end of optimizationLevel4Flags
static List<String> _flagList(String lines) => lines
.split('\n')
.map((line) => line.trim())
.where((line) => line.isNotEmpty)
.toList();
CompileWasmCommand({bool verbose = false})
: super(commandName, help, verbose) {
argParser
..addOption(
outputFileOption.flag,
help: outputFileOption.help,
abbr: outputFileOption.abbr,
)
..addFlag(
'minify',
negatable: true,
help: 'Minify names that are needed at runtime (such as class names). '
'Affects e.g. `<obj>.runtimeType.toString()`). If passed, this '
'takes precedence over the optimization-level option.',
hide: !verbose,
)
..addFlag(
'strip-wasm',
defaultsTo: true,
negatable: true,
help:
'Whether to strip the resulting wasm file of static symbol names.',
hide: !verbose,
)
..addFlag(
'print-wasm',
help: 'Print human-readable wasm debug output',
hide: !verbose,
negatable: false,
)
..addFlag(
'print-kernel',
help: 'Print human-readable Dart kernel debug output',
hide: !verbose,
negatable: false,
)
..addFlag(
'verbose',
abbr: 'v',
help: 'Print debug output during compilation',
negatable: false,
)
..addFlag(
enableAssertsOption.flag,
negatable: false,
help: enableAssertsOption.help,
)
..addOption(
'shared-memory',
help: 'Import a shared memory buffer.'
' The max number of pages must be passed.',
valueHelp: 'page count',
hide: !verbose,
)
..addMultiOption(
'extra-compiler-option',
abbr: 'E',
help: 'An extra option to pass to the dart2wasm compiler.',
hide: !verbose,
splitCommas: false,
)
..addOption(
'optimization-level',
abbr: 'O',
help: 'Controls optimizations that can help reduce code-size and '
'improve performance of the generated code.',
allowed: ['0', '1', '2', '3', '4'],
defaultsTo: '1',
valueHelp: 'level',
allowedHelp: {
'0': optimizationLevel0Flags.join(' '),
'1': optimizationLevel1Flags.join(' '),
'2': optimizationLevel2Flags.join(' '),
'3': optimizationLevel3Flags.join(' '),
'4': optimizationLevel4Flags.join(' '),
},
hide: !verbose,
)
..addFlag(
'source-maps',
help: 'Generate a source map file.',
defaultsTo: true,
)
..addOption(
packagesOption.flag,
abbr: packagesOption.abbr,
valueHelp: packagesOption.valueHelp,
help: packagesOption.help,
hide: !verbose,
)
..addMultiOption(
defineOption.flag,
help: defineOption.help,
abbr: defineOption.abbr,
valueHelp: defineOption.valueHelp,
splitCommas: false,
)
..addExperimentalFlags(verbose: verbose);
}
@override
String get invocation => '${super.invocation} <dart entry point>';
@override
FutureOr<int> run() async {
final args = argResults!;
final verbose = this.verbose || args.flag('verbose');
if (!Sdk.checkArtifactExists(sdk.wasmPlatformDill) ||
!Sdk.checkArtifactExists(sdk.dartAotRuntime) ||
!Sdk.checkArtifactExists(sdk.dart2wasmSnapshot) ||
!Sdk.checkArtifactExists(sdk.wasmOpt)) {
return 255;
}
// We expect a single rest argument; the dart entry point.
if (args.rest.length != 1) {
// This throws.
usageException('Missing Dart entry point.');
}
final String sourcePath = args.rest[0];
final extraCompilerOptions = args.multiOption('extra-compiler-option');
final isMultiRoot =
extraCompilerOptions.any((e) => e.contains('multi-root'));
// If we know the source file doesn't exist, we want to abort early with an
// obvious error message. We can't resolve the actual path here if the input
// is an URI, so we skip that check in that case.
final sourceIsPath =
!isMultiRoot || Uri.tryParse(sourcePath)?.hasScheme == false;
if (sourceIsPath && !checkFile(sourcePath)) {
return genericErrorExitCode;
}
// Determine output file name.
String? outputFile = args.option(outputFileOption.flag);
if (outputFile == null) {
final inputWithoutDart = sourcePath.endsWith('.dart')
? sourcePath.substring(0, sourcePath.length - 5)
: sourcePath;
outputFile = '$inputWithoutDart.wasm';
}
if (!outputFile.endsWith('.wasm')) {
log.stderr(
'Error: The output file "$outputFile" does not end with ".wasm"');
return 255;
}
final outputFileBasename =
outputFile.substring(0, outputFile.length - '.wasm'.length);
final packages = args.option(packagesOption.flag);
final defines = args.multiOption(defineOption.flag);
int? maxPages;
if (args.option('shared-memory') != null) {
maxPages = int.tryParse(args.option('shared-memory')!);
if (maxPages == null) {
usageException(
'Error: The --shared-memory flag must specify a number!');
}
}
final optimizationLevel = int.parse(args.option('optimization-level')!);
final runWasmOpt = optimizationLevel >= 1;
void handleOverride(List<String> flags, String name, bool? value) {
// If no override provided, default to what -O implies.
if (value == null) return;
flags.removeWhere((option) => option == '--no-$name');
flags.removeWhere((option) => option == '--$name');
// Explicitly use the flag value, irrespective of -O settings.
value ? flags.add('--$name') : flags.add('--no-$name');
}
final optimizationFlags = (switch (optimizationLevel) {
0 => optimizationLevel0Flags,
1 => optimizationLevel1Flags,
2 => optimizationLevel2Flags,
3 => optimizationLevel3Flags,
4 => optimizationLevel4Flags,
_ => throw 'unreachable',
})
.toList();
handleOverride(optimizationFlags, 'minify',
args.wasParsed('minify') ? args.flag('minify') : null);
final generateSourceMap = args.flag('source-maps');
final enabledExperiments = args.enabledExperiments;
final dart2wasmCommand = [
sdk.dartAotRuntime,
sdk.dart2wasmSnapshot,
'--platform=${sdk.wasmPlatformDill}',
if (verbose) '--verbose',
if (packages != null) '--packages=$packages',
if (args.flag('print-wasm')) '--print-wasm',
if (args.flag('print-kernel')) '--print-kernel',
if (args.flag(enableAssertsOption.flag)) '--${enableAssertsOption.flag}',
if (!generateSourceMap) '--no-source-maps',
for (final define in defines) '-D$define',
if (maxPages != null) ...[
'--import-shared-memory',
'--shared-memory-max-pages=$maxPages',
],
...enabledExperiments.map((e) => '--enable-experiment=$e'),
// First we pass flags based on the optimization level.
...optimizationFlags,
// Then we pass any extra compiler flags through.
...extraCompilerOptions,
sourcePath,
outputFile,
];
try {
final exitCode = await runProcess(dart2wasmCommand);
if (exitCode != 0) return exitCode;
} catch (e, st) {
log.stderr('Error: Wasm compilation failed');
log.stderr(e.toString());
if (verbose) {
log.stderr(st.toString());
}
return compileErrorExitCode;
}
final bool strip = args.flag('strip-wasm');
if (runWasmOpt) {
final unoptFile = '$outputFileBasename.unopt.wasm';
File(outputFile).renameSync(unoptFile);
final unoptSourceMapFile = '$outputFileBasename.unopt.wasm.map';
if (generateSourceMap) {
File('$outputFile.map').renameSync(unoptSourceMapFile);
}
final flags = [
...binaryenFlags,
if (!strip) '-g',
if (generateSourceMap) ...[
'-ism',
unoptSourceMapFile,
'-osm',
'$outputFile.map'
]
];
if (verbose) {
log.stdout('Optimizing output with: ${sdk.wasmOpt} $flags');
}
final processResult = Process.runSync(
sdk.wasmOpt,
[...flags, '-o', outputFile, unoptFile],
);
if (processResult.exitCode != 0) {
log.stderr('Error: Wasm compilation failed while optimizing output');
log.stderr(processResult.stderr);
return compileErrorExitCode;
}
}
final mjsFile = '$outputFileBasename.mjs';
log.stdout(
"Generated wasm module '$outputFile', and JS init file '$mjsFile'.");
return 0;
}
}
abstract class CompileSubcommandCommand extends DartdevCommand {
final outputFileOption = Option(
flag: 'output',
abbr: 'o',
help: '''
Write the output to <file name>.
This can be an absolute or relative path.
''',
);
final verbosityOption = Option(
flag: 'verbosity',
help: '''
Sets the verbosity level of the compilation.
''',
defaultsTo: Verbosity.defaultValue,
allowed: Verbosity.allowedValues,
allowedHelp: Verbosity.allowedValuesHelp,
);
final soundNullSafetyOption = Option(
flag: 'sound-null-safety',
help: 'DEPRECATED: Respect the nullability of types at runtime.',
flagDefaultsTo: true,
);
late final Option defineOption = Option(
flag: 'define',
abbr: 'D',
valueHelp: 'key=value',
help: '''
Define an environment declaration. To specify multiple declarations, use multiple options or use commas to separate key-value pairs.
For example: dart compile $name -Da=1,b=2 main.dart''',
);
late final Option packagesOption = Option(
flag: 'packages',
abbr: 'p',
valueHelp: 'path',
help:
'''Get package locations from the specified file instead of .dart_tool/package_config.json.
<path> can be relative or absolute.
For example: dart compile $name --packages=/tmp/pkgs.json main.dart''');
final Option enableAssertsOption =
Option(flag: 'enable-asserts', help: 'Enable assert statements.');
CompileSubcommandCommand(super.name, super.description, super.verbose,
{super.hidden});
}
class CompileCommand extends DartdevCommand {
static const String cmdName = 'compile';
CompileCommand({
bool verbose = false,
bool nativeAssetsExperimentEnabled = false,
}) : super(cmdName, 'Compile Dart to various formats.', verbose) {
addSubcommand(CompileJSCommand(verbose: verbose));
addSubcommand(CompileDDCCommand(verbose: verbose));
addSubcommand(CompileJitSnapshotCommand(verbose: verbose));
addSubcommand(CompileKernelSnapshotCommand(verbose: verbose));
addSubcommand(CompileNativeCommand(
commandName: CompileNativeCommand.exeCmdName,
help: 'to a self-contained executable.',
format: Kind.exe,
verbose: verbose,
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
));
addSubcommand(CompileNativeCommand(
commandName: CompileNativeCommand.aotSnapshotCmdName,
help: 'to an AOT snapshot.\n'
'To run the snapshot use: dartaotruntime <AOT snapshot file>',
format: Kind.aot,
verbose: verbose,
nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled,
));
addSubcommand(CompileWasmCommand(verbose: verbose));
}
}