diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart index 96c12e8a13c2..eb3d9f66f31a 100644 --- a/pkg/dart2native/lib/dart2native.dart +++ b/pkg/dart2native/lib/dart2native.dart @@ -5,28 +5,81 @@ import 'dart:io'; import 'dart:typed_data'; +import 'package:collection/collection.dart'; +import 'package:kernel/binary/tag.dart' show Tag; +import 'package:path/path.dart' as path; + import 'dart2native_macho.dart' show writeAppendedMachOExecutable; import 'dart2native_pe.dart' show writeAppendedPortableExecutable; +final binDir = File(Platform.resolvedExecutable).parent; +final executableSuffix = Platform.isWindows ? '.exe' : ''; +final genKernel = path.join( + binDir.path, + 'snapshots', + 'gen_kernel_aot.dart.snapshot', +); +final genSnapshot = path.join( + binDir.path, + 'utils', + 'gen_snapshot$executableSuffix', +); +final platformDill = path.join( + binDir.parent.path, + 'lib', + '_internal', + 'vm_platform_strong.dill', +); +final productPlatformDill = path.join( + binDir.parent.path, + 'lib', + '_internal', + 'vm_platform_strong_product.dill', +); + // Maximum page size across all supported architectures (arm64 macOS has 16K // pages, some arm64 Linux distributions have 64K pages). const elfPageSize = 65536; const appjitMagicNumber = [0xdc, 0xdc, 0xf6, 0xf6, 0, 0, 0, 0]; +const kernelMagicNumber = [0x90, 0xab, 0xcd, 0xef]; + +Future isKernelFile(String path) async { + const kernelMagicNumber = Tag.ComponentFile; + // Convert the 32-bit header into a list of 4 bytes. + final kernelMagicNumberList = Uint8List(4) + ..buffer.asByteData().setInt32( + 0, + kernelMagicNumber, + Endian.big, + ); -enum Kind { aot, exe } + final header = await File(path) + .openRead( + 0, + kernelMagicNumberList.length, + ) + .first; -Future writeAppendedExecutable( - String dartaotruntimePath, String payloadPath, String outputPath) async { + return header.equals(kernelMagicNumberList); +} + +// WARNING: this method is used within google3, so don't try to refactor so +// [dartaotruntime] is a constant inside this file. +Future writeAppendedExecutable( + String dartaotruntime, + String payloadPath, + String outputPath, +) async { if (Platform.isMacOS) { return await writeAppendedMachOExecutable( - dartaotruntimePath, payloadPath, outputPath); + dartaotruntime, payloadPath, outputPath); } else if (Platform.isWindows) { return await writeAppendedPortableExecutable( - dartaotruntimePath, payloadPath, outputPath); + dartaotruntime, payloadPath, outputPath); } - final dartaotruntime = File(dartaotruntimePath); - final int dartaotruntimeLength = dartaotruntime.lengthSync(); + final dartaotruntimeFile = File(dartaotruntime); + final int dartaotruntimeLength = dartaotruntimeFile.lengthSync(); final padding = ((elfPageSize - dartaotruntimeLength) % elfPageSize); final padBytes = Uint8List(padding); @@ -37,7 +90,7 @@ Future writeAppendedExecutable( ..setUint64(0, offset, Endian.little); final outputFile = File(outputPath).openWrite(); - outputFile.add(dartaotruntime.readAsBytesSync()); + outputFile.add(dartaotruntimeFile.readAsBytesSync()); outputFile.add(padBytes); outputFile.add(File(payloadPath).readAsBytesSync()); outputFile.add(offsetBytes.buffer.asUint8List()); @@ -49,45 +102,49 @@ Future markExecutable(String outputFile) { return Process.run('chmod', ['+x', outputFile]); } -/// Generates the AOT kernel by running the provided [genKernel] path. +/// Generates kernel by running the provided [genKernel] path. /// /// Also takes a path to the [resourcesFile] JSON file, where the method calls /// to static functions annotated with [Resource] will be collected. -Future generateAotKernel( - String dart, - String genKernel, - String platformDill, - String sourceFile, - String kernelFile, +Future generateKernelHelper({ + required String dartaotruntime, + required String sourceFile, + required String kernelFile, String? packages, - List defines, { + List defines = const [], String enableExperiment = '', String? targetOS, List extraGenKernelOptions = const [], String? nativeAssets, String? resourcesFile, + bool fromDill = false, + bool aot = false, + bool embedSources = false, + bool linkPlatform = true, + bool product = true, }) { - return Process.run(dart, [ + final args = [ genKernel, - '--platform', - platformDill, + '--platform=${product ? productPlatformDill : platformDill}', + if (product) '-Ddart.vm.product=true', if (enableExperiment.isNotEmpty) '--enable-experiment=$enableExperiment', if (targetOS != null) '--target-os=$targetOS', - '--aot', - '-Ddart.vm.product=true', + if (fromDill) '--from-dill=$sourceFile', + if (aot) '--aot', + if (!embedSources) '--no-embed-sources', + if (!linkPlatform) '--no-link-platform', ...(defines.map((d) => '-D$d')), - if (packages != null) ...['--packages', packages], - '-o', - kernelFile, + if (packages != null) '--packages=$packages', + if (nativeAssets != null) '--native-assets=$nativeAssets', + if (resourcesFile != null) '--resources-file=$resourcesFile', + '--output=$kernelFile', ...extraGenKernelOptions, - if (nativeAssets != null) ...['--native-assets', nativeAssets], - if (resourcesFile != null) ...['--resources-file', resourcesFile], - sourceFile - ]); + sourceFile, + ]; + return Process.run(dartaotruntime, args); } -Future generateAotSnapshot( - String genSnapshot, +Future generateAotSnapshotHelper( String kernelFile, String snapshotFile, String? debugFile, diff --git a/pkg/dart2native/lib/dart2native_macho.dart b/pkg/dart2native/lib/dart2native_macho.dart index 6cd0cbe15caa..546c06d5cd81 100644 --- a/pkg/dart2native/lib/dart2native_macho.dart +++ b/pkg/dart2native/lib/dart2native_macho.dart @@ -63,6 +63,9 @@ class _MacOSVersion { // Writes an "appended" dart runtime + script snapshot file in a format // compatible with MachO executables. +// +// WARNING: this method is used within google3, so don't try to refactor so +// [dartaotruntimePath] is a constant inside this file. Future writeAppendedMachOExecutable( String dartaotruntimePath, String payloadPath, String outputPath) async { final aotRuntimeFile = File(dartaotruntimePath); diff --git a/pkg/dart2native/lib/dart2native_pe.dart b/pkg/dart2native/lib/dart2native_pe.dart index 55a2d2dd3097..8c4bf14cfe74 100644 --- a/pkg/dart2native/lib/dart2native_pe.dart +++ b/pkg/dart2native/lib/dart2native_pe.dart @@ -323,6 +323,9 @@ class PortableExecutable { // Writes an "appended" dart runtime + script snapshot file in a format // compatible with Portable Executable files. +// +// WARNING: this method is used within google3, so don't try to refactor so +// [dartaotruntimePath] is a constant inside this file. Future writeAppendedPortableExecutable( String dartaotruntimePath, String payloadPath, String outputPath) async { File originalExecutableFile = File(dartaotruntimePath); diff --git a/pkg/dart2native/lib/generate.dart b/pkg/dart2native/lib/generate.dart index 5fc9ee40f660..0025a9f8fe95 100644 --- a/pkg/dart2native/lib/generate.dart +++ b/pkg/dart2native/lib/generate.dart @@ -7,70 +7,111 @@ import 'dart:io'; import 'package:path/path.dart' as path; import 'dart2native.dart'; +export 'dart2native.dart' show genKernel, genSnapshot; -final Directory binDir = File(Platform.resolvedExecutable).parent; -final String executableSuffix = Platform.isWindows ? '.exe' : ''; -final String dartaotruntime = - path.join(binDir.path, 'dartaotruntime$executableSuffix'); -final String genKernel = - path.join(binDir.path, 'snapshots', 'gen_kernel_aot.dart.snapshot'); -final String genSnapshot = - path.join(binDir.path, 'utils', 'gen_snapshot$executableSuffix'); -final String productPlatformDill = path.join( - binDir.parent.path, 'lib', '_internal', 'vm_platform_strong_product.dill'); +final dartaotruntime = path.join( + binDir.path, + 'dartaotruntime$executableSuffix', +); +/// The kinds of native executables supported by [generateNative]. +enum Kind { + aot, + exe; + + String appendFileExtension(String fileName) { + return switch (this) { + Kind.aot => '$fileName.aot', + Kind.exe => '$fileName.exe', + }; + } +} + +/// Generates a self-contained executable or AOT snapshot. +/// +/// [sourceFile] can be the path to either a Dart source file containing `main` +/// or a kernel file generated with `--link-platform`. +/// +/// [defines] is the list of Dart defines to be set in the compiled program. +/// +/// [kind] is the type of executable to be generated ([Kind.exe] or [Kind.aot]). +/// +/// [outputFile] is the location the generated output will be written. If null, +/// the generated output will be written adjacent to [sourceFile] with the file +/// extension matching the executable type specified by [kind]. +/// +/// [debugFile] specifies the file debugging information should be written to. +/// +/// [packages] is the path to the `.dart_tool/package_config.json`. +/// +/// [targetOS] specifies the operating system the executable is being generated +/// for. This must be provided when [kind] is [Kind.exe], and it must match the +/// current operating system. +/// +/// [nativeAssets] is the path to `native_assets.yaml`. +/// +/// [resourcesFile] is the path to `resources.json`. +/// +/// [enableExperiment] is a comma separated list of language experiments to be +/// enabled. +/// +/// [verbosity] specifies the logging verbosity of the CFE. +/// +/// [extraOptions] is a set of extra options to be passed to `genSnapshot`. Future generateNative({ - String kind = 'exe', required String sourceFile, + required List defines, + Kind kind = Kind.exe, String? outputFile, String? debugFile, String? packages, String? targetOS, - required List defines, + String? nativeAssets, + String? resourcesFile, String enableExperiment = '', bool soundNullSafety = true, bool verbose = false, String verbosity = 'all', List extraOptions = const [], - String? nativeAssets, - String? resourcesFile, }) async { final Directory tempDir = Directory.systemTemp.createTempSync(); - try { - final sourcePath = path.canonicalize(path.normalize(sourceFile)); - if (packages != null) { - packages = path.canonicalize(path.normalize(packages)); + final String kernelFile = path.join(tempDir.path, 'kernel.dill'); + + final sourcePath = _normalize(sourceFile)!; + final sourceWithoutDartOrDill = sourcePath.replaceFirst( + RegExp(r'\.(dart|dill)$'), + '', + ); + final outputPath = _normalize( + outputFile ?? kind.appendFileExtension(sourceWithoutDartOrDill), + )!; + final debugPath = _normalize(debugFile); + packages = _normalize(packages); + + if (kind == Kind.exe) { + if (targetOS == null) { + throw ArgumentError('targetOS must be specified for executables.'); + } else if (targetOS != Platform.operatingSystem) { + throw StateError('Cross compilation not supported for executables.'); } - final Kind outputKind = { - 'aot': Kind.aot, - 'exe': Kind.exe, - }[kind]!; - final sourceWithoutDart = sourcePath.replaceFirst(RegExp(r'\.dart$'), ''); - final outputPath = path.canonicalize(path.normalize(outputFile ?? - { - Kind.aot: '$sourceWithoutDart.aot', - Kind.exe: '$sourceWithoutDart.exe', - }[outputKind]!)); - final debugPath = - debugFile != null ? path.canonicalize(path.normalize(debugFile)) : null; + } - if (verbose) { - if (targetOS != null) { - print('Specializing Platform getters for target OS $targetOS.'); - } - print('Compiling $sourcePath to $outputPath using format $kind:'); - print('Generating AOT kernel dill.'); + if (verbose) { + if (targetOS != null) { + print('Specializing Platform getters for target OS $targetOS.'); } + print('Compiling $sourcePath to $outputPath using format $kind:'); + print('Generating AOT kernel dill.'); + } - final String kernelFile = path.join(tempDir.path, 'kernel.dill'); - final kernelResult = await generateAotKernel( - dartaotruntime, - genKernel, - productPlatformDill, - sourcePath, - kernelFile, - packages, - defines, + try { + final kernelResult = await generateKernelHelper( + dartaotruntime: dartaotruntime, + sourceFile: sourcePath, + kernelFile: kernelFile, + packages: packages, + defines: defines, + fromDill: await isKernelFile(sourcePath), enableExperiment: enableExperiment, targetOS: targetOS, extraGenKernelOptions: [ @@ -80,6 +121,7 @@ Future generateNative({ ], nativeAssets: nativeAssets, resourcesFile: resourcesFile, + aot: true, ); await _forwardOutput(kernelResult); if (kernelResult.exitCode != 0) { @@ -93,11 +135,15 @@ Future generateNative({ if (verbose) { print('Generating AOT snapshot. $genSnapshot $extraAotOptions'); } - final String snapshotFile = (outputKind == Kind.aot + final String snapshotFile = (kind == Kind.aot ? outputPath : path.join(tempDir.path, 'snapshot.aot')); - final snapshotResult = await generateAotSnapshot( - genSnapshot, kernelFile, snapshotFile, debugPath, extraAotOptions); + final snapshotResult = await generateAotSnapshotHelper( + kernelFile, + snapshotFile, + debugPath, + extraAotOptions, + ); if (verbose || snapshotResult.exitCode != 0) { await _forwardOutput(snapshotResult); @@ -106,7 +152,7 @@ Future generateNative({ throw 'Generating AOT snapshot failed!'; } - if (outputKind == Kind.exe) { + if (kind == Kind.exe) { if (verbose) { print('Generating executable.'); } @@ -126,6 +172,88 @@ Future generateNative({ } } +/// Generates a kernel file. +/// +/// [sourceFile] can be the path to either a Dart source file containing `main` +/// or a kernel file. +/// +/// [outputFile] is the location the generated output will be written. If null, +/// the generated output will be written adjacent to [sourceFile] with the file +/// extension matching the executable type specified by [kind]. +/// +/// [defines] is the list of Dart defines to be set in the compiled program. +/// +/// [packages] is the path to the `.dart_tool/package_config.json`. +/// +/// [verbosity] specifies the logging verbosity of the CFE. +/// +/// [enableExperiment] is a comma separated list of language experiments to be +/// enabled. +/// +/// [linkPlatform] controls whether or not the platform kernel is included in +/// the output kernel file. This must be `true` if the resulting kernel is +/// meant to be used with `dart compile {exe, aot-snapshot}`. +/// +/// [embedSources] controls whether or not Dart source code is included in the +/// output kernel file. +/// +/// [product] specifies whether or not the resulting kernel should be generated +/// using PRODUCT mode platform binaries. +/// +/// [nativeAssets] is the path to `native_assets.yaml`. +/// +/// [resourcesFile] is the path to `resources.json`. +Future generateKernel({ + required String sourceFile, + required String outputFile, + required List defines, + required String? packages, + required String verbosity, + required String enableExperiment, + bool linkPlatform = false, + bool embedSources = true, + // TODO: do we want to allow for users to generate non-product mode kernel? + // What are the impliciations of using product mode kernel in a non-product runtime? + bool product = true, + bool soundNullSafety = true, + bool verbose = false, + String? nativeAssets, + String? resourcesFile, +}) async { + final sourcePath = _normalize(sourceFile)!; + final outputPath = _normalize(outputFile)!; + packages = _normalize(packages); + + final kernelResult = await generateKernelHelper( + dartaotruntime: dartaotruntime, + sourceFile: sourcePath, + kernelFile: outputPath, + packages: packages, + defines: defines, + linkPlatform: linkPlatform, + embedSources: embedSources, + fromDill: await isKernelFile(sourcePath), + enableExperiment: enableExperiment, + extraGenKernelOptions: [ + '--invocation-modes=compile', + '--verbosity=$verbosity', + '--${soundNullSafety ? '' : 'no-'}sound-null-safety', + ], + nativeAssets: nativeAssets, + resourcesFile: resourcesFile, + product: product, + ); + await _forwardOutput(kernelResult); + if (kernelResult.exitCode != 0) { + throw 'Generating kernel failed!'; + } +} + +String? _normalize(String? p) { + if (p == null) return null; + return path.canonicalize(path.normalize(p)); +} + Future _forwardOutput(ProcessResult result) async { if (result.stdout.isNotEmpty) { final bool needsNewLine = !result.stdout.endsWith('\n'); diff --git a/pkg/dart2native/pubspec.yaml b/pkg/dart2native/pubspec.yaml index 309ca5ac390c..bb524f9e8ffb 100644 --- a/pkg/dart2native/pubspec.yaml +++ b/pkg/dart2native/pubspec.yaml @@ -3,7 +3,7 @@ name: dart2native publish_to: none environment: - sdk: '>=2.17.0 <3.0.0' + sdk: ^3.3.0 # Add the bin/dart2native.dart script to the scripts pub installs. executables: @@ -11,6 +11,8 @@ executables: # Use 'any' constraints here; we get our versions from the DEPS file. dependencies: + collection: any + kernel: any path: any # Use 'any' constraints here; we get our versions from the DEPS file. diff --git a/pkg/dartdev/lib/src/commands/build.dart b/pkg/dartdev/lib/src/commands/build.dart index 62e926383ba5..abff24ded8de 100644 --- a/pkg/dartdev/lib/src/commands/build.dart +++ b/pkg/dartdev/lib/src/commands/build.dart @@ -93,12 +93,15 @@ class BuildCommand extends DartdevCommand { sourceUri.toFilePath().removeDotDart().makeFolder(), ); - final format = args[formatOptionName] as String; - final outputExeUri = outputUri - .resolve('${sourceUri.pathSegments.last.split('.').first}.$format'); + final format = Kind.values.byName(args[formatOptionName] as String); + final outputExeUri = outputUri.resolve( + format.appendFileExtension( + sourceUri.pathSegments.last.split('.').first, + ), + ); String? targetOS = args['target-os']; - if (format != 'exe') { - assert(format == 'aot'); + if (format != Kind.exe) { + assert(format == Kind.aot); // If we're generating an AOT snapshot and not an executable, then // targetOS is allowed to be null for a platform-independent snapshot // or a different platform than the host. @@ -106,7 +109,7 @@ class BuildCommand extends DartdevCommand { targetOS = Platform.operatingSystem; } else if (targetOS != Platform.operatingSystem) { stderr.writeln( - "'dart build -f $format' does not support cross-OS compilation."); + "'dart build -f ${format.name}' does not support cross-OS compilation."); stderr.writeln('Host OS: ${Platform.operatingSystem}'); stderr.writeln('Target OS: $targetOS'); return 128; diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart index 14ecd6bb9e46..a80ffb5ffb55 100644 --- a/pkg/dartdev/lib/src/commands/compile.dart +++ b/pkg/dartdev/lib/src/commands/compile.dart @@ -52,6 +52,26 @@ bool checkFile(String sourcePath) { 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'; @@ -113,22 +133,128 @@ class CompileJSCommand extends CompileSubcommandCommand { } } -class CompileSnapshotCommand extends CompileSubcommandCommand { - static const String jitSnapshotCmdName = 'jit-snapshot'; - static const String kernelCmdName = 'kernel'; +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 '; - final String commandName; - final String help; - final String fileExt; - final String formatName; + 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, + ) + ..addExperimentalFlags(verbose: verbose); + } - CompileSnapshotCommand({ - required this.commandName, - required this.help, - required this.fileExt, - required this.formatName, + @override + FutureOr 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 -1; + } + + // Determine output file name. + String? outputFile = args[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 -1; + } + + final bool soundNullSafety = args['sound-null-safety']; + if (!soundNullSafety && !shouldAllowNoSoundNullSafety()) { + return compileErrorExitCode; + } + + try { + await generateKernel( + sourceFile: sourcePath, + outputFile: outputFile, + defines: args['define'], + packages: args['packages'], + enableExperiment: args.enabledExperiments.join(','), + linkPlatform: args['link-platform'], + embedSources: args['embed-sources'], + soundNullSafety: args['sound-null-safety'], + verbose: verbose, + verbosity: args['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 '; + + CompileJitSnapshotCommand({ bool verbose = false, - }) : super(commandName, 'Compile Dart $help', verbose) { + }) : super('jit-snapshot', help, verbose) { argParser ..addOption( outputFileOption.flag, @@ -163,34 +289,24 @@ class CompileSnapshotCommand extends CompileSubcommandCommand { } @override - String get invocation { - String msg = '${super.invocation} '; - if (isJitSnapshot) { - msg += ' []'; - } - return msg; - } + String get invocation => + '${super.invocation} []'; @override ArgParser createArgParser() { return ArgParser( - // Don't parse the training arguments for JIT snapshots, but don't accept - // flags after the script name for kernel snapshots. - allowTrailingOptions: !isJitSnapshot, + // Don't parse the training arguments for JIT snapshots. + allowTrailingOptions: false, usageLineLength: dartdevUsageLineLength, ); } - bool get isJitSnapshot => commandName == jitSnapshotCmdName; - @override FutureOr run() async { final args = argResults!; if (args.rest.isEmpty) { // This throws. usageException('Missing Dart entry point.'); - } else if (!isJitSnapshot && args.rest.length > 1) { - usageException('Unexpected arguments after Dart entry point.'); } final String sourcePath = args.rest[0]; @@ -204,7 +320,12 @@ class CompileSnapshotCommand extends CompileSubcommandCommand { final inputWithoutDart = sourcePath.endsWith('.dart') ? sourcePath.substring(0, sourcePath.length - 5) : sourcePath; - outputFile = '$inputWithoutDart.$fileExt'; + outputFile = '$inputWithoutDart.jit'; + } + + if (!checkFileWriteable(outputFile)) { + log.stderr('Unable to open file $outputFile for writing snapshot.'); + return -1; } final enabledExperiments = args.enabledExperiments; @@ -212,7 +333,7 @@ class CompileSnapshotCommand extends CompileSubcommandCommand { // Build arguments. final buildArgs = []; - buildArgs.add('--snapshot-kind=$formatName'); + buildArgs.add('--snapshot-kind=app-jit'); buildArgs.add('--snapshot=${path.canonicalize(outputFile)}'); final bool soundNullSafety = args['sound-null-safety']; @@ -247,7 +368,7 @@ class CompileSnapshotCommand extends CompileSubcommandCommand { buildArgs.addAll(args.rest.sublist(1)); } - log.stdout('Compiling $sourcePath to $commandName file $outputFile.'); + log.stdout('Compiling $sourcePath to jit-snapshot file $outputFile.'); // TODO(bkonyi): perform compilation in same process. return await runProcess([sdk.dart, ...buildArgs]); } @@ -258,7 +379,7 @@ class CompileNativeCommand extends CompileSubcommandCommand { static const String aotSnapshotCmdName = 'aot-snapshot'; final String commandName; - final String format; + final Kind format; final String help; final bool nativeAssetsExperimentEnabled; @@ -365,8 +486,8 @@ Remove debugging information from the output and save it separately to the speci } String? targetOS = args['target-os']; - if (format != 'exe') { - assert(format == 'aot'); + if (format != Kind.exe) { + assert(format == Kind.aot); // If we're generating an AOT snapshot and not an executable, then // targetOS is allowed to be null for a platform-independent snapshot // or a different platform than the host. @@ -739,7 +860,7 @@ For example: dart compile $name --packages=/tmp/pkgs.json main.dart'''); // // See https://github.com/dart-lang/sdk/issues/51513 for context. if (name == CompileNativeCommand.aotSnapshotCmdName || - name == CompileSnapshotCommand.kernelCmdName) { + name == CompileKernelSnapshotCommand.commandName) { log.stdout( 'Warning: the flag --no-sound-null-safety is deprecated and pending removal.'); return true; @@ -757,27 +878,12 @@ class CompileCommand extends DartdevCommand { bool nativeAssetsExperimentEnabled = false, }) : super(cmdName, 'Compile Dart to various formats.', verbose) { addSubcommand(CompileJSCommand(verbose: verbose)); - addSubcommand(CompileSnapshotCommand( - commandName: CompileSnapshotCommand.jitSnapshotCmdName, - help: 'to a JIT snapshot.\n' - 'The executable will be run once to snapshot a warm JIT.\n' - 'To run the snapshot use: dart run ', - fileExt: 'jit', - formatName: 'app-jit', - verbose: verbose, - )); - addSubcommand(CompileSnapshotCommand( - commandName: CompileSnapshotCommand.kernelCmdName, - help: 'to a kernel snapshot.\n' - 'To run the snapshot use: dart run ', - fileExt: 'dill', - formatName: 'kernel', - verbose: verbose, - )); + addSubcommand(CompileJitSnapshotCommand(verbose: verbose)); + addSubcommand(CompileKernelSnapshotCommand(verbose: verbose)); addSubcommand(CompileNativeCommand( commandName: CompileNativeCommand.exeCmdName, help: 'to a self-contained executable.', - format: 'exe', + format: Kind.exe, verbose: verbose, nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled, )); @@ -785,7 +891,7 @@ class CompileCommand extends DartdevCommand { commandName: CompileNativeCommand.aotSnapshotCmdName, help: 'to an AOT snapshot.\n' 'To run the snapshot use: dartaotruntime ', - format: 'aot', + format: Kind.aot, verbose: verbose, nativeAssetsExperimentEnabled: nativeAssetsExperimentEnabled, )); diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart index e75c86fd6e1d..422c818c2ab8 100644 --- a/pkg/dartdev/test/commands/compile_test.dart +++ b/pkg/dartdev/test/commands/compile_test.dart @@ -791,6 +791,48 @@ void main() { expect(result.exitCode, 0); }, skip: isRunningOnIA32); + test('Compile exe from kernel', () async { + final p = project(mainSrc: ''' +void main() {} +'''); + final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); + final dillOutFile = path.canonicalize(path.join(p.dirPath, 'mydill')); + final exeOutFile = path.canonicalize(path.join(p.dirPath, 'myexe')); + + var result = await p.run( + [ + 'compile', + 'kernel', + '-o', + dillOutFile, + inFile, + ], + ); + expect(result.exitCode, 0); + expect( + File(dillOutFile).existsSync(), + true, + reason: 'File not found: $dillOutFile', + ); + + result = await p.run( + [ + 'compile', + 'exe', + '-o', + exeOutFile, + dillOutFile, + ], + ); + + expect(result.exitCode, 0); + expect( + File(exeOutFile).existsSync(), + true, + reason: 'File not found: $exeOutFile', + ); + }, skip: isRunningOnIA32); + test('Compile wasm with wrong output filename', () async { final p = project(mainSrc: 'void main() {}'); final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); @@ -1092,6 +1134,48 @@ void main() { expect(result.exitCode, 0); }, skip: isRunningOnIA32); + test('Compile AOT snapshot from kernel', () async { + final p = project(mainSrc: ''' +void main() {} +'''); + final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); + final dillOutFile = path.canonicalize(path.join(p.dirPath, 'mydill')); + final aotOutFile = path.canonicalize(path.join(p.dirPath, 'myaot')); + + var result = await p.run( + [ + 'compile', + 'kernel', + '-o', + dillOutFile, + inFile, + ], + ); + expect(result.exitCode, 0); + expect( + File(dillOutFile).existsSync(), + true, + reason: 'File not found: $dillOutFile', + ); + + result = await p.run( + [ + 'compile', + 'aot-snapshot', + '-o', + aotOutFile, + dillOutFile, + ], + ); + + expect(result.exitCode, 0); + expect( + File(aotOutFile).existsSync(), + true, + reason: 'File not found: $aotOutFile', + ); + }, skip: isRunningOnIA32); + test('Compile kernel with invalid output directory', () async { final p = project(mainSrc: '''void main() {}'''); final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); @@ -1185,7 +1269,7 @@ void main() {} ], ); - expect(result.stderr, contains(unsoundNullSafetyMessage)); + expect(result.stdout, contains(unsoundNullSafetyMessage)); expect(result.stdout, contains(unsoundNullSafetyWarning)); expect(result.exitCode, 0); expect(File(outFile).existsSync(), true, @@ -1286,7 +1370,7 @@ void main() { expect(result.stderr, isNot(contains(soundNullSafetyMessage))); expect(result.stderr, contains('must be assigned before it can be used')); - expect(result.exitCode, 254); + expect(result.exitCode, 64); }); test('Compile kernel with warnings', () async { @@ -1310,7 +1394,7 @@ void main() { ); expect(result.stderr, isNot(contains(soundNullSafetyMessage))); - expect(result.stderr, contains('Warning:')); + expect(result.stdout, contains('Warning:')); expect(result.exitCode, 0); });