Unverified Commit 82f969ff authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Move AOT snapshotting to Snapshotter class (#17015)

This moves AOT snapshotting out of build_aot.dart and into the
Snapshotter class. It also adds unit tests for iOS debug, profile, and
release builds.
parent adf97fb5
......@@ -9,13 +9,18 @@ import 'package:crypto/crypto.dart' show md5;
import 'package:meta/meta.dart';
import 'package:quiver/core.dart' show hash2;
import '../android/android_sdk.dart';
import '../artifacts.dart';
import '../build_info.dart';
import '../compile.dart';
import '../dart/package_map.dart';
import '../globals.dart';
import '../ios/mac.dart';
import '../version.dart';
import 'context.dart';
import 'file_system.dart';
import 'process.dart';
import 'utils.dart' show toTitleCase;
GenSnapshot get genSnapshot => context[GenSnapshot];
......@@ -203,8 +208,349 @@ class Snapshotter {
}
/// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script.
Future<Null> buildAotSnapshot() async {
throw new UnimplementedError('AOT snapshotting not yet implemented');
Future<int> buildAotSnapshot({
@required TargetPlatform platform,
@required BuildMode buildMode,
@required String mainPath,
@required String depfilePath,
@required String packagesPath,
@required String outputPath,
@required bool interpreter,
@required bool previewDart2,
@required bool preferSharedLibrary,
List<String> extraFrontEndOptions: const <String>[],
List<String> extraGenSnapshotOptions: const <String>[],
}) async {
if (!isAotBuildMode(buildMode) && !interpreter) {
printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.');
return -1;
}
if (!(platform == TargetPlatform.android_arm ||
platform == TargetPlatform.android_arm64 ||
platform == TargetPlatform.ios)) {
printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
return -2;
}
final Directory outputDir = fs.directory(outputPath);
outputDir.createSync(recursive: true);
final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data');
final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr');
final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
final String dependencies = fs.path.join(outputDir.path, 'snapshot.d');
final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
final String assemblySo = fs.path.join(outputDir.path, 'app.so');
final bool compileToSharedLibrary =
preferSharedLibrary && androidSdk.ndkCompiler != null;
if (preferSharedLibrary && !compileToSharedLibrary) {
printStatus(
'Could not find NDK compiler. Not building in shared library mode');
}
final String vmEntryPoints = artifacts.getArtifactPath(Artifact.dartVmEntryPointsTxt, platform, buildMode);
assert(vmEntryPoints != null);
final String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode);
assert(ioEntryPoints != null);
final List<String> entryPointsJsonFiles = <String>[];
if (previewDart2 && !interpreter) {
entryPointsJsonFiles.addAll(<String>[
artifacts.getArtifactPath(Artifact.entryPointsJson, platform, buildMode),
artifacts.getArtifactPath(Artifact.entryPointsExtraJson, platform, buildMode),
]);
}
final PackageMap packageMap = new PackageMap(packagesPath);
final String packageMapError = packageMap.checkValid();
if (packageMapError != null) {
printError(packageMapError);
return -3;
}
final String skyEnginePkg = _getPackagePath(packageMap, 'sky_engine');
final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
final List<String> inputPaths = <String>[
vmEntryPoints,
ioEntryPoints,
uiPath,
vmServicePath,
mainPath,
];
inputPaths.addAll(entryPointsJsonFiles);
final Set<String> outputPaths = new Set<String>();
// These paths are used only on iOS.
String snapshotDartIOS;
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
if (compileToSharedLibrary) {
outputPaths.add(assemblySo);
} else {
outputPaths.addAll(<String>[
vmSnapshotData,
isolateSnapshotData,
]);
}
break;
case TargetPlatform.ios:
snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
inputPaths.add(snapshotDartIOS);
break;
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(false);
}
final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
if (missingInputs.isNotEmpty) {
printError('Missing input files: $missingInputs');
return -4;
}
final List<String> genSnapshotArgs = <String>[
'--vm_snapshot_data=$vmSnapshotData',
'--isolate_snapshot_data=$isolateSnapshotData',
'--url_mapping=dart:ui,$uiPath',
'--url_mapping=dart:vmservice_io,$vmServicePath',
'--dependencies=$dependencies',
];
if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty)
printTrace('Extra front-end options: $extraFrontEndOptions');
if ((extraGenSnapshotOptions != null) && extraGenSnapshotOptions.isNotEmpty) {
printTrace('Extra gen-snapshot options: $extraGenSnapshotOptions');
genSnapshotArgs.addAll(extraGenSnapshotOptions);
}
if (!interpreter) {
genSnapshotArgs.add('--embedder_entry_points_manifest=$vmEntryPoints');
genSnapshotArgs.add('--embedder_entry_points_manifest=$ioEntryPoints');
}
// iOS symbols used to load snapshot data in the engine.
const String kVmSnapshotData = 'kDartVmSnapshotData';
const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
// iOS snapshot generated files, compiled object files.
final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
final String kApplicationKernelPath = fs.path.join(getBuildDirectory(), 'app.dill');
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
if (compileToSharedLibrary) {
genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');
genSnapshotArgs.add('--assembly=$assembly');
outputPaths.add(assemblySo);
} else {
genSnapshotArgs.addAll(<String>[
'--snapshot_kind=app-aot-blobs',
'--vm_snapshot_instructions=$vmSnapshotInstructions',
'--isolate_snapshot_instructions=$isolateSnapshotInstructions',
]);
}
if (platform == TargetPlatform.android_arm) {
genSnapshotArgs.addAll(<String>[
'--no-sim-use-hardfp', // Android uses the softfloat ABI.
'--no-use-integer-division', // Not supported by the Pixel in 32-bit mode.
]);
}
break;
case TargetPlatform.ios:
if (interpreter) {
genSnapshotArgs.add('--snapshot_kind=core');
genSnapshotArgs.add(snapshotDartIOS);
outputPaths.addAll(<String>[
kVmSnapshotDataO,
kIsolateSnapshotDataO,
]);
} else {
genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');
genSnapshotArgs.add('--assembly=$assembly');
outputPaths.add(assemblyO);
}
break;
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(false);
}
if (buildMode != BuildMode.release) {
genSnapshotArgs.addAll(<String>[
'--no-checked',
'--conditional_directives',
]);
}
final String entryPoint = mainPath;
final SnapshotType snapshotType = new SnapshotType(platform, buildMode);
Future<Fingerprint> makeFingerprint() async {
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
..add(entryPoint)
..addAll(outputPaths);
return Snapshotter.createFingerprint(snapshotType, entryPoint, snapshotInputPaths);
}
final File fingerprintFile = fs.file('$dependencies.fingerprint');
final List<File> fingerprintFiles = <File>[fingerprintFile, fs.file(dependencies)]
..addAll(inputPaths.map(fs.file))
..addAll(outputPaths.map(fs.file));
if (fingerprintFiles.every((File file) => file.existsSync())) {
try {
final String json = await fingerprintFile.readAsString();
final Fingerprint oldFingerprint = new Fingerprint.fromJson(json);
if (oldFingerprint == await makeFingerprint()) {
printStatus('Skipping AOT snapshot build. Fingerprint match.');
return 0;
}
} catch (e) {
// Log exception and continue, this step is a performance improvement only.
printTrace('Rebuilding snapshot due to fingerprint check error: $e');
}
}
if (previewDart2) {
final CompilerOutput compilerOutput = await kernelCompiler.compile(
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
mainPath: mainPath,
outputFilePath: kApplicationKernelPath,
depFilePath: dependencies,
extraFrontEndOptions: extraFrontEndOptions,
linkPlatformKernelIn: true,
aot: !interpreter,
entryPointsJsonFiles: entryPointsJsonFiles,
trackWidgetCreation: false,
);
mainPath = compilerOutput?.outputFilename;
if (mainPath == null) {
printError('Compiler terminated unexpectedly.');
return -5;
}
// Write path to frontend_server, since things need to be re-generated when
// that changes.
await outputDir.childFile('frontend_server.d')
.writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n');
genSnapshotArgs.addAll(<String>[
'--reify-generic-functions',
'--strong',
]);
}
genSnapshotArgs.add(mainPath);
final int genSnapshotExitCode = await genSnapshot.run(
snapshotType: new SnapshotType(platform, buildMode),
packagesPath: packageMap.packagesPath,
depfilePath: dependencies,
additionalArgs: genSnapshotArgs,
);
if (genSnapshotExitCode != 0) {
printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
return -6;
}
// Write path to gen_snapshot, since snapshots have to be re-generated when we roll
// the Dart SDK.
await outputDir.childFile('gen_snapshot.d').writeAsString('snapshot.d: $genSnapshot\n');
// On iOS, we use Xcode to compile the snapshot into a dynamic library that the
// end-developer can link into their app.
if (platform == TargetPlatform.ios) {
printStatus('Building App.framework...');
const List<String> commonBuildOptions = const <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
if (interpreter) {
await fs.file(vmSnapshotData).rename(fs.path.join(outputDir.path, kVmSnapshotData));
await fs.file(isolateSnapshotData).rename(fs.path.join(outputDir.path, kIsolateSnapshotData));
await xxd.run(
<String>['--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)],
workingDirectory: outputDir.path,
);
await xxd.run(
<String>['--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)],
workingDirectory: outputDir.path,
);
await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
} else {
await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', assembly, '-o', assemblyO]));
}
final String frameworkDir = fs.path.join(outputDir.path, 'App.framework');
fs.directory(frameworkDir).createSync(recursive: true);
final String appLib = fs.path.join(frameworkDir, 'App');
final List<String> linkArgs = commonBuildOptions.toList()..addAll(<String>[
'-dynamiclib',
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
'-o', appLib,
]);
if (interpreter) {
linkArgs.add(kVmSnapshotDataO);
linkArgs.add(kIsolateSnapshotDataO);
} else {
linkArgs.add(assemblyO);
}
await xcode.clang(linkArgs);
} else {
if (compileToSharedLibrary) {
// A word of warning: Instead of compiling via two steps, to a .o file and
// then to a .so file we use only one command. When using two commands
// gcc will end up putting a .eh_frame and a .debug_frame into the shared
// library. Without stripping .debug_frame afterwards, unwinding tools
// based upon libunwind use just one and ignore the contents of the other
// (which causes it to not look into the other section and therefore not
// find the correct unwinding information).
await runCheckedAsync(<String>[androidSdk.ndkCompiler]
..addAll(androidSdk.ndkCompilerArgs)
..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assembly ]));
}
}
// Compute and record build fingerprint.
try {
final Fingerprint fingerprint = await makeFingerprint();
await fingerprintFile.writeAsString(fingerprint.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
printStatus('Error during AOT snapshot fingerprinting: $e\n$s');
}
return 0;
}
String _getPackagePath(PackageMap packageMap, String package) {
return fs.path.dirname(fs.path.fromUri(packageMap.map[package]));
}
Future<bool> _isBuildRequired(SnapshotType type, String outputSnapshotPath, String depfilePath, String mainPath, String fingerprintPath) async {
......
......@@ -4,28 +4,17 @@
import 'dart:async';
import '../android/android_sdk.dart';
import '../artifacts.dart';
import '../base/build.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../compile.dart';
import '../dart/package_map.dart';
import '../globals.dart';
import '../ios/mac.dart' show xcode, xxd;
import '../resident_runner.dart';
import '../runner/flutter_command.dart';
import 'build.dart';
// Files generated by the ahead-of-time snapshot builder.
const List<String> kAotSnapshotFiles = const <String>[
'vm_snapshot_data', 'vm_snapshot_instr', 'isolate_snapshot_data', 'isolate_snapshot_instr',
];
class BuildAotCommand extends BuildSubCommand {
BuildAotCommand({bool verboseHelp: false}) {
usesTargetOption();
......@@ -102,10 +91,6 @@ class BuildAotCommand extends BuildSubCommand {
}
}
String _getPackagePath(PackageMap packageMap, String package) {
return fs.path.dirname(packageMap.map[package].toFilePath());
}
/// Build an AOT snapshot. Return null (and log to `printError`) if the method
/// fails.
Future<String> buildAotSnapshot(
......@@ -121,362 +106,28 @@ Future<String> buildAotSnapshot(
}) async {
outputPath ??= getAotBuildDirectory();
try {
return _buildAotSnapshot(
mainPath,
platform,
buildMode,
final Snapshotter snapshotter = new Snapshotter();
final int snapshotExitCode = await snapshotter.buildAotSnapshot(
platform: platform,
buildMode: buildMode,
mainPath: mainPath,
depfilePath: 'depFilePathGoesHere',
packagesPath: PackageMap.globalPackagesPath,
outputPath: outputPath,
interpreter: interpreter,
previewDart2: previewDart2,
preferSharedLibrary: preferSharedLibrary,
extraFrontEndOptions: extraFrontEndOptions,
extraGenSnapshotOptions: extraGenSnapshotOptions,
preferSharedLibrary: preferSharedLibrary,
);
} on String catch (error) {
// Catch the String exceptions thrown from the `runCheckedSync` methods below.
printError(error);
return null;
}
}
// TODO(cbracken): split AOT and Assembly AOT snapshotting logic and migrate to Snapshotter class.
Future<String> _buildAotSnapshot(
String mainPath,
TargetPlatform platform,
BuildMode buildMode, {
String outputPath,
bool interpreter: false,
bool previewDart2: false,
List<String> extraFrontEndOptions,
List<String> extraGenSnapshotOptions,
bool preferSharedLibrary: false,
}) async {
outputPath ??= getAotBuildDirectory();
if (!isAotBuildMode(buildMode) && !interpreter) {
printError('${toTitleCase(getModeName(buildMode))} mode does not support AOT compilation.');
return null;
}
if (!(platform == TargetPlatform.android_arm ||
platform == TargetPlatform.android_arm64 ||
platform == TargetPlatform.ios)) {
printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
if (snapshotExitCode != 0) {
printError('Snapshotting exited with non-zero exit code: $snapshotExitCode');
return null;
}
final Directory outputDir = fs.directory(outputPath);
outputDir.createSync(recursive: true);
final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data');
final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr');
final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
final String dependencies = fs.path.join(outputDir.path, 'snapshot.d');
final String assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
final String assemblySo = fs.path.join(outputDir.path, 'app.so');
final bool compileToSharedLibrary =
preferSharedLibrary && androidSdk.ndkCompiler != null;
if (preferSharedLibrary && !compileToSharedLibrary) {
printStatus(
'Could not find NDK compiler. Not building in shared library mode');
}
final String vmEntryPoints = artifacts.getArtifactPath(
Artifact.dartVmEntryPointsTxt,
platform,
buildMode,
);
final String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode);
final List<String> entryPointsJsonFiles = <String>[];
if (previewDart2 && !interpreter) {
entryPointsJsonFiles.addAll(<String>[
artifacts.getArtifactPath(Artifact.entryPointsJson, platform, buildMode),
artifacts.getArtifactPath(Artifact.entryPointsExtraJson, platform, buildMode),
]);
}
final PackageMap packageMap = new PackageMap(PackageMap.globalPackagesPath);
final String packageMapError = packageMap.checkValid();
if (packageMapError != null) {
printError(packageMapError);
return null;
}
final String skyEnginePkg = _getPackagePath(packageMap, 'sky_engine');
final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
final List<String> inputPaths = <String>[
vmEntryPoints,
ioEntryPoints,
uiPath,
vmServicePath,
mainPath,
];
inputPaths.addAll(entryPointsJsonFiles);
final Set<String> outputPaths = new Set<String>();
// These paths are used only on iOS.
String snapshotDartIOS;
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
if (compileToSharedLibrary) {
outputPaths.add(assemblySo);
} else {
outputPaths.addAll(<String>[
vmSnapshotData,
isolateSnapshotData,
]);
}
break;
case TargetPlatform.ios:
snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
inputPaths.add(snapshotDartIOS);
break;
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(false);
}
final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
if (missingInputs.isNotEmpty) {
printError('Missing input files: $missingInputs');
return null;
}
final List<String> genSnapshotArgs = <String>[
'--vm_snapshot_data=$vmSnapshotData',
'--isolate_snapshot_data=$isolateSnapshotData',
'--url_mapping=dart:ui,$uiPath',
'--url_mapping=dart:vmservice_io,$vmServicePath',
'--dependencies=$dependencies',
];
if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty)
printTrace('Extra front-end options: $extraFrontEndOptions');
if ((extraGenSnapshotOptions != null) && extraGenSnapshotOptions.isNotEmpty) {
printTrace('Extra gen-snapshot options: $extraGenSnapshotOptions');
genSnapshotArgs.addAll(extraGenSnapshotOptions);
}
if (!interpreter) {
genSnapshotArgs.add('--embedder_entry_points_manifest=$vmEntryPoints');
genSnapshotArgs.add('--embedder_entry_points_manifest=$ioEntryPoints');
}
// iOS symbols used to load snapshot data in the engine.
const String kVmSnapshotData = 'kDartVmSnapshotData';
const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
// iOS snapshot generated files, compiled object files.
final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
final String kApplicationKernelPath = fs.path.join(getBuildDirectory(), 'app.dill');
switch (platform) {
case TargetPlatform.android_arm:
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
if (compileToSharedLibrary) {
genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');
genSnapshotArgs.add('--assembly=$assembly');
outputPaths.add(assemblySo);
} else {
genSnapshotArgs.addAll(<String>[
'--snapshot_kind=app-aot-blobs',
'--vm_snapshot_instructions=$vmSnapshotInstructions',
'--isolate_snapshot_instructions=$isolateSnapshotInstructions',
]);
}
if (platform == TargetPlatform.android_arm) {
genSnapshotArgs.addAll(<String>[
'--no-sim-use-hardfp', // Android uses the softfloat ABI.
'--no-use-integer-division', // Not supported by the Pixel in 32-bit mode.
]);
}
break;
case TargetPlatform.ios:
if (interpreter) {
genSnapshotArgs.add('--snapshot_kind=core');
genSnapshotArgs.add(snapshotDartIOS);
outputPaths.addAll(<String>[
kVmSnapshotDataO,
kIsolateSnapshotDataO,
]);
} else {
genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');
genSnapshotArgs.add('--assembly=$assembly');
outputPaths.add(assemblyO);
}
break;
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.fuchsia:
case TargetPlatform.tester:
assert(false);
}
if (buildMode != BuildMode.release) {
genSnapshotArgs.addAll(<String>[
'--no-checked',
'--conditional_directives',
]);
}
final String entryPoint = mainPath;
final SnapshotType snapshotType = new SnapshotType(platform, buildMode);
Future<Fingerprint> makeFingerprint() async {
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
..add(entryPoint)
..addAll(outputPaths);
return Snapshotter.createFingerprint(snapshotType, entryPoint, snapshotInputPaths);
}
final File fingerprintFile = fs.file('$dependencies.fingerprint');
final List<File> fingerprintFiles = <File>[fingerprintFile, fs.file(dependencies)]
..addAll(inputPaths.map(fs.file))
..addAll(outputPaths.map(fs.file));
if (fingerprintFiles.every((File file) => file.existsSync())) {
try {
final String json = await fingerprintFile.readAsString();
final Fingerprint oldFingerprint = new Fingerprint.fromJson(json);
if (oldFingerprint == await makeFingerprint()) {
printStatus('Skipping AOT snapshot build. Fingerprint match.');
return outputPath;
}
} catch (e) {
// Log exception and continue, this step is a performance improvement only.
printTrace('Rebuilding snapshot due to fingerprint check error: $e');
}
}
if (previewDart2) {
final CompilerOutput compilerOutput = await kernelCompiler.compile(
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
mainPath: mainPath,
outputFilePath: kApplicationKernelPath,
depFilePath: dependencies,
extraFrontEndOptions: extraFrontEndOptions,
linkPlatformKernelIn : true,
aot : !interpreter,
entryPointsJsonFiles: entryPointsJsonFiles,
trackWidgetCreation: false,
);
mainPath = compilerOutput?.outputFilename;
if (mainPath == null) {
printError('Compiler terminated unexpectedly.');
return null;
}
// Write path to frontend_server, since things need to be re-generated when
// that changes.
await outputDir.childFile('frontend_server.d')
.writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n');
genSnapshotArgs.addAll(<String>[
'--reify-generic-functions',
'--strong',
]);
}
genSnapshotArgs.add(mainPath);
final int genSnapshotExitCode = await genSnapshot.run(
snapshotType: new SnapshotType(platform, buildMode),
packagesPath: packageMap.packagesPath,
depfilePath: dependencies,
additionalArgs: genSnapshotArgs,
);
if (genSnapshotExitCode != 0) {
printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
} on String catch (error) {
// Catch the String exceptions thrown from the `runCheckedSync` methods below.
printError(error);
return null;
}
// Write path to gen_snapshot, since snapshots have to be re-generated when we roll
// the Dart SDK.
await outputDir.childFile('gen_snapshot.d').writeAsString('snapshot.d: $genSnapshot\n');
// On iOS, we use Xcode to compile the snapshot into a dynamic library that the
// end-developer can link into their app.
if (platform == TargetPlatform.ios) {
printStatus('Building App.framework...');
const List<String> commonBuildOptions = const <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
if (interpreter) {
await fs.file(vmSnapshotData).rename(fs.path.join(outputDir.path, kVmSnapshotData));
await fs.file(isolateSnapshotData).rename(fs.path.join(outputDir.path, kIsolateSnapshotData));
await xxd.run(
<String>['--include', kVmSnapshotData, fs.path.basename(kVmSnapshotDataC)],
workingDirectory: outputDir.path,
);
await xxd.run(
<String>['--include', kIsolateSnapshotData, fs.path.basename(kIsolateSnapshotDataC)],
workingDirectory: outputDir.path,
);
await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', kVmSnapshotDataC, '-o', kVmSnapshotDataO]));
await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', kIsolateSnapshotDataC, '-o', kIsolateSnapshotDataO]));
} else {
await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', assembly, '-o', assemblyO]));
}
final String frameworkDir = fs.path.join(outputDir.path, 'App.framework');
fs.directory(frameworkDir).createSync(recursive: true);
final String appLib = fs.path.join(frameworkDir, 'App');
final List<String> linkArgs = commonBuildOptions.toList()..addAll(<String>[
'-dynamiclib',
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
'-install_name', '@rpath/App.framework/App',
'-o', appLib,
]);
if (interpreter) {
linkArgs.add(kVmSnapshotDataO);
linkArgs.add(kIsolateSnapshotDataO);
} else {
linkArgs.add(assemblyO);
}
await xcode.clang(linkArgs);
} else {
if (compileToSharedLibrary) {
// A word of warning: Instead of compiling via two steps, to a .o file and
// then to a .so file we use only one command. When using two commands
// gcc will end up putting a .eh_frame and a .debug_frame into the shared
// library. Without stripping .debug_frame afterwards, unwinding tools
// based upon libunwind use just one and ignore the contents of the other
// (which causes it to not look into the other section and therefore not
// find the correct unwinding information).
await runCheckedAsync(<String>[androidSdk.ndkCompiler]
..addAll(androidSdk.ndkCompilerArgs)
..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assembly ]));
}
}
// Compute and record build fingerprint.
try {
final Fingerprint fingerprint = await makeFingerprint();
await fingerprintFile.writeAsString(fingerprint.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
printStatus('Error during AOT snapshot fingerprinting: $e\n$s');
}
return outputPath;
}
......@@ -9,9 +9,11 @@ import 'dart:convert' show json;
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/base/build.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
......@@ -20,6 +22,8 @@ import '../src/context.dart';
class MockFlutterVersion extends Mock implements FlutterVersion {}
class MockArtifacts extends Mock implements Artifacts {}
class MockXcode extends Mock implements Xcode {}
class MockXxd extends Mock implements Xxd {}
class _FakeGenSnapshot implements GenSnapshot {
_FakeGenSnapshot({
......@@ -66,6 +70,29 @@ class _FakeGenSnapshot implements GenSnapshot {
}
}
class _FakeKernelCompiler implements KernelCompiler {
CompilerOutput output;
@override
Future<CompilerOutput> compile({
String sdkRoot,
String mainPath,
String outputFilePath,
String depFilePath,
bool linkPlatformKernelIn: false,
bool aot: false,
List<String> entryPointsJsonFiles,
bool trackWidgetCreation: false,
List<String> extraFrontEndOptions,
String incrementalCompilerByteStorePath,
String packagesPath,
List<String> fileSystemRoots,
String fileSystemScheme,
}) async {
return output;
}
}
void main() {
group('SnapshotType', () {
test('throws, if build mode is null', () {
......@@ -325,7 +352,7 @@ void main() {
}, overrides: contextOverrides);
});
group('Snapshotter', () {
group('Snapshotter - Script Snapshots', () {
const String kVersion = '123456abcdef';
const String kIsolateSnapshotData = 'isolate_snapshot.bin';
const String kVmSnapshotData = 'vm_isolate_snapshot.bin';
......@@ -382,8 +409,8 @@ void main() {
final Map<String, dynamic> jsonObject = json.decode(fs.file('output.snapshot.d.fingerprint').readAsStringSync());
expect(jsonObject['properties']['entryPoint'], entryPoint);
expect(jsonObject['files'], hasLength(checksums.length + 2));
checksums.forEach((String path, String checksum) {
expect(jsonObject['files'][path], checksum);
checksums.forEach((String filePath, String checksum) {
expect(jsonObject['files'][filePath], checksum);
});
expect(jsonObject['files'][kVmSnapshotData], '2ec34912477a46c03ddef07e8b909b46');
expect(jsonObject['files'][kIsolateSnapshotData], '621b3844bb7d4d17d2cfc5edf9a91c4c');
......@@ -569,4 +596,205 @@ void main() {
}, overrides: contextOverrides);
});
});
group('Snapshotter - iOS AOT', () {
const String kVmEntrypoints = 'dart_vm_entry_points.txt';
const String kIoEntries = 'dart_io_entries.txt';
const String kSnapshotDart = 'snapshot.dart';
const String kEntrypointsJson = 'entry_points.json';
const String kEntrypointsExtraJson = 'entry_points_extra.json';
String skyEnginePath;
_FakeGenSnapshot genSnapshot;
_FakeKernelCompiler kernelCompiler;
MemoryFileSystem fs;
Snapshotter snapshotter;
MockArtifacts mockArtifacts;
MockXcode mockXcode;
MockXxd mockXxd;
setUp(() async {
fs = new MemoryFileSystem();
fs.file(kVmEntrypoints).createSync();
fs.file(kIoEntries).createSync();
fs.file(kSnapshotDart).createSync();
fs.file(kEntrypointsJson).createSync();
fs.file(kEntrypointsExtraJson).createSync();
fs.file('.packages').writeAsStringSync('sky_engine:file:///flutter/bin/cache/pkg/sky_engine/lib/');
skyEnginePath = fs.path.fromUri(new Uri.file('/flutter/bin/cache/pkg/sky_engine'));
fs.directory(fs.path.join(skyEnginePath, 'lib', 'ui')).createSync(recursive: true);
fs.directory(fs.path.join(skyEnginePath, 'sdk_ext')).createSync(recursive: true);
fs.file(fs.path.join(skyEnginePath, '.packages')).createSync();
fs.file(fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')).createSync();
fs.file(fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')).createSync();
genSnapshot = new _FakeGenSnapshot();
kernelCompiler = new _FakeKernelCompiler();
snapshotter = new Snapshotter();
mockArtifacts = new MockArtifacts();
mockXcode = new MockXcode();
mockXxd = new MockXxd();
for (BuildMode mode in BuildMode.values) {
when(mockArtifacts.getArtifactPath(Artifact.dartVmEntryPointsTxt, TargetPlatform.ios, mode)).thenReturn(kVmEntrypoints);
when(mockArtifacts.getArtifactPath(Artifact.dartIoEntriesTxt, TargetPlatform.ios, mode)).thenReturn(kIoEntries);
when(mockArtifacts.getArtifactPath(Artifact.snapshotDart, TargetPlatform.ios, mode)).thenReturn(kSnapshotDart);
when(mockArtifacts.getArtifactPath(Artifact.entryPointsJson, TargetPlatform.ios, mode)).thenReturn(kEntrypointsJson);
when(mockArtifacts.getArtifactPath(Artifact.entryPointsExtraJson, TargetPlatform.ios, mode)).thenReturn(kEntrypointsExtraJson);
}
});
final Map<Type, Generator> contextOverrides = <Type, Generator>{
Artifacts: () => mockArtifacts,
FileSystem: () => fs,
GenSnapshot: () => genSnapshot,
KernelCompiler: () => kernelCompiler,
Xcode: () => mockXcode,
Xxd: () => mockXxd,
};
testUsingContext('builds iOS debug AOT snapshot', () async {
fs.file('main.dart').writeAsStringSync('void main() {}');
final String outputPath = fs.path.join('build', 'foo');
fs.directory(outputPath).createSync(recursive: true);
kernelCompiler.output = const CompilerOutput('main.dill', 0);
genSnapshot.outputs = <String, String>{
fs.path.join(outputPath, 'vm_snapshot_data'): '',
fs.path.join(outputPath, 'vm_snapshot_instr'): '',
fs.path.join(outputPath, 'isolate_snapshot_data'): '',
fs.path.join(outputPath, 'isolate_snapshot_instr'): '',
fs.path.join(outputPath, 'snapshot.d'): '',
fs.path.join(outputPath, 'snapshot_assembly.S'): '',
};
final int genSnapshotExitCode = await snapshotter.buildAotSnapshot(
platform: TargetPlatform.ios,
buildMode: BuildMode.debug,
mainPath: 'main.dart',
depfilePath: fs.path.join(outputPath, 'snapshot.d'),
packagesPath: '.packages',
outputPath: outputPath,
interpreter: true,
preferSharedLibrary: false,
previewDart2: true,
);
expect(genSnapshotExitCode, 0);
expect(genSnapshot.callCount, 1);
expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
expect(genSnapshot.snapshotType.mode, BuildMode.debug);
expect(genSnapshot.packagesPath, '.packages');
expect(genSnapshot.depfilePath, fs.path.join(outputPath, 'snapshot.d'));
expect(genSnapshot.additionalArgs, <String>[
'--vm_snapshot_data=${fs.path.join(outputPath, 'vm_snapshot_data')}',
'--isolate_snapshot_data=${fs.path.join(outputPath, 'isolate_snapshot_data')}',
'--url_mapping=dart:ui,${fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')}',
'--url_mapping=dart:vmservice_io,${fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')}',
'--dependencies=${fs.path.join(outputPath, 'snapshot.d')}',
'--snapshot_kind=core',
'snapshot.dart',
'--no-checked',
'--conditional_directives',
'--reify-generic-functions',
'--strong',
'main.dill',
]);
}, overrides: contextOverrides);
testUsingContext('builds iOS profile AOT snapshot', () async {
fs.file('main.dart').writeAsStringSync('void main() {}');
final String outputPath = fs.path.join('build', 'foo');
fs.directory(outputPath).createSync(recursive: true);
kernelCompiler.output = const CompilerOutput('main.dill', 0);
genSnapshot.outputs = <String, String>{
fs.path.join(outputPath, 'snapshot_assembly.S'): '',
fs.path.join(outputPath, 'snapshot.d'): '',
};
final int genSnapshotExitCode = await snapshotter.buildAotSnapshot(
platform: TargetPlatform.ios,
buildMode: BuildMode.profile,
mainPath: 'main.dart',
depfilePath: fs.path.join(outputPath, 'snapshot.d'),
packagesPath: '.packages',
outputPath: outputPath,
interpreter: false,
preferSharedLibrary: false,
previewDart2: true,
);
expect(genSnapshotExitCode, 0);
expect(genSnapshot.callCount, 1);
expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
expect(genSnapshot.snapshotType.mode, BuildMode.profile);
expect(genSnapshot.packagesPath, '.packages');
expect(genSnapshot.depfilePath, fs.path.join(outputPath, 'snapshot.d'));
expect(genSnapshot.additionalArgs, <String>[
'--vm_snapshot_data=${fs.path.join(outputPath, 'vm_snapshot_data')}',
'--isolate_snapshot_data=${fs.path.join(outputPath, 'isolate_snapshot_data')}',
'--url_mapping=dart:ui,${fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')}',
'--url_mapping=dart:vmservice_io,${fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')}',
'--dependencies=${fs.path.join(outputPath, 'snapshot.d')}',
'--embedder_entry_points_manifest=$kVmEntrypoints',
'--embedder_entry_points_manifest=$kIoEntries',
'--snapshot_kind=app-aot-assembly',
'--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
'--no-checked',
'--conditional_directives',
'--reify-generic-functions',
'--strong',
'main.dill',
]);
}, overrides: contextOverrides);
testUsingContext('builds iOS release AOT snapshot', () async {
fs.file('main.dart').writeAsStringSync('void main() {}');
final String outputPath = fs.path.join('build', 'foo');
fs.directory(outputPath).createSync(recursive: true);
kernelCompiler.output = const CompilerOutput('main.dill', 0);
genSnapshot.outputs = <String, String>{
fs.path.join(outputPath, 'snapshot_assembly.S'): '',
fs.path.join(outputPath, 'snapshot.d'): '',
};
final int genSnapshotExitCode = await snapshotter.buildAotSnapshot(
platform: TargetPlatform.ios,
buildMode: BuildMode.release,
mainPath: 'main.dart',
depfilePath: fs.path.join(outputPath, 'snapshot.d'),
packagesPath: '.packages',
outputPath: outputPath,
interpreter: false,
preferSharedLibrary: false,
previewDart2: true,
);
expect(genSnapshotExitCode, 0);
expect(genSnapshot.callCount, 1);
expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
expect(genSnapshot.snapshotType.mode, BuildMode.release);
expect(genSnapshot.packagesPath, '.packages');
expect(genSnapshot.depfilePath, fs.path.join(outputPath, 'snapshot.d'));
expect(genSnapshot.additionalArgs, <String>[
'--vm_snapshot_data=${fs.path.join(outputPath, 'vm_snapshot_data')}',
'--isolate_snapshot_data=${fs.path.join(outputPath, 'isolate_snapshot_data')}',
'--url_mapping=dart:ui,${fs.path.join(skyEnginePath, 'lib', 'ui', 'ui.dart')}',
'--url_mapping=dart:vmservice_io,${fs.path.join(skyEnginePath, 'sdk_ext', 'vmservice_io.dart')}',
'--dependencies=${fs.path.join(outputPath, 'snapshot.d')}',
'--embedder_entry_points_manifest=$kVmEntrypoints',
'--embedder_entry_points_manifest=$kIoEntries',
'--snapshot_kind=app-aot-assembly',
'--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
'--reify-generic-functions',
'--strong',
'main.dill',
]);
}, overrides: contextOverrides);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment