245498232c
`package:native_assets_cli` has been split up and `package:native_assets_builder` has been renamed. Change-Id: Ic9d5c21bb3c7ecf7924fdee6b4281153ad37fac5 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-win-release-arm64-try,pkg-mac-release-try,pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425504 Reviewed-by: Ivan Inozemtsev <iinozemtsev@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
464 lines
14 KiB
Dart
464 lines
14 KiB
Dart
// Copyright (c) 2020, 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:io';
|
|
|
|
import 'package:code_assets/code_assets.dart' show OS;
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'dart2native.dart';
|
|
import 'src/generate_utils.dart';
|
|
|
|
export 'dart2native.dart' show genKernel, genSnapshotHost;
|
|
|
|
final hostDartAotRuntime = path.join(
|
|
binDir.path,
|
|
'dartaotruntime$executableSuffix',
|
|
);
|
|
|
|
/// The kinds of native executables supported by [KernelGenerator].
|
|
enum Kind {
|
|
aot,
|
|
exe;
|
|
|
|
String appendFileExtension(String fileName) {
|
|
return switch (this) {
|
|
Kind.aot => '$fileName.aot',
|
|
Kind.exe => '$fileName.exe',
|
|
};
|
|
}
|
|
}
|
|
|
|
/// First step of generating a snapshot, generating a kernel.
|
|
///
|
|
/// See also the docs for [_Generator].
|
|
extension type KernelGenerator._(_Generator _generator) {
|
|
KernelGenerator({
|
|
required String genSnapshot,
|
|
required String targetDartAotRuntime,
|
|
required String sourceFile,
|
|
required List<String> defines,
|
|
Kind kind = Kind.exe,
|
|
String? outputFile,
|
|
String? debugFile,
|
|
String? packages,
|
|
OS? targetOS,
|
|
String? depFile,
|
|
String enableExperiment = '',
|
|
bool enableAsserts = false,
|
|
bool verbose = false,
|
|
String verbosity = 'all',
|
|
required Directory tempDir,
|
|
}) : _generator = _Generator(
|
|
genSnapshot: genSnapshot,
|
|
targetDartAotRuntime: targetDartAotRuntime,
|
|
sourceFile: sourceFile,
|
|
defines: defines,
|
|
tempDir: tempDir,
|
|
debugFile: debugFile,
|
|
enableAsserts: enableAsserts,
|
|
enableExperiment: enableExperiment,
|
|
kind: kind,
|
|
outputFile: outputFile,
|
|
packages: packages,
|
|
targetOS: targetOS,
|
|
verbose: verbose,
|
|
verbosity: verbosity,
|
|
depFile: depFile,
|
|
);
|
|
|
|
/// Generate a kernel file,
|
|
///
|
|
/// [recordedUsagesFile] is the path to `recorded_usages.json`, where the
|
|
/// tree-shaking information collected during kernel compilation is stored.
|
|
Future<SnapshotGenerator> generate({
|
|
String? recordedUsagesFile,
|
|
List<String>? extraOptions,
|
|
}) =>
|
|
_generator.generateKernel(
|
|
recordedUsagesFile: recordedUsagesFile,
|
|
extraOptions: extraOptions,
|
|
);
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// 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.
|
|
///
|
|
/// 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;
|
|
|
|
/// The type of executable to be generated, either [Kind.exe] or [Kind.aot].
|
|
final Kind _kind;
|
|
|
|
/// 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 OS? _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;
|
|
|
|
/// The path to the [depfile](https://ninja-build.org/manual.html#_depfile).
|
|
final String? _depFile;
|
|
|
|
/// The path to the `gen_snapshot` tool.
|
|
final String _genSnapshot;
|
|
|
|
/// The path to the `dartaotruntime` for a target platform.
|
|
final String _targetDartAotRuntime;
|
|
|
|
_Generator({
|
|
required String genSnapshot,
|
|
required String targetDartAotRuntime,
|
|
required String sourceFile,
|
|
required List<String> defines,
|
|
required Kind kind,
|
|
String? outputFile,
|
|
String? debugFile,
|
|
String? packages,
|
|
OS? targetOS,
|
|
String? depFile,
|
|
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,
|
|
_depFile = depFile,
|
|
_programKernelFile = path.join(tempDir.path, 'program.dill'),
|
|
_sourcePath = _normalize(sourceFile)!,
|
|
_packages = _normalize(packages),
|
|
_genSnapshot = genSnapshot,
|
|
_targetDartAotRuntime = targetDartAotRuntime {
|
|
if (_kind == Kind.exe) {
|
|
if (_targetOS == null) {
|
|
throw ArgumentError('targetOS must be specified for executables.');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<SnapshotGenerator> generateKernel({
|
|
String? recordedUsagesFile,
|
|
List<String>? extraOptions,
|
|
}) async {
|
|
if (_verbose) {
|
|
if (_targetOS != null) {
|
|
print('Specializing Platform getters for target OS $_targetOS.');
|
|
}
|
|
print('Generating AOT kernel dill.');
|
|
}
|
|
|
|
final kernelResult = await generateKernelHelper(
|
|
hostDartAotRuntime: hostDartAotRuntime,
|
|
sourceFile: _sourcePath,
|
|
kernelFile: _programKernelFile,
|
|
packages: _packages,
|
|
defines: _defines,
|
|
depFile: _depFile,
|
|
fromDill: await isKernelFile(_sourcePath),
|
|
enableAsserts: _enableAsserts,
|
|
enableExperiment: _enableExperiment,
|
|
targetOS: _targetOS,
|
|
extraGenKernelOptions: [
|
|
'--invocation-modes=compile',
|
|
'--verbosity=$_verbosity',
|
|
if (_depFile != null) '--depfile-target=$_outputPath',
|
|
...?extraOptions,
|
|
],
|
|
recordedUsagesFile: recordedUsagesFile,
|
|
aot: true,
|
|
);
|
|
await _forwardOutput(kernelResult);
|
|
if (kernelResult.exitCode != 0) {
|
|
throw StateError('Generating AOT kernel dill failed!');
|
|
}
|
|
return SnapshotGenerator._(this);
|
|
}
|
|
|
|
Future<void> generateSnapshotWithAssets({
|
|
String? nativeAssets,
|
|
required List<String> extraOptions,
|
|
}) async {
|
|
final kernelFile = await _concatenateAssetsToKernel(nativeAssets);
|
|
await _generateSnapshot(extraOptions, kernelFile);
|
|
}
|
|
|
|
String get _outputPath {
|
|
final sourceWithoutDartOrDill = _sourcePath.replaceFirst(
|
|
RegExp(r'\.(dart|dill)$'),
|
|
'',
|
|
);
|
|
return _normalize(
|
|
_outputFile ?? _kind.appendFileExtension(sourceWithoutDartOrDill),
|
|
)!;
|
|
}
|
|
|
|
Future<void> _generateSnapshot(
|
|
List<String> extraOptions,
|
|
String kernelFile,
|
|
) async {
|
|
final outputPath = _outputPath;
|
|
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(
|
|
_genSnapshot,
|
|
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(
|
|
_targetDartAotRuntime, snapshotFile, outputPath, _targetOS!);
|
|
|
|
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) {
|
|
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');
|
|
final kernelResult = await generateKernelHelper(
|
|
hostDartAotRuntime: hostDartAotRuntime,
|
|
kernelFile: nativeAssetsDillFile,
|
|
packages: _packages,
|
|
defines: _defines,
|
|
enableAsserts: _enableAsserts,
|
|
enableExperiment: _enableExperiment,
|
|
targetOS: _targetOS,
|
|
extraGenKernelOptions: [
|
|
'--invocation-modes=compile',
|
|
'--verbosity=$_verbosity',
|
|
],
|
|
nativeAssets: nativeAssets,
|
|
aot: true,
|
|
);
|
|
await _forwardOutput(kernelResult);
|
|
if (kernelResult.exitCode != 0) {
|
|
throw StateError('Generating AOT kernel dill failed!');
|
|
}
|
|
final kernelFile = path.join(_tempDir.path, 'kernel.dill');
|
|
final programKernelBytes = await File(_programKernelFile).readAsBytes();
|
|
final nativeAssetKernelBytes =
|
|
await File(nativeAssetsDillFile).readAsBytes();
|
|
await File(kernelFile).writeAsBytes(
|
|
[
|
|
...programKernelBytes,
|
|
...nativeAssetKernelBytes,
|
|
],
|
|
flush: true,
|
|
);
|
|
return kernelFile;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 its [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`.
|
|
///
|
|
/// [recordedUsagesFile] is the path to `recorded_usages.json`.
|
|
Future<void> generateKernel({
|
|
required String sourceFile,
|
|
required String outputFile,
|
|
required List<String> 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 implications of using a product mode kernel
|
|
// in a non-product runtime?
|
|
bool product = true,
|
|
bool verbose = false,
|
|
String? nativeAssets,
|
|
String? recordedUsagesFile,
|
|
String? depFile,
|
|
List<String>? extraOptions,
|
|
}) async {
|
|
final sourcePath = _normalize(sourceFile)!;
|
|
final outputPath = _normalize(outputFile)!;
|
|
packages = _normalize(packages);
|
|
|
|
final kernelResult = await generateKernelHelper(
|
|
hostDartAotRuntime: hostDartAotRuntime,
|
|
sourceFile: sourcePath,
|
|
kernelFile: outputPath,
|
|
packages: packages,
|
|
defines: defines,
|
|
linkPlatform: linkPlatform,
|
|
embedSources: embedSources,
|
|
fromDill: await isKernelFile(sourcePath),
|
|
enableExperiment: enableExperiment,
|
|
depFile: depFile,
|
|
extraGenKernelOptions: [
|
|
'--invocation-modes=compile',
|
|
'--verbosity=$verbosity',
|
|
...?extraOptions,
|
|
],
|
|
nativeAssets: nativeAssets,
|
|
recordedUsagesFile: recordedUsagesFile,
|
|
product: product,
|
|
);
|
|
await _forwardOutput(kernelResult);
|
|
if (kernelResult.exitCode != 0) {
|
|
throw StateError('Generating kernel failed!');
|
|
}
|
|
}
|
|
|
|
String? _normalize(String? p) {
|
|
if (p == null) return null;
|
|
return path.canonicalize(path.normalize(p));
|
|
}
|
|
|
|
/// Forward the output of [result] to stdout and stderr.
|
|
Future<void> _forwardOutput(ProcessResult result) async {
|
|
if (result.stdout case final resultOutput
|
|
when processOutputIsNotEmpty(resultOutput)) {
|
|
final needsNewLine =
|
|
resultOutput is! String || !resultOutput.endsWith('\n');
|
|
if (result.exitCode == 0) {
|
|
stdout.write(resultOutput);
|
|
if (needsNewLine) stdout.writeln();
|
|
await stdout.flush();
|
|
} else {
|
|
stderr.write(resultOutput);
|
|
if (needsNewLine) stderr.writeln();
|
|
}
|
|
}
|
|
if (result.stderr case final resultErrorOutput
|
|
when processOutputIsNotEmpty(resultErrorOutput)) {
|
|
final needsNewLine =
|
|
resultErrorOutput is! String || !resultErrorOutput.endsWith('\n');
|
|
stderr.write(resultErrorOutput);
|
|
if (needsNewLine) {
|
|
stderr.writeln();
|
|
}
|
|
await stderr.flush();
|
|
}
|
|
}
|