Add link.dart hook
Adding a linking script support, see go/resource-shaking. The goal is to run a `link.dart` script after kernel compilation complimentary to `build.dart` running before.
Change-Id: Iadc8648ae5fa2e823b6541c5bc08617bb860a017
Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/338380
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
diff --git a/DEPS b/DEPS
index 95cf9e2..7db601a 100644
--- a/DEPS
+++ b/DEPS
@@ -164,7 +164,7 @@
"material_color_utilities_rev": "799b6ba2f3f1c28c67cc7e0b4f18e0c7d7f3c03e",
"mime_rev": "b01c9a24e0991da479bd405138be3b3e403ff456",
"mockito_rev": "81ecb88b631a9c1e677852a33ad916238dee0ef2",
- "native_rev": "a152cfa21129510e9227962a01ec12918e192ecc", # mosum@ and dacoharkes@ are rolling breaking changes manually while the assets features are in experimental.
+ "native_rev": "1a6faf502c01c598ce8ed6c77ea22c29774dbf34", # mosum@ and dacoharkes@ are rolling breaking changes manually while the assets features are in experimental.
"package_config_rev": "a36e496d61937a800c22f68035dc98bf8ead7fb2",
"path_rev": "f411b96a2598d3911b485d606430fd775fd9e41a",
"pool_rev": "e6df05a287a2193c4c566608f9cd12e8689d3264",
diff --git a/pkg/dart2native/lib/generate.dart b/pkg/dart2native/lib/generate.dart
index 0fb6d69..7f1f6d1 100644
--- a/pkg/dart2native/lib/generate.dart
+++ b/pkg/dart2native/lib/generate.dart
@@ -16,7 +16,7 @@
'dartaotruntime$executableSuffix',
);
-/// The kinds of native executables supported by [generateNative].
+/// The kinds of native executables supported by [KernelGenerator].
enum Kind {
aot,
exe;
@@ -29,98 +29,176 @@
}
}
+/// First step of generating a snapshot, generating a kernel.
+///
+/// See also the docs for [_Generator].
+extension type KernelGenerator._(_Generator _generator) {
+ KernelGenerator({
+ required String sourceFile,
+ required List<String> defines,
+ Kind kind = Kind.exe,
+ String? outputFile,
+ String? debugFile,
+ String? packages,
+ String? targetOS,
+ String enableExperiment = '',
+ bool enableAsserts = false,
+ bool verbose = false,
+ String verbosity = 'all',
+ required Directory tempDir,
+ }) : _generator = _Generator(
+ sourceFile: sourceFile,
+ defines: defines,
+ tempDir: tempDir,
+ debugFile: debugFile,
+ enableAsserts: enableAsserts,
+ enableExperiment: enableExperiment,
+ kind: kind,
+ outputFile: outputFile,
+ packages: packages,
+ targetOS: targetOS,
+ verbose: verbose,
+ verbosity: verbosity,
+ );
+
+ /// Generate a kernel file,
+ ///
+ /// [resourcesFile] is the path to `resources.json`, where the tree-shaking
+ /// information collected during kernel compilation is stored.
+ Future<SnapshotGenerator> generate({String? resourcesFile}) =>
+ _generator.generateKernel(resourcesFile: resourcesFile);
+}
+
+/// Second step of generating a snapshot is generating the snapshot itself.
+///
+/// See also the docs for [_Generator].
+extension type SnapshotGenerator._(_Generator _generator) {
+ /// Generate a snapshot or executable.
+ ///
+ /// This means concatenating the list of assets to the kernel and then calling
+ /// `genSnapshot`. [nativeAssets] is the path to `native_assets.yaml`, and
+ /// [extraOptions] is a set of extra options to be passed to `genSnapshot`.
+ Future<void> generate({
+ String? nativeAssets,
+ List<String> extraOptions = const [],
+ }) =>
+ _generator.generateSnapshotWithAssets(
+ nativeAssets: nativeAssets,
+ extraOptions: extraOptions,
+ );
+}
+
/// 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`.
+/// This is a two-step process. First, a kernel is generated. Then, if present,
+/// the list of assets is concatenated to the kernel as a library. In a final
+/// step, the snapshot or executable itself is generated.
///
-/// [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<void> generateNative({
- required String sourceFile,
- required List<String> defines,
- Kind kind = Kind.exe,
- String? outputFile,
- String? debugFile,
- String? packages,
- String? targetOS,
- String? nativeAssets,
- String? resourcesFile,
- String enableExperiment = '',
- bool enableAsserts = false,
- bool verbose = false,
- String verbosity = 'all',
- List<String> extraOptions = const [],
-}) async {
- final tempDir = Directory.systemTemp.createTempSync();
- final programKernelFile = path.join(tempDir.path, 'program.dill');
+/// To reduce possible errors in calling order of the steps, this class is only
+/// exposed through [KernelGenerator] and [SnapshotGenerator], which make it
+/// impossible to call steps out of order.
+class _Generator {
+ /// The list of Dart defines to be set in the compiled program.
+ final List<String> _defines;
- 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);
+ /// The type of executable to be generated, either [Kind.exe] or [Kind.aot].
+ final Kind _kind;
- if (kind == Kind.exe) {
- if (targetOS == null) {
- throw ArgumentError('targetOS must be specified for executables.');
- } else if (targetOS != Platform.operatingSystem) {
- throw UnsupportedError(
- 'Cross compilation not supported for executables.');
+ /// The location the generated output will be written. If null the generated
+ /// output will be written adjacent to [_sourcePath] with the file extension
+ /// matching the executable type specified by [_kind].
+ final String? _outputFile;
+
+ /// Specifies the file debugging information should be written to.
+ final String? _debugFile;
+
+ /// 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.
+ final String? _targetOS;
+
+ /// A comma separated list of language experiments to be enabled.
+ final String _enableExperiment;
+
+ ///
+ final bool _enableAsserts;
+ final bool _verbose;
+
+ /// Specifies the logging verbosity of the CFE.
+ final String _verbosity;
+
+ /// A temporary directory specified by the caller, who also has to clean it
+ /// up.
+ final Directory _tempDir;
+
+ /// The location of the compiled kernel file, which will be written on a call
+ /// to [generateKernel].
+ final String _programKernelFile;
+
+ /// The path to either a Dart source file containing `main` or a kernel file
+ /// generated with `--link-platform`.
+ final String _sourcePath;
+
+ /// The path to the `.dart_tool/package_config.json`.
+ final String? _packages;
+
+ _Generator({
+ required String sourceFile,
+ required List<String> defines,
+ required Kind kind,
+ String? outputFile,
+ String? debugFile,
+ String? packages,
+ String? targetOS,
+ required String enableExperiment,
+ required bool enableAsserts,
+ required bool verbose,
+ required String verbosity,
+ required Directory tempDir,
+ }) : _kind = kind,
+ _verbose = verbose,
+ _tempDir = tempDir,
+ _verbosity = verbosity,
+ _enableAsserts = enableAsserts,
+ _enableExperiment = enableExperiment,
+ _targetOS = targetOS,
+ _debugFile = debugFile,
+ _outputFile = outputFile,
+ _defines = defines,
+ _programKernelFile = path.join(tempDir.path, 'program.dill'),
+ _sourcePath = _normalize(sourceFile)!,
+ _packages = _normalize(packages) {
+ if (_kind == Kind.exe) {
+ if (_targetOS == null) {
+ throw ArgumentError('targetOS must be specified for executables.');
+ } else if (_targetOS != Platform.operatingSystem) {
+ throw UnsupportedError(
+ 'Cross compilation not supported for executables.');
+ }
}
}
- if (verbose) {
- if (targetOS != null) {
- print('Specializing Platform getters for target OS $targetOS.');
+ Future<SnapshotGenerator> generateKernel({String? resourcesFile}) async {
+ if (_verbose) {
+ if (_targetOS != null) {
+ print('Specializing Platform getters for target OS $_targetOS.');
+ }
+ print('Generating AOT kernel dill.');
}
- print('Compiling $sourcePath to $outputPath using format $kind:');
- print('Generating AOT kernel dill.');
- }
- try {
final kernelResult = await generateKernelHelper(
dartaotruntime: dartaotruntime,
- sourceFile: sourcePath,
- kernelFile: programKernelFile,
- packages: packages,
- defines: defines,
- fromDill: await isKernelFile(sourcePath),
- enableAsserts: enableAsserts,
- enableExperiment: enableExperiment,
- targetOS: targetOS,
+ sourceFile: _sourcePath,
+ kernelFile: _programKernelFile,
+ packages: _packages,
+ defines: _defines,
+ fromDill: await isKernelFile(_sourcePath),
+ enableAsserts: _enableAsserts,
+ enableExperiment: _enableExperiment,
+ targetOS: _targetOS,
extraGenKernelOptions: [
'--invocation-modes=compile',
- '--verbosity=$verbosity',
+ '--verbosity=$_verbosity',
],
resourcesFile: resourcesFile,
aot: true,
@@ -129,25 +207,88 @@
if (kernelResult.exitCode != 0) {
throw StateError('Generating AOT kernel dill failed!');
}
- String kernelFile;
+ return SnapshotGenerator._(this);
+ }
+
+ Future<void> generateSnapshotWithAssets({
+ String? nativeAssets,
+ required List<String> extraOptions,
+ }) async {
+ final kernelFile = await _concatenateAssetsToKernel(nativeAssets);
+ await _generateSnapshot(extraOptions, kernelFile);
+ }
+
+ Future<void> _generateSnapshot(
+ List<String> extraOptions,
+ String kernelFile,
+ ) async {
+ final sourceWithoutDartOrDill = _sourcePath.replaceFirst(
+ RegExp(r'\.(dart|dill)$'),
+ '',
+ );
+ final outputPath = _normalize(
+ _outputFile ?? _kind.appendFileExtension(sourceWithoutDartOrDill),
+ )!;
+ final debugPath = _normalize(_debugFile);
+
+ if (_verbose) {
+ print('Compiling $_sourcePath to $outputPath using format $_kind:');
+ print('Generating AOT snapshot. $genSnapshot $extraOptions');
+ }
+ final snapshotFile = _kind == Kind.aot
+ ? outputPath
+ : path.join(_tempDir.path, 'snapshot.aot');
+ final snapshotResult = await generateAotSnapshotHelper(
+ kernelFile,
+ snapshotFile,
+ debugPath,
+ _enableAsserts,
+ extraOptions,
+ );
+
+ if (_verbose || snapshotResult.exitCode != 0) {
+ await _forwardOutput(snapshotResult);
+ }
+ if (snapshotResult.exitCode != 0) {
+ throw StateError('Generating AOT snapshot failed!');
+ }
+
+ if (_kind == Kind.exe) {
+ if (_verbose) {
+ print('Generating executable.');
+ }
+ await writeAppendedExecutable(dartaotruntime, snapshotFile, outputPath);
+
+ if (Platform.isLinux || Platform.isMacOS) {
+ if (_verbose) {
+ print('Marking binary executable.');
+ }
+ await markExecutable(outputPath);
+ }
+ }
+
+ print('Generated: $outputPath');
+ }
+
+ Future<String> _concatenateAssetsToKernel(String? nativeAssets) async {
if (nativeAssets == null) {
- kernelFile = programKernelFile;
+ return _programKernelFile;
} else {
// TODO(dacoharkes): This method will need to be split in two parts. Then
// the link hooks can be run in between those two parts.
final nativeAssetsDillFile =
- path.join(tempDir.path, 'native_assets.dill');
+ path.join(_tempDir.path, 'native_assets.dill');
final kernelResult = await generateKernelHelper(
dartaotruntime: dartaotruntime,
kernelFile: nativeAssetsDillFile,
- packages: packages,
- defines: defines,
- enableAsserts: enableAsserts,
- enableExperiment: enableExperiment,
- targetOS: targetOS,
+ packages: _packages,
+ defines: _defines,
+ enableAsserts: _enableAsserts,
+ enableExperiment: _enableExperiment,
+ targetOS: _targetOS,
extraGenKernelOptions: [
'--invocation-modes=compile',
- '--verbosity=$verbosity',
+ '--verbosity=$_verbosity',
],
nativeAssets: nativeAssets,
aot: true,
@@ -156,8 +297,8 @@
if (kernelResult.exitCode != 0) {
throw StateError('Generating AOT kernel dill failed!');
}
- kernelFile = path.join(tempDir.path, 'kernel.dill');
- final programKernelBytes = await File(programKernelFile).readAsBytes();
+ final kernelFile = path.join(_tempDir.path, 'kernel.dill');
+ final programKernelBytes = await File(_programKernelFile).readAsBytes();
final nativeAssetKernelBytes =
await File(nativeAssetsDillFile).readAsBytes();
await File(kernelFile).writeAsBytes(
@@ -167,45 +308,8 @@
],
flush: true,
);
+ return kernelFile;
}
-
- if (verbose) {
- print('Generating AOT snapshot. $genSnapshot $extraOptions');
- }
- final snapshotFile =
- kind == Kind.aot ? outputPath : path.join(tempDir.path, 'snapshot.aot');
- final snapshotResult = await generateAotSnapshotHelper(
- kernelFile,
- snapshotFile,
- debugPath,
- enableAsserts,
- extraOptions,
- );
-
- if (verbose || snapshotResult.exitCode != 0) {
- await _forwardOutput(snapshotResult);
- }
- if (snapshotResult.exitCode != 0) {
- throw StateError('Generating AOT snapshot failed!');
- }
-
- if (kind == Kind.exe) {
- if (verbose) {
- print('Generating executable.');
- }
- await writeAppendedExecutable(dartaotruntime, snapshotFile, outputPath);
-
- if (Platform.isLinux || Platform.isMacOS) {
- if (verbose) {
- print('Marking binary executable.');
- }
- await markExecutable(outputPath);
- }
- }
-
- print('Generated: $outputPath');
- } finally {
- tempDir.deleteSync(recursive: true);
}
}
diff --git a/pkg/dartdev/lib/src/commands/build.dart b/pkg/dartdev/lib/src/commands/build.dart
index fec6afe..5fcf734 100644
--- a/pkg/dartdev/lib/src/commands/build.dart
+++ b/pkg/dartdev/lib/src/commands/build.dart
@@ -20,6 +20,9 @@
import '../core.dart';
import '../native_assets.dart';
+const _libOutputDirectory = 'lib';
+const _dataOutputDirectory = 'assets';
+
class BuildCommand extends DartdevCommand {
static const String cmdName = 'build';
static const String outputOptionName = 'output';
@@ -99,7 +102,7 @@
sourceUri.pathSegments.last.split('.').first,
),
);
- String? targetOS = args.option('target-os');
+ String? targetOS = args['target-os'];
if (format != Kind.exe) {
assert(format == Kind.aot);
// If we're generating an AOT snapshot and not an executable, then
@@ -122,13 +125,15 @@
}
await outputDir.create(recursive: true);
+ // Start native asset generation here.
stdout.writeln('Building native assets.');
final workingDirectory = Directory.current.uri;
final target = Target.current;
- final buildResult = await NativeAssetsBuildRunner(
+ final nativeAssetsBuildRunner = NativeAssetsBuildRunner(
dartExecutable: Uri.file(sdk.dart),
logger: logger(verbose),
- ).build(
+ );
+ final buildResult = await nativeAssetsBuildRunner.build(
workingDirectory: workingDirectory,
target: target,
linkModePreference: LinkModePreferenceImpl.dynamic,
@@ -139,93 +144,119 @@
],
);
if (!buildResult.success) {
- stderr.write('Native assets build failed.');
+ stderr.writeln('Native assets build failed.');
return 255;
}
- final assets = buildResult.assets;
- final nativeAssets = assets.whereType<NativeCodeAssetImpl>();
- final staticAssets =
- nativeAssets.where((e) => e.linkMode == StaticLinkingImpl());
- if (staticAssets.isNotEmpty) {
- stderr.write(
- """'dart build' does not yet support NativeCodeAssets with static linking.
-Use linkMode as dynamic library instead.""");
- return 255;
- }
+ // End native asset generation here.
- Uri? tempUri;
- Uri? nativeAssetsDartUri;
final tempDir = Directory.systemTemp.createTempSync();
- if (nativeAssets.isNotEmpty) {
- stdout.writeln('Copying native assets.');
- KernelAsset targetLocation(NativeCodeAssetImpl asset) {
- final linkMode = asset.linkMode;
- final KernelAssetPath kernelAssetPath;
- switch (linkMode) {
- case DynamicLoadingSystemImpl _:
- kernelAssetPath = KernelAssetSystemPath(linkMode.uri);
- case LookupInExecutableImpl _:
- kernelAssetPath = KernelAssetInExecutable();
- case LookupInProcessImpl _:
- kernelAssetPath = KernelAssetInProcess();
- case DynamicLoadingBundledImpl _:
- kernelAssetPath = KernelAssetRelativePath(
- Uri(path: asset.file!.pathSegments.last),
- );
- default:
- throw Exception(
- 'Unsupported NativeCodeAsset linkMode ${linkMode.runtimeType} in asset $asset',
- );
- }
- return KernelAsset(
- id: asset.id,
- target: target,
- path: kernelAssetPath,
- );
+ try {
+ final packageConfig = await packageConfigUri(sourceUri);
+ final resources = path.join(tempDir.path, 'resources.json');
+ final generator = KernelGenerator(
+ kind: format,
+ sourceFile: sourceUri.toFilePath(),
+ outputFile: outputExeUri.toFilePath(),
+ verbose: verbose,
+ verbosity: args.option('verbosity')!,
+ defines: [],
+ packages: packageConfig?.toFilePath(),
+ targetOS: targetOS,
+ enableExperiment: args.enabledExperiments.join(','),
+ tempDir: tempDir,
+ );
+
+ final snapshotGenerator = await generator.generate(
+ resourcesFile: resources,
+ );
+
+ // Start linking here.
+ final linkResult = await nativeAssetsBuildRunner.link(
+ resourceIdentifiers: Uri.file(resources),
+ workingDirectory: workingDirectory,
+ target: target,
+ buildMode: BuildModeImpl.release,
+ includeParentEnvironment: true,
+ buildResult: buildResult,
+ );
+
+ if (!linkResult.success) {
+ stderr.writeln('Native assets link failed.');
+ return 255;
}
- final assetTargetLocations = {
- for (final asset in nativeAssets) asset: targetLocation(asset),
- };
- final copiedFiles = await Future.wait([
- for (final assetMapping in assetTargetLocations.entries)
- if (assetMapping.value.path is KernelAssetRelativePath)
- File.fromUri(assetMapping.key.file!).copy(outputUri
- .resolveUri(
- (assetMapping.value.path as KernelAssetRelativePath).uri)
- .toFilePath())
- ]);
- stdout.writeln('Copied ${copiedFiles.length} native assets.');
+ final tempUri = tempDir.uri;
+ Uri? assetsDartUri;
+ final allAssets = [...buildResult.assets, ...linkResult.assets];
+ final staticAssets = allAssets
+ .whereType<NativeCodeAssetImpl>()
+ .where((e) => e.linkMode == StaticLinkingImpl());
+ if (staticAssets.isNotEmpty) {
+ stderr.write(
+ """'dart build' does not yet support NativeCodeAssets with static linking.
+Use linkMode as dynamic library instead.""");
+ return 255;
+ }
+ if (allAssets.isNotEmpty) {
+ final targetMapping = _targetMapping(allAssets, target);
+ assetsDartUri = await _writeAssetsYaml(
+ targetMapping.map((e) => e.target).toList(),
+ assetsDartUri,
+ tempUri,
+ );
+ if (allAssets.isNotEmpty) {
+ stdout.writeln(
+ 'Copying ${allAssets.length} build assets: ${allAssets.map((e) => e.id)}');
+ _copyAssets(targetMapping, outputUri);
+ }
+ }
- tempUri = tempDir.uri;
- nativeAssetsDartUri = tempUri.resolve('native_assets.yaml');
- final assetsContent = KernelAssets(assetTargetLocations.values.toList())
- .toNativeAssetsFile();
- await Directory.fromUri(nativeAssetsDartUri.resolve('.')).create();
- await File(nativeAssetsDartUri.toFilePath()).writeAsString(assetsContent);
+ await snapshotGenerator.generate(
+ nativeAssets: assetsDartUri?.toFilePath(),
+ );
+
+ // End linking here.
+ } finally {
+ await tempDir.delete(recursive: true);
}
- final packageConfig = await packageConfigUri(sourceUri);
-
- await generateNative(
- kind: format,
- sourceFile: sourceUri.toFilePath(),
- outputFile: outputExeUri.toFilePath(),
- verbose: verbose,
- verbosity: args.option('verbosity')!,
- defines: [],
- nativeAssets: nativeAssetsDartUri?.toFilePath(),
- packages: packageConfig?.toFilePath(),
- targetOS: targetOS,
- enableExperiment: args.enabledExperiments.join(','),
- resourcesFile: path.join(tempDir.path, 'resources.json'),
- );
-
- if (tempUri != null) {
- await Directory.fromUri(tempUri).delete(recursive: true);
- }
-
return 0;
}
+
+ List<({AssetImpl asset, KernelAsset target})> _targetMapping(
+ Iterable<AssetImpl> assets,
+ Target target,
+ ) {
+ return [
+ for (final asset in assets)
+ (asset: asset, target: asset.targetLocation(target)),
+ ];
+ }
+
+ void _copyAssets(
+ List<({AssetImpl asset, KernelAsset target})> assetTargetLocations,
+ Uri output,
+ ) {
+ for (final (asset: asset, target: target) in assetTargetLocations) {
+ final targetPath = target.path;
+ if (targetPath is KernelAssetRelativePath) {
+ asset.file!.copyTo(targetPath, output);
+ }
+ }
+ }
+
+ Future<Uri> _writeAssetsYaml(
+ List<KernelAsset> assetTargetLocations,
+ Uri? nativeAssetsDartUri,
+ Uri tempUri,
+ ) async {
+ stdout.writeln('Writing native_assets.yaml.');
+ nativeAssetsDartUri = tempUri.resolve('native_assets.yaml');
+ final assetsContent =
+ KernelAssets(assetTargetLocations).toNativeAssetsFile();
+ await Directory.fromUri(nativeAssetsDartUri.resolve('.')).create();
+ await File(nativeAssetsDartUri.toFilePath()).writeAsString(assetsContent);
+ return nativeAssetsDartUri;
+ }
}
extension on String {
@@ -234,6 +265,67 @@
String removeDotDart() => replaceFirst(RegExp(r'\.dart$'), '');
}
+extension on Uri {
+ void copyTo(KernelAssetRelativePath target, Uri outputUri) {
+ if (this != target.uri) {
+ final targetUri = outputUri.resolveUri(target.uri);
+ File.fromUri(targetUri).createSync(
+ recursive: true,
+ exclusive: true,
+ );
+ File.fromUri(this).copySync(targetUri.toFilePath());
+ }
+ }
+}
+
+extension on AssetImpl {
+ KernelAsset targetLocation(Target target) {
+ return switch (this) {
+ NativeCodeAssetImpl nativeAsset => nativeAsset.targetLocation(target),
+ DataAssetImpl dataAsset => dataAsset.targetLocation(target),
+ AssetImpl() => throw UnimplementedError(),
+ };
+ }
+}
+
+extension on NativeCodeAssetImpl {
+ KernelAsset targetLocation(Target target) {
+ final KernelAssetPath kernelAssetPath;
+ switch (linkMode) {
+ case DynamicLoadingSystemImpl dynamicLoading:
+ kernelAssetPath = KernelAssetSystemPath(dynamicLoading.uri);
+ case LookupInExecutableImpl _:
+ kernelAssetPath = KernelAssetInExecutable();
+ case LookupInProcessImpl _:
+ kernelAssetPath = KernelAssetInProcess();
+ case DynamicLoadingBundledImpl _:
+ kernelAssetPath = KernelAssetRelativePath(
+ Uri(path: path.join(_libOutputDirectory, file!.pathSegments.last)),
+ );
+ default:
+ throw Exception(
+ 'Unsupported NativeCodeAsset linkMode ${linkMode.runtimeType} in asset $this',
+ );
+ }
+ return KernelAsset(
+ id: id,
+ target: target,
+ path: kernelAssetPath,
+ );
+ }
+}
+
+extension on DataAssetImpl {
+ KernelAsset targetLocation(Target target) {
+ return KernelAsset(
+ id: id,
+ target: target,
+ path: KernelAssetRelativePath(
+ Uri(path: path.join(_dataOutputDirectory, file.pathSegments.last))),
+ );
+ }
+}
+
// TODO(https://github.com/dart-lang/package_config/issues/126): Expose this
// logic in package:package_config.
Future<Uri?> packageConfigUri(Uri uri) async {
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index d48ce44..86ad166 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -507,9 +507,9 @@
stderr.writeln('Target OS: $targetOS');
return 128;
}
-
+ final tempDir = Directory.systemTemp.createTempSync();
try {
- await generateNative(
+ final kernelGenerator = KernelGenerator(
kind: format,
sourceFile: sourcePath,
outputFile: args.option('output'),
@@ -520,8 +520,12 @@
debugFile: args.option('save-debugging-info'),
verbose: verbose,
verbosity: args.option('verbosity')!,
- extraOptions: args.multiOption('extra-gen-snapshot-options'),
targetOS: targetOS,
+ tempDir: tempDir,
+ );
+ final snapshotGenerator = await kernelGenerator.generate();
+ await snapshotGenerator.generate(
+ extraOptions: args.multiOption('extra-gen-snapshot-options'),
);
return 0;
} catch (e, st) {
@@ -531,6 +535,8 @@
log.stderr(st.toString());
}
return compileErrorExitCode;
+ } finally {
+ await tempDir.delete(recursive: true);
}
}
}
diff --git a/pkg/dartdev/lib/src/native_assets.dart b/pkg/dartdev/lib/src/native_assets.dart
index 1dbb803..6d47db8 100644
--- a/pkg/dartdev/lib/src/native_assets.dart
+++ b/pkg/dartdev/lib/src/native_assets.dart
@@ -29,11 +29,12 @@
.exists()) {
return (true, <AssetImpl>[]);
}
- final buildResult = await NativeAssetsBuildRunner(
+ final nativeAssetsBuildRunner = NativeAssetsBuildRunner(
// This always runs in JIT mode.
dartExecutable: Uri.file(sdk.dart),
logger: logger(verbose),
- ).build(
+ );
+ final buildResult = await nativeAssetsBuildRunner.build(
workingDirectory: workingDirectory,
// When running in JIT mode, only the host OS needs to be build.
target: Target.current,
@@ -45,9 +46,22 @@
runPackageName: runPackageName,
supportedAssetTypes: [
NativeCodeAsset.type,
+ DataAsset.type,
],
);
- return (buildResult.success, buildResult.assets);
+
+ final linkResult = await nativeAssetsBuildRunner.link(
+ workingDirectory: workingDirectory,
+ target: Target.current,
+ buildMode: BuildModeImpl.release,
+ includeParentEnvironment: true,
+ buildResult: buildResult,
+ );
+
+ return (
+ buildResult.success && linkResult.success,
+ [...buildResult.assets, ...linkResult.assets],
+ );
}
/// Compiles all native assets for host OS in JIT mode, and creates the
@@ -69,8 +83,14 @@
return (false, null);
}
final kernelAssets = KernelAssets([
- for (final asset in assets.whereType<NativeCodeAssetImpl>())
- _targetLocation(asset),
+ ...[
+ for (final asset in assets.whereType<NativeCodeAssetImpl>())
+ _targetLocation(asset),
+ ],
+ ...[
+ for (final asset in assets.whereType<DataAssetImpl>())
+ _dataTargetLocation(asset),
+ ]
]);
final workingDirectory = Directory.current.uri;
@@ -107,6 +127,14 @@
);
}
+KernelAsset _dataTargetLocation(DataAssetImpl asset) {
+ return KernelAsset(
+ id: asset.id,
+ target: Target.current,
+ path: KernelAssetAbsolutePath(asset.file),
+ );
+}
+
Future<bool> warnOnNativeAssets() async {
final workingDirectory = Directory.current.uri;
if (!await File.fromUri(
@@ -118,8 +146,10 @@
try {
final packageLayout =
await PackageLayout.fromRootPackageRoot(workingDirectory);
- final packagesWithNativeAssets =
- await packageLayout.packagesWithNativeAssets;
+ final packagesWithNativeAssets = [
+ ...await packageLayout.packagesWithAssets(Hook.build),
+ ...await packageLayout.packagesWithAssets(Hook.link)
+ ];
if (packagesWithNativeAssets.isEmpty) {
return false;
}
diff --git a/pkg/dartdev/test/native_assets/build_test.dart b/pkg/dartdev/test/native_assets/build_test.dart
index 33810b4..b97cf6e 100644
--- a/pkg/dartdev/test/native_assets/build_test.dart
+++ b/pkg/dartdev/test/native_assets/build_test.dart
@@ -6,6 +6,7 @@
import 'dart:io';
+import 'package:native_assets_cli/native_assets_cli_internal.dart';
import 'package:test/test.dart';
import '../utils.dart';
@@ -116,4 +117,63 @@
expect(result.exitCode, 255);
});
});
+
+ test('dart link assets', timeout: longTimeout, () async {
+ await nativeAssetsTest('drop_dylib_link', (dartAppUri) async {
+ final result = await runDart(
+ arguments: [
+ '--enable-experiment=native-assets',
+ 'build',
+ 'bin/drop_dylib_link.dart',
+ ],
+ workingDirectory: dartAppUri,
+ logger: logger,
+ expectExitCodeZero: false,
+ );
+ expect(result.exitCode, 0);
+
+ // Check that the build directory exists
+ final directory =
+ Directory.fromUri(dartAppUri.resolve('bin/drop_dylib_link'));
+ expect(directory.existsSync(), true);
+
+ // Check that only one dylib is in the final application package
+ final buildFiles = directory.listSync(recursive: true);
+ expect(
+ buildFiles.where((file) => file.path.contains('add')),
+ isNotEmpty,
+ );
+ expect(
+ buildFiles.where((file) => file.path.contains('multiply')),
+ isEmpty,
+ );
+ });
+ });
+
+ test('dart link assets', timeout: longTimeout, () async {
+ await nativeAssetsTest('add_asset_link', (dartAppUri) async {
+ final result = await runDart(
+ arguments: [
+ '--enable-experiment=native-assets',
+ 'build',
+ 'bin/add_asset_link.dart',
+ ],
+ workingDirectory: dartAppUri,
+ logger: logger,
+ expectExitCodeZero: false,
+ );
+ expect(result.exitCode, 0);
+
+ // Check that the build directory exists
+ final directory =
+ Directory.fromUri(dartAppUri.resolve('bin/add_asset_link'));
+ expect(directory.existsSync(), true);
+ final dylib =
+ OSImpl.current.libraryFileName('add', DynamicLoadingBundledImpl());
+ expect(
+ File.fromUri(directory.uri.resolve('lib/$dylib')).existsSync(),
+ true,
+ );
+ });
+ });
}
diff --git a/pkg/dartdev/test/native_assets/helpers.dart b/pkg/dartdev/test/native_assets/helpers.dart
index a536b59..65264f5 100644
--- a/pkg/dartdev/test/native_assets/helpers.dart
+++ b/pkg/dartdev/test/native_assets/helpers.dart
@@ -161,6 +161,8 @@
assert(const [
'dart_app',
'native_add',
+ 'drop_dylib_link',
+ 'add_asset_link',
].contains(packageUnderTest));
return await inTempDir((tempUri) async {
await copyTestProjects(tempUri, logger);
diff --git a/pkg/dartdev/test/native_assets/run_test.dart b/pkg/dartdev/test/native_assets/run_test.dart
index c4906bd1..df72734 100644
--- a/pkg/dartdev/test/native_assets/run_test.dart
+++ b/pkg/dartdev/test/native_assets/run_test.dart
@@ -91,4 +91,64 @@
expect(result.stdout, isNot(contains('build.dart')));
});
});
+
+ test('dart link assets succeeds', timeout: longTimeout, () async {
+ await nativeAssetsTest('drop_dylib_link', (dartAppUri) async {
+ final result = await runDart(
+ arguments: [
+ '--enable-experiment=native-assets',
+ 'run',
+ 'bin/drop_dylib_link.dart',
+ 'add'
+ ],
+ workingDirectory: dartAppUri,
+ logger: logger,
+ expectExitCodeZero: false,
+ );
+ expect(result.exitCode, 0);
+ });
+ });
+
+ test('dart link assets doesnt have treeshaken asset', timeout: longTimeout,
+ () async {
+ await nativeAssetsTest('drop_dylib_link', (dartAppUri) async {
+ try {
+ await runDart(
+ arguments: [
+ '--enable-experiment=native-assets',
+ 'run',
+ 'bin/drop_dylib_link.dart',
+ 'multiply'
+ ],
+ workingDirectory: dartAppUri,
+ logger: logger,
+ expectExitCodeZero: false,
+ );
+ } catch (e) {
+ expect(e, e is ArgumentError);
+ expect(
+ (e as ArgumentError).message.toString(),
+ contains('''
+Couldn't resolve native function 'multiply' in 'package:drop_dylib_link/dylib_multiply' : No asset with id 'package:drop_dylib_link/dylib_multiply' found. Available native assets: package:drop_dylib_link/dylib_add.
+'''),
+ );
+ }
+ });
+ });
+
+ test('dart add asset in linking', timeout: longTimeout, () async {
+ await nativeAssetsTest('add_asset_link', (dartAppUri) async {
+ final result = await runDart(
+ arguments: [
+ '--enable-experiment=native-assets',
+ 'run',
+ 'bin/add_asset_link.dart',
+ ],
+ workingDirectory: dartAppUri,
+ logger: logger,
+ expectExitCodeZero: false,
+ );
+ expect(result.exitCode, 0);
+ });
+ });
}