Commit 01a27ca4 authored by Jason Simmons's avatar Jason Simmons

Build ahead-of-time compiled snapshots and incorporate them into Android APKs (#3592)

This currently requires a local build of the engine.

Run "flutter build aot" to build an AOT snapshot.
Run "flutter build apk --profile" to build a "profile mode" APK that uses AOT.
parent 53c07045
...@@ -8,12 +8,14 @@ import 'dart:io'; ...@@ -8,12 +8,14 @@ import 'dart:io';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'build_apk.dart'; import 'build_apk.dart';
import 'build_aot.dart';
import 'build_flx.dart'; import 'build_flx.dart';
import 'build_ios.dart'; import 'build_ios.dart';
class BuildCommand extends FlutterCommand { class BuildCommand extends FlutterCommand {
BuildCommand() { BuildCommand() {
addSubcommand(new BuildApkCommand()); addSubcommand(new BuildApkCommand());
addSubcommand(new BuildAotCommand());
addSubcommand(new BuildCleanCommand()); addSubcommand(new BuildCleanCommand());
addSubcommand(new BuildIOSCommand()); addSubcommand(new BuildIOSCommand());
addSubcommand(new BuildFlxCommand()); addSubcommand(new BuildFlxCommand());
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart' as path;
import '../base/process.dart';
import '../build_configuration.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
import 'run.dart';
const String _kDefaultAotOutputDir = 'build/aot';
// Files generated by the ahead-of-time snapshot builder.
const List<String> kAotSnapshotFiles = const <String>[
'snapshot_aot_instr', 'snapshot_aot_isolate', 'snapshot_aot_rodata', 'snapshot_aot_vmisolate',
];
class BuildAotCommand extends FlutterCommand {
BuildAotCommand() {
usesTargetOption();
addBuildModeFlags();
usesPubOption();
argParser.addOption('output-dir', defaultsTo: _kDefaultAotOutputDir);
}
@override
final String name = 'aot';
@override
final String description = "Build an ahead-of-time compiled snapshot of your app's Dart code. "
"(local engine builds only)";
@override
Future<int> runInProject() async {
String outputPath = buildAotSnapshot(
findMainDartFile(argResults['target']),
outputPath: argResults['output-dir']
);
if (outputPath == null)
return 1;
printStatus('Built $outputPath.');
return 0;
}
}
String _getSdkExtensionPath(String packagesPath, String package) {
Directory packageDir = new Directory(path.join(packagesPath, package));
return path.join(path.dirname(packageDir.resolveSymbolicLinksSync()), 'sdk_ext');
}
String buildAotSnapshot(
String mainPath, {
String outputPath: _kDefaultAotOutputDir
}) {
String engineSrc = tools.engineSrcPath;
if (engineSrc == null) {
printError('AOT compilation requires --engine-src-path');
return null;
}
String engineOut = tools.getEngineArtifactsDirectory(
TargetPlatform.android_arm, BuildMode.profile).path;
String genSnapshot = path.join(engineOut, 'clang_x86', 'gen_snapshot');
Directory outputDir = new Directory(outputPath);
outputDir.createSync(recursive: true);
String vmIsolateSnapshot = path.join(outputDir.path, 'snapshot_aot_vmisolate');
String isolateSnapshot = path.join(outputDir.path, 'snapshot_aot_isolate');
String instructionsBlob = path.join(outputDir.path, 'snapshot_aot_instr');
String rodataBlob = path.join(outputDir.path, 'snapshot_aot_rodata');
String bindingsSrc = path.join(engineSrc, 'sky', 'engine', 'bindings');
String vmEntryPoints = path.join(bindingsSrc, 'dart_vm_entry_points.txt');
String vmEntryPointsAndroid = path.join(bindingsSrc, 'dart_vm_entry_points_android.txt');
String packagesPath = path.absolute(Directory.current.path, 'packages');
if (!FileSystemEntity.isDirectorySync(packagesPath)) {
printError('Could not find packages directory: $packagesPath\n' +
'Did you run `pub get` in this directory?');
return null;
}
String mojoSdkExt = _getSdkExtensionPath(packagesPath, 'mojo');
String mojoInternalPath = path.join(mojoSdkExt, 'internal.dart');
String skyEngineSdkExt = _getSdkExtensionPath(packagesPath, 'sky_engine');
String uiPath = path.join(skyEngineSdkExt, 'dart_ui.dart');
String vmServicePath = path.join(skyEngineSdkExt, 'dart', 'runtime', 'bin', 'vmservice', 'vmservice_io.dart');
String jniPath = path.join(skyEngineSdkExt, 'dart_jni', 'jni.dart');
List<String> filePaths = [
genSnapshot, vmEntryPoints, vmEntryPointsAndroid, mojoInternalPath, uiPath, vmServicePath, jniPath
];
List<String> missingFiles = filePaths.where((String p) => !FileSystemEntity.isFileSync(p)).toList();
if (missingFiles.isNotEmpty) {
printError('Missing files: $missingFiles');
return null;
}
List<String> genSnapshotCmd = [
genSnapshot,
'--vm_isolate_snapshot=$vmIsolateSnapshot',
'--isolate_snapshot=$isolateSnapshot',
'--instructions_blob=$instructionsBlob',
'--rodata_blob=$rodataBlob',
'--embedder_entry_points_manifest=$vmEntryPoints',
'--embedder_entry_points_manifest=$vmEntryPointsAndroid',
'--package_root=$packagesPath',
'--url_mapping=dart:mojo.internal,$mojoInternalPath',
'--url_mapping=dart:ui,$uiPath',
'--url_mapping=dart:vmservice_sky,$vmServicePath',
'--url_mapping=dart:jni,$jniPath',
'--no-sim-use-hardfp',
];
if (!tools.engineRelease) {
genSnapshotCmd.addAll([
'--no-checked',
'--conditional_directives',
]);
}
genSnapshotCmd.add(mainPath);
printStatus('Building snapshot...');
runCheckedSync(genSnapshotCmd);
return outputPath;
}
...@@ -18,6 +18,7 @@ import '../globals.dart'; ...@@ -18,6 +18,7 @@ import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../services.dart'; import '../services.dart';
import '../toolchain.dart'; import '../toolchain.dart';
import 'build_aot.dart';
import 'run.dart'; import 'run.dart';
export '../android/android_device.dart' show AndroidDevice; export '../android/android_device.dart' show AndroidDevice;
...@@ -156,6 +157,9 @@ class BuildApkCommand extends FlutterCommand { ...@@ -156,6 +157,9 @@ class BuildApkCommand extends FlutterCommand {
argParser.addOption('flx', argParser.addOption('flx',
abbr: 'f', abbr: 'f',
help: 'Path to the FLX file. If this is not provided, an FLX will be built.'); help: 'Path to the FLX file. If this is not provided, an FLX will be built.');
argParser.addOption('aot-path',
help: 'Path to the ahead-of-time compiled snapshot directory.\n'
'If this is not provided, an AOT snapshot will be built.');
argParser.addOption('add-file', argParser.addOption('add-file',
help: 'Add a file to the APK (must have the format <path/in/APK>=<local/file/path>).', help: 'Add a file to the APK (must have the format <path/in/APK>=<local/file/path>).',
allowMultiple: true); allowMultiple: true);
...@@ -217,6 +221,7 @@ class BuildApkCommand extends FlutterCommand { ...@@ -217,6 +221,7 @@ class BuildApkCommand extends FlutterCommand {
outputFile: argResults['output-file'], outputFile: argResults['output-file'],
target: argResults['target'], target: argResults['target'],
flxPath: argResults['flx'], flxPath: argResults['flx'],
aotPath: argResults['aot-path'],
extraFiles: extraFiles, extraFiles: extraFiles,
keystore: (argResults['keystore'] ?? '').isEmpty ? null : new ApkKeystoreInfo( keystore: (argResults['keystore'] ?? '').isEmpty ? null : new ApkKeystoreInfo(
keystore: argResults['keystore'], keystore: argResults['keystore'],
...@@ -401,6 +406,9 @@ bool _needsRebuild(String apkPath, String manifest) { ...@@ -401,6 +406,9 @@ bool _needsRebuild(String apkPath, String manifest) {
return false; return false;
} }
// Returns true if the selected build mode uses ahead-of-time compilation.
bool _isAotBuildMode(BuildMode mode) => mode == BuildMode.profile;
Future<int> buildAndroid( Future<int> buildAndroid(
TargetPlatform platform, TargetPlatform platform,
BuildMode buildMode, { BuildMode buildMode, {
...@@ -411,6 +419,7 @@ Future<int> buildAndroid( ...@@ -411,6 +419,7 @@ Future<int> buildAndroid(
String outputFile: _kDefaultOutputPath, String outputFile: _kDefaultOutputPath,
String target, String target,
String flxPath, String flxPath,
String aotPath,
Map<String, File> extraFiles, Map<String, File> extraFiles,
ApkKeystoreInfo keystore ApkKeystoreInfo keystore
}) async { }) async {
...@@ -458,18 +467,44 @@ Future<int> buildAndroid( ...@@ -458,18 +467,44 @@ Future<int> buildAndroid(
printError('(Omit the --flx option to build the FLX automatically)'); printError('(Omit the --flx option to build the FLX automatically)');
return 1; return 1;
} }
return _buildApk(platform, buildMode, components, flxPath, keystore, outputFile);
} else { } else {
// Find the path to the main Dart file; build the FLX. // Build the FLX.
String mainPath = findMainDartFile(target); flxPath = await flx.buildFlx(
String localBundlePath = await flx.buildFlx(
toolchain, toolchain,
mainPath: mainPath, mainPath: findMainDartFile(target),
precompiledSnapshot: _isAotBuildMode(buildMode),
includeRobotoFonts: false); includeRobotoFonts: false);
}
return _buildApk(platform, buildMode, components, localBundlePath, keystore, outputFile); // Build an AOT snapshot if needed.
if (_isAotBuildMode(buildMode) && aotPath == null) {
aotPath = buildAotSnapshot(findMainDartFile(target));
if (aotPath == null) {
printError('Failed to build AOT snapshot');
return 1;
}
}
if (aotPath != null) {
if (!_isAotBuildMode(buildMode)) {
printError('AOT snapshot can not be used in build mode $buildMode');
return 1;
} }
if (!FileSystemEntity.isDirectorySync(aotPath)) {
printError('AOT snapshot does not exist: $aotPath');
return 1;
}
for (String aotFilename in kAotSnapshotFiles) {
String aotFilePath = path.join(aotPath, aotFilename);
if (!FileSystemEntity.isFileSync(aotFilePath)) {
printError('Missing AOT snapshot file: $aotFilePath');
return 1;
}
components.extraFiles['assets/$aotFilename'] = new File(aotFilePath);
}
}
return _buildApk(platform, buildMode, components, flxPath, keystore, outputFile);
} }
Future<int> buildApk( Future<int> buildApk(
......
...@@ -245,6 +245,7 @@ ZipEntry _createFontManifest(Map<String, dynamic> manifestDescriptor, ...@@ -245,6 +245,7 @@ ZipEntry _createFontManifest(Map<String, dynamic> manifestDescriptor,
Future<String> buildFlx( Future<String> buildFlx(
Toolchain toolchain, { Toolchain toolchain, {
String mainPath: defaultMainPath, String mainPath: defaultMainPath,
bool precompiledSnapshot: false,
bool includeRobotoFonts: true bool includeRobotoFonts: true
}) async { }) async {
int result; int result;
...@@ -255,6 +256,7 @@ Future<String> buildFlx( ...@@ -255,6 +256,7 @@ Future<String> buildFlx(
snapshotPath: localSnapshotPath, snapshotPath: localSnapshotPath,
outputPath: localBundlePath, outputPath: localBundlePath,
mainPath: mainPath, mainPath: mainPath,
precompiledSnapshot: precompiledSnapshot,
includeRobotoFonts: includeRobotoFonts includeRobotoFonts: includeRobotoFonts
); );
if (result == 0) if (result == 0)
......
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