| // 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)); |
| } |
| } |