Unverified Commit 54c10f44 authored by Stanislav Baranov's avatar Stanislav Baranov Committed by GitHub

Implement build flow for hot updates on Android (#22391)

This also involves switching from Core JIT to App JIT snapshot, and replacing per-isolate VM snapshot with the shared VM snapshot.

For now there is no separate update bundle file, as the generated update gets packaged directly into the APK for testing purposes.
parent b65d3bce
......@@ -307,6 +307,10 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('precompile')) {
compilationTraceFilePathValue = project.property('precompile')
}
Boolean buildHotUpdateValue = false
if (project.hasProperty('hotupdate')) {
buildHotUpdateValue = project.property('hotupdate').toBoolean()
}
String extraFrontEndOptionsValue = null
if (project.hasProperty('extra-front-end-options')) {
extraFrontEndOptionsValue = project.property('extra-front-end-options')
......@@ -349,6 +353,7 @@ class FlutterPlugin implements Plugin<Project> {
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
compilationTraceFilePath compilationTraceFilePathValue
buildHotUpdate buildHotUpdateValue
buildSharedLibrary buildSharedLibraryValue
targetPlatform targetPlatformValue
sourceDir project.file(project.flutter.source)
......@@ -398,6 +403,8 @@ abstract class BaseFlutterTask extends DefaultTask {
@Optional @Input
String compilationTraceFilePath
@Optional @Input
Boolean buildHotUpdate
@Optional @Input
Boolean buildSharedLibrary
@Optional @Input
String targetPlatform
......@@ -491,6 +498,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (compilationTraceFilePath != null) {
args "--precompile", compilationTraceFilePath
}
if (buildHotUpdate) {
args "--hotupdate"
}
if (extraFrontEndOptions != null) {
args "--extra-front-end-options", "${extraFrontEndOptions}"
}
......
......@@ -365,6 +365,8 @@ Future<Null> _buildGradleProjectV2(
command.add('-Ptrack-widget-creation=true');
if (buildInfo.compilationTraceFilePath != null)
command.add('-Pprecompile=${buildInfo.compilationTraceFilePath}');
if (buildInfo.buildHotUpdate)
command.add('-Photupdate=true');
if (buildInfo.extraFrontEndOptions != null)
command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}');
if (buildInfo.extraGenSnapshotOptions != null)
......
......@@ -338,10 +338,9 @@ class AOTSnapshotter {
}
}
class CoreJITSnapshotter {
/// Builds a "Core JIT" VM snapshot of the specified kernel. This snapshot
/// includes data as well as either machine code or DBC, depending on build
/// configuration.
class JITSnapshotter {
/// Builds a JIT VM snapshot of the specified kernel. This snapshot includes
/// data as well as either machine code or DBC, depending on build configuration.
Future<int> build({
@required TargetPlatform platform,
@required BuildMode buildMode,
......@@ -349,18 +348,28 @@ class CoreJITSnapshotter {
@required String packagesPath,
@required String outputPath,
@required String compilationTraceFilePath,
@required bool buildHotUpdate,
List<String> extraGenSnapshotOptions = const <String>[],
}) async {
if (!_isValidCoreJitPlatform(platform)) {
printError('${getNameForTargetPlatform(platform)} does not support Core JIT compilation.');
if (!_isValidJitPlatform(platform)) {
printError('${getNameForTargetPlatform(platform)} does not support JIT snapshotting.');
return 1;
}
final Directory outputDir = fs.directory(outputPath);
outputDir.createSync(recursive: true);
final List<String> inputPaths = <String>[mainPath, compilationTraceFilePath];
final Set<String> outputPaths = Set<String>();
final String engineVmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
final String engineIsolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData);
final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
final List<String> inputPaths = <String>[
mainPath, compilationTraceFilePath, engineVmSnapshotData, engineIsolateSnapshotData,
];
if (buildHotUpdate) {
inputPaths.add(isolateSnapshotInstructions);
}
final String depfilePath = fs.path.join(outputDir.path, 'snapshot.d');
final List<String> genSnapshotArgs = <String>[
......@@ -376,21 +385,26 @@ class CoreJITSnapshotter {
genSnapshotArgs.addAll(extraGenSnapshotOptions);
}
// Blob Core JIT snapshot.
final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data');
final String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
final String vmSnapshotInstructions = fs.path.join(outputDir.path, 'vm_snapshot_instr');
final String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
outputPaths.addAll(<String>[vmSnapshotData, isolateSnapshotData, vmSnapshotInstructions, isolateSnapshotInstructions]);
final Set<String> outputPaths = Set<String>();
outputPaths.addAll(<String>[isolateSnapshotData]);
if (!buildHotUpdate) {
outputPaths.add(isolateSnapshotInstructions);
}
genSnapshotArgs.addAll(<String>[
'--snapshot_kind=core-jit',
'--vm_snapshot_data=$vmSnapshotData',
'--isolate_snapshot_data=$isolateSnapshotData',
'--vm_snapshot_instructions=$vmSnapshotInstructions',
'--isolate_snapshot_instructions=$isolateSnapshotInstructions',
'--snapshot_kind=app-jit',
'--load_compilation_trace=$compilationTraceFilePath',
'--load_vm_snapshot_data=$engineVmSnapshotData',
'--load_isolate_snapshot_data=$engineIsolateSnapshotData',
'--isolate_snapshot_data=$isolateSnapshotData',
]);
if (!buildHotUpdate) {
genSnapshotArgs.add('--isolate_snapshot_instructions=$isolateSnapshotInstructions');
} else {
genSnapshotArgs.add('--reused_instructions=$isolateSnapshotInstructions');
}
if (platform == TargetPlatform.android_arm) {
// Use softfp for Android armv7 devices.
// TODO(cbracken): eliminate this when we fix https://github.com/flutter/flutter/issues/17489
......@@ -417,12 +431,13 @@ class CoreJITSnapshotter {
'buildMode': buildMode.toString(),
'targetPlatform': platform.toString(),
'entryPoint': mainPath,
'buildHotUpdate': buildHotUpdate.toString(),
'extraGenSnapshotOptions': extraGenSnapshotOptions.join(' '),
},
depfilePaths: <String>[],
);
if (await fingerprinter.doesFingerprintMatch()) {
printTrace('Skipping Core JIT snapshot build. Fingerprint match.');
printTrace('Skipping JIT snapshot build. Fingerprint match.');
return 0;
}
......@@ -447,7 +462,7 @@ class CoreJITSnapshotter {
return 0;
}
bool _isValidCoreJitPlatform(TargetPlatform platform) {
bool _isValidJitPlatform(TargetPlatform platform) {
return const <TargetPlatform>[
TargetPlatform.android_arm,
TargetPlatform.android_arm64,
......
......@@ -13,6 +13,7 @@ class BuildInfo {
const BuildInfo(this.mode, this.flavor, {
this.trackWidgetCreation = false,
this.compilationTraceFilePath,
this.buildHotUpdate,
this.extraFrontEndOptions,
this.extraGenSnapshotOptions,
this.buildSharedLibrary,
......@@ -42,6 +43,9 @@ class BuildInfo {
/// Dart compilation trace file to use for JIT VM snapshot.
final String compilationTraceFilePath;
/// Build differential snapshot.
final bool buildHotUpdate;
/// Extra command-line options for front-end.
final String extraFrontEndOptions;
......@@ -97,6 +101,7 @@ class BuildInfo {
BuildInfo(mode, flavor,
trackWidgetCreation: trackWidgetCreation,
compilationTraceFilePath: compilationTraceFilePath,
buildHotUpdate: buildHotUpdate,
extraFrontEndOptions: extraFrontEndOptions,
extraGenSnapshotOptions: extraGenSnapshotOptions,
buildSharedLibrary: buildSharedLibrary,
......
......@@ -25,7 +25,6 @@ const String defaultPrivateKeyPath = 'privatekey.der';
const String _kKernelKey = 'kernel_blob.bin';
const String _kVMSnapshotData = 'vm_snapshot_data';
const String _kVMSnapshotInstr = 'vm_snapshot_instr';
const String _kIsolateSnapshotData = 'isolate_snapshot_data';
const String _kIsolateSnapshotInstr = 'isolate_snapshot_instr';
const String _kDylibKey = 'libapp.so';
......@@ -46,6 +45,7 @@ Future<void> build({
bool reportLicensedPackages = false,
bool trackWidgetCreation = false,
String compilationTraceFilePath,
bool buildHotUpdate = false,
List<String> extraFrontEndOptions = const <String>[],
List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots,
......@@ -85,7 +85,7 @@ Future<void> build({
.writeAsString('frontend_server.d: ${artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk)}\n');
if (compilationTraceFilePath != null) {
final CoreJITSnapshotter snapshotter = CoreJITSnapshotter();
final JITSnapshotter snapshotter = JITSnapshotter();
final int snapshotExitCode = await snapshotter.build(
platform: platform,
buildMode: buildMode,
......@@ -94,6 +94,7 @@ Future<void> build({
packagesPath: packagesPath,
compilationTraceFilePath: compilationTraceFilePath,
extraGenSnapshotOptions: extraGenSnapshotOptions,
buildHotUpdate: buildHotUpdate,
);
if (snapshotExitCode != 0) {
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
......@@ -160,12 +161,10 @@ Future<void> assemble({
final Map<String, DevFSContent> assetEntries = Map<String, DevFSContent>.from(assetBundle.entries);
if (kernelContent != null) {
if (compilationTraceFilePath != null) {
final String vmSnapshotData = fs.path.join(getBuildDirectory(), _kVMSnapshotData);
final String vmSnapshotInstr = fs.path.join(getBuildDirectory(), _kVMSnapshotInstr);
final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
final String isolateSnapshotData = fs.path.join(getBuildDirectory(), _kIsolateSnapshotData);
final String isolateSnapshotInstr = fs.path.join(getBuildDirectory(), _kIsolateSnapshotInstr);
assetEntries[_kVMSnapshotData] = DevFSFileContent(fs.file(vmSnapshotData));
assetEntries[_kVMSnapshotInstr] = DevFSFileContent(fs.file(vmSnapshotInstr));
assetEntries[_kIsolateSnapshotData] = DevFSFileContent(fs.file(isolateSnapshotData));
assetEntries[_kIsolateSnapshotInstr] = DevFSFileContent(fs.file(isolateSnapshotInstr));
} else {
......
......@@ -40,7 +40,15 @@ class BuildBundleCommand extends BuildSubCommand {
'file produced by the training run of the application. With this\n'
'flag, instead of using default Dart VM snapshot provided by the\n'
'engine, the application will use its own snapshot that includes\n'
'additional functions.'
'additional compiled functions.'
)
..addFlag('hotupdate',
hide: !verboseHelp,
help: 'Build differential snapshot based on the last state of the build\n'
'tree and any changes to the application source code since then.\n'
'This flag is only allowed when using --dynamic. With this flag,\n'
'a partial VM snapshot is generated that is loaded on top of the\n'
'original VM snapshot that contains precompiled code.'
)
..addMultiOption(FlutterOptions.kExtraFrontEndOptions,
splitCommas: true,
......@@ -94,6 +102,7 @@ class BuildBundleCommand extends BuildSubCommand {
reportLicensedPackages: argResults['report-licensed-packages'],
trackWidgetCreation: argResults['track-widget-creation'],
compilationTraceFilePath: argResults['precompile'],
buildHotUpdate: argResults['hotupdate'],
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
fileSystemScheme: argResults['filesystem-scheme'],
......
......@@ -129,6 +129,14 @@ class RunCommand extends RunCommandBase {
'engine, the application will use its own snapshot that includes\n'
'additional functions.'
)
..addFlag('hotupdate',
hide: !verboseHelp,
help: 'Build differential snapshot based on the last state of the build\n'
'tree and any changes to the application source code since then.\n'
'This flag is only allowed when using --dynamic. With this flag,\n'
'a partial VM snapshot is generated that is loaded on top of the\n'
'original VM snapshot that contains precompiled code.'
)
..addFlag('track-widget-creation',
hide: !verboseHelp,
help: 'Track widget creation locations. Requires Dart 2.0 functionality.',
......
......@@ -243,6 +243,9 @@ abstract class FlutterCommand extends Command<Null> {
compilationTraceFilePath: argParser.options.containsKey('precompile')
? argResults['precompile']
: null,
buildHotUpdate: argParser.options.containsKey('hotupdate')
? argResults['hotupdate']
: false,
extraFrontEndOptions: argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions)
? argResults[FlutterOptions.kExtraFrontEndOptions]
: null,
......@@ -495,10 +498,15 @@ abstract class FlutterCommand extends Command<Null> {
? argResults['dynamic'] : false;
final String compilationTraceFilePath = argParser.options.containsKey('precompile')
? argResults['precompile'] : null;
final bool buildHotUpdate = argParser.options.containsKey('hotupdate')
? argResults['hotupdate'] : false;
if (compilationTraceFilePath != null && getBuildMode() == BuildMode.debug)
throw ToolExit('Error: --precompile is not allowed when --debug is specified.');
if (compilationTraceFilePath != null && !dynamicFlag)
throw ToolExit('Error: --precompile is allowed only when --dynamic is specified.');
if (buildHotUpdate && compilationTraceFilePath == null)
throw ToolExit('Error: --hotupdate is allowed only when --precompile is specified.');
}
ApplicationPackageStore applicationPackages;
......
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