mirror of
https://github.com/flutter/flutter
synced 2024-09-19 16:21:58 +00:00
Flutter tool support for building dynamic updates (#25576)
This commit is contained in:
parent
a282058d69
commit
55f3da7afc
|
@ -321,9 +321,17 @@ class FlutterPlugin implements Plugin<Project> {
|
||||||
if (project.hasProperty('precompile')) {
|
if (project.hasProperty('precompile')) {
|
||||||
compilationTraceFilePathValue = project.property('precompile')
|
compilationTraceFilePathValue = project.property('precompile')
|
||||||
}
|
}
|
||||||
Boolean buildHotUpdateValue = false
|
Boolean createPatchValue = false
|
||||||
if (project.hasProperty('hotupdate')) {
|
if (project.hasProperty('patch')) {
|
||||||
buildHotUpdateValue = project.property('hotupdate').toBoolean()
|
createPatchValue = project.property('patch').toBoolean()
|
||||||
|
}
|
||||||
|
Integer buildNumberValue = null
|
||||||
|
if (project.hasProperty('build-number')) {
|
||||||
|
buildNumberValue = project.property('build-number').toInteger()
|
||||||
|
}
|
||||||
|
String baselineDirValue = null
|
||||||
|
if (project.hasProperty('baseline-dir')) {
|
||||||
|
baselineDirValue = project.property('baseline-dir')
|
||||||
}
|
}
|
||||||
String extraFrontEndOptionsValue = null
|
String extraFrontEndOptionsValue = null
|
||||||
if (project.hasProperty('extra-front-end-options')) {
|
if (project.hasProperty('extra-front-end-options')) {
|
||||||
|
@ -367,7 +375,9 @@ class FlutterPlugin implements Plugin<Project> {
|
||||||
fileSystemScheme fileSystemSchemeValue
|
fileSystemScheme fileSystemSchemeValue
|
||||||
trackWidgetCreation trackWidgetCreationValue
|
trackWidgetCreation trackWidgetCreationValue
|
||||||
compilationTraceFilePath compilationTraceFilePathValue
|
compilationTraceFilePath compilationTraceFilePathValue
|
||||||
buildHotUpdate buildHotUpdateValue
|
createPatch createPatchValue
|
||||||
|
buildNumber buildNumberValue
|
||||||
|
baselineDir baselineDirValue
|
||||||
buildSharedLibrary buildSharedLibraryValue
|
buildSharedLibrary buildSharedLibraryValue
|
||||||
targetPlatform targetPlatformValue
|
targetPlatform targetPlatformValue
|
||||||
sourceDir project.file(project.flutter.source)
|
sourceDir project.file(project.flutter.source)
|
||||||
|
@ -428,7 +438,11 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||||
@Optional @Input
|
@Optional @Input
|
||||||
String compilationTraceFilePath
|
String compilationTraceFilePath
|
||||||
@Optional @Input
|
@Optional @Input
|
||||||
Boolean buildHotUpdate
|
Boolean createPatch
|
||||||
|
@Optional @Input
|
||||||
|
Integer buildNumber
|
||||||
|
@Optional @Input
|
||||||
|
String baselineDir
|
||||||
@Optional @Input
|
@Optional @Input
|
||||||
Boolean buildSharedLibrary
|
Boolean buildSharedLibrary
|
||||||
@Optional @Input
|
@Optional @Input
|
||||||
|
@ -523,8 +537,15 @@ abstract class BaseFlutterTask extends DefaultTask {
|
||||||
if (compilationTraceFilePath != null) {
|
if (compilationTraceFilePath != null) {
|
||||||
args "--precompile", compilationTraceFilePath
|
args "--precompile", compilationTraceFilePath
|
||||||
}
|
}
|
||||||
if (buildHotUpdate) {
|
if (createPatch) {
|
||||||
args "--hotupdate"
|
args "--patch"
|
||||||
|
args "--build-number", project.android.defaultConfig.versionCode
|
||||||
|
if (buildNumber != null) {
|
||||||
|
assert buildNumber == project.android.defaultConfig.versionCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (baselineDir != null) {
|
||||||
|
args "--baseline-dir", baselineDir
|
||||||
}
|
}
|
||||||
if (extraFrontEndOptions != null) {
|
if (extraFrontEndOptions != null) {
|
||||||
args "--extra-front-end-options", "${extraFrontEndOptions}"
|
args "--extra-front-end-options", "${extraFrontEndOptions}"
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../android/android_sdk.dart';
|
import '../android/android_sdk.dart';
|
||||||
|
import '../application_package.dart';
|
||||||
import '../artifacts.dart';
|
import '../artifacts.dart';
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
|
@ -374,8 +377,8 @@ Future<void> _buildGradleProjectV2(
|
||||||
command.add('-Ptrack-widget-creation=${buildInfo.trackWidgetCreation}');
|
command.add('-Ptrack-widget-creation=${buildInfo.trackWidgetCreation}');
|
||||||
if (buildInfo.compilationTraceFilePath != null)
|
if (buildInfo.compilationTraceFilePath != null)
|
||||||
command.add('-Pprecompile=${buildInfo.compilationTraceFilePath}');
|
command.add('-Pprecompile=${buildInfo.compilationTraceFilePath}');
|
||||||
if (buildInfo.buildHotUpdate)
|
if (buildInfo.createPatch)
|
||||||
command.add('-Photupdate=true');
|
command.add('-Ppatch=true');
|
||||||
if (buildInfo.extraFrontEndOptions != null)
|
if (buildInfo.extraFrontEndOptions != null)
|
||||||
command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}');
|
command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}');
|
||||||
if (buildInfo.extraGenSnapshotOptions != null)
|
if (buildInfo.extraGenSnapshotOptions != null)
|
||||||
|
@ -420,6 +423,71 @@ Future<void> _buildGradleProjectV2(
|
||||||
appSize = ' (${getSizeAsMB(apkFile.lengthSync())})';
|
appSize = ' (${getSizeAsMB(apkFile.lengthSync())})';
|
||||||
}
|
}
|
||||||
printStatus('Built ${fs.path.relative(apkFile.path)}$appSize.');
|
printStatus('Built ${fs.path.relative(apkFile.path)}$appSize.');
|
||||||
|
|
||||||
|
final AndroidApk package = AndroidApk.fromApk(apkFile);
|
||||||
|
final File baselineApkFile =
|
||||||
|
fs.directory(buildInfo.baselineDir).childFile('${package.versionCode}.apk');
|
||||||
|
|
||||||
|
if (buildInfo.createBaseline) {
|
||||||
|
// Save baseline apk for generating dynamic patches in later builds.
|
||||||
|
baselineApkFile.parent.createSync(recursive: true);
|
||||||
|
apkFile.copySync(baselineApkFile.path);
|
||||||
|
printStatus('Saved baseline package ${baselineApkFile.path}.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildInfo.createPatch) {
|
||||||
|
if (!baselineApkFile.existsSync())
|
||||||
|
throwToolExit('Error: Could not find baseline package ${baselineApkFile.path}.');
|
||||||
|
|
||||||
|
printStatus('Found baseline package ${baselineApkFile.path}.');
|
||||||
|
final Archive newApk = ZipDecoder().decodeBytes(apkFile.readAsBytesSync());
|
||||||
|
final Archive oldApk = ZipDecoder().decodeBytes(baselineApkFile.readAsBytesSync());
|
||||||
|
|
||||||
|
final Archive update = Archive();
|
||||||
|
for (ArchiveFile newFile in newApk) {
|
||||||
|
if (!newFile.isFile || !newFile.name.startsWith('assets/flutter_assets/'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
final ArchiveFile oldFile = oldApk.findFile(newFile.name);
|
||||||
|
if (oldFile != null && oldFile.crc32 == newFile.crc32)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
final String name = fs.path.relative(newFile.name, from: 'assets/');
|
||||||
|
update.addFile(ArchiveFile(name, newFile.content.length, newFile.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
final File updateFile = fs.directory(buildInfo.patchDir)
|
||||||
|
.childFile('${package.versionCode}-${buildInfo.patchNumber}.zip');
|
||||||
|
|
||||||
|
if (update.files.isEmpty) {
|
||||||
|
printStatus('No changes detected relative to baseline build.');
|
||||||
|
|
||||||
|
if (updateFile.existsSync()) {
|
||||||
|
updateFile.deleteSync();
|
||||||
|
printStatus('Deleted dynamic patch ${updateFile.path}.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ArchiveFile oldFile = oldApk.findFile('assets/flutter_assets/isolate_snapshot_data');
|
||||||
|
if (oldFile == null)
|
||||||
|
throwToolExit('Error: Could not find baseline assets/flutter_assets/isolate_snapshot_data.');
|
||||||
|
|
||||||
|
final int baselineChecksum = getCrc32(oldFile.content);
|
||||||
|
final Map<String, dynamic> manifest = <String, dynamic>{
|
||||||
|
'baselineChecksum': baselineChecksum,
|
||||||
|
'buildNumber': package.versionCode,
|
||||||
|
'patchNumber': buildInfo.patchNumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
const JsonEncoder encoder = JsonEncoder.withIndent(' ');
|
||||||
|
final String manifestJson = encoder.convert(manifest);
|
||||||
|
update.addFile(ArchiveFile('manifest.json', manifestJson.length, manifestJson.codeUnits));
|
||||||
|
|
||||||
|
updateFile.parent.createSync(recursive: true);
|
||||||
|
updateFile.writeAsBytesSync(ZipEncoder().encode(update), flush: true);
|
||||||
|
printStatus('Created dynamic patch ${updateFile.path}.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File _findApkFile(GradleProject project, BuildInfo buildInfo) {
|
File _findApkFile(GradleProject project, BuildInfo buildInfo) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ class AndroidApk extends ApplicationPackage {
|
||||||
AndroidApk({
|
AndroidApk({
|
||||||
String id,
|
String id,
|
||||||
@required this.file,
|
@required this.file,
|
||||||
|
@required this.versionCode,
|
||||||
@required this.launchActivity
|
@required this.launchActivity
|
||||||
}) : assert(file != null),
|
}) : assert(file != null),
|
||||||
assert(launchActivity != null),
|
assert(launchActivity != null),
|
||||||
|
@ -78,6 +79,7 @@ class AndroidApk extends ApplicationPackage {
|
||||||
return AndroidApk(
|
return AndroidApk(
|
||||||
id: data.packageName,
|
id: data.packageName,
|
||||||
file: apk,
|
file: apk,
|
||||||
|
versionCode: int.tryParse(data.versionCode),
|
||||||
launchActivity: '${data.packageName}/${data.launchableActivityName}'
|
launchActivity: '${data.packageName}/${data.launchableActivityName}'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +90,9 @@ class AndroidApk extends ApplicationPackage {
|
||||||
/// The path to the activity that should be launched.
|
/// The path to the activity that should be launched.
|
||||||
final String launchActivity;
|
final String launchActivity;
|
||||||
|
|
||||||
|
/// The version code of the APK.
|
||||||
|
final int versionCode;
|
||||||
|
|
||||||
/// Creates a new AndroidApk based on the information in the Android manifest.
|
/// Creates a new AndroidApk based on the information in the Android manifest.
|
||||||
static Future<AndroidApk> fromAndroidProject(AndroidProject androidProject) async {
|
static Future<AndroidApk> fromAndroidProject(AndroidProject androidProject) async {
|
||||||
File apkFile;
|
File apkFile;
|
||||||
|
@ -138,6 +143,7 @@ class AndroidApk extends ApplicationPackage {
|
||||||
return AndroidApk(
|
return AndroidApk(
|
||||||
id: packageId,
|
id: packageId,
|
||||||
file: apkFile,
|
file: apkFile,
|
||||||
|
versionCode: null,
|
||||||
launchActivity: launchActivity
|
launchActivity: launchActivity
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -449,8 +455,25 @@ class ApkManifestData {
|
||||||
final String activityName = nameAttribute
|
final String activityName = nameAttribute
|
||||||
.value.substring(1, nameAttribute.value.indexOf('" '));
|
.value.substring(1, nameAttribute.value.indexOf('" '));
|
||||||
|
|
||||||
|
// Example format: (type 0x10)0x1
|
||||||
|
final _Attribute versionCodeAttr = manifest.firstAttribute('android:versionCode');
|
||||||
|
if (versionCodeAttr == null) {
|
||||||
|
printError('Error running $packageName. Manifest versionCode not found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!versionCodeAttr.value.startsWith('(type 0x10)')) {
|
||||||
|
printError('Error running $packageName. Manifest versionCode invalid');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final int versionCode = int.tryParse(versionCodeAttr.value.substring(11));
|
||||||
|
if (versionCode == null) {
|
||||||
|
printError('Error running $packageName. Manifest versionCode invalid');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final Map<String, Map<String, String>> map = <String, Map<String, String>>{};
|
final Map<String, Map<String, String>> map = <String, Map<String, String>>{};
|
||||||
map['package'] = <String, String>{'name': packageName};
|
map['package'] = <String, String>{'name': packageName};
|
||||||
|
map['version-code'] = <String, String>{'name': versionCode.toString()};
|
||||||
map['launchable-activity'] = <String, String>{'name': activityName};
|
map['launchable-activity'] = <String, String>{'name': activityName};
|
||||||
|
|
||||||
return ApkManifestData._(map);
|
return ApkManifestData._(map);
|
||||||
|
@ -464,6 +487,8 @@ class ApkManifestData {
|
||||||
|
|
||||||
String get packageName => _data['package'] == null ? null : _data['package']['name'];
|
String get packageName => _data['package'] == null ? null : _data['package']['name'];
|
||||||
|
|
||||||
|
String get versionCode => _data['version-code'] == null ? null : _data['version-code']['name'];
|
||||||
|
|
||||||
String get launchableActivityName {
|
String get launchableActivityName {
|
||||||
return _data['launchable-activity'] == null ? null : _data['launchable-activity']['name'];
|
return _data['launchable-activity'] == null ? null : _data['launchable-activity']['name'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../android/android_sdk.dart';
|
import '../android/android_sdk.dart';
|
||||||
|
@ -348,7 +350,9 @@ class JITSnapshotter {
|
||||||
@required String packagesPath,
|
@required String packagesPath,
|
||||||
@required String outputPath,
|
@required String outputPath,
|
||||||
@required String compilationTraceFilePath,
|
@required String compilationTraceFilePath,
|
||||||
@required bool buildHotUpdate,
|
@required bool createPatch,
|
||||||
|
int buildNumber,
|
||||||
|
String baselineDir,
|
||||||
List<String> extraGenSnapshotOptions = const <String>[],
|
List<String> extraGenSnapshotOptions = const <String>[],
|
||||||
}) async {
|
}) async {
|
||||||
if (!_isValidJitPlatform(platform)) {
|
if (!_isValidJitPlatform(platform)) {
|
||||||
|
@ -367,8 +371,73 @@ class JITSnapshotter {
|
||||||
final List<String> inputPaths = <String>[
|
final List<String> inputPaths = <String>[
|
||||||
mainPath, compilationTraceFilePath, engineVmSnapshotData, engineIsolateSnapshotData,
|
mainPath, compilationTraceFilePath, engineVmSnapshotData, engineIsolateSnapshotData,
|
||||||
];
|
];
|
||||||
if (buildHotUpdate) {
|
|
||||||
|
if (createPatch) {
|
||||||
inputPaths.add(isolateSnapshotInstructions);
|
inputPaths.add(isolateSnapshotInstructions);
|
||||||
|
|
||||||
|
if (buildNumber == null) {
|
||||||
|
printError('Error: Dynamic patching requires --build-number specified');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (baselineDir == null) {
|
||||||
|
printError('Error: Dynamic patching requires --baseline-dir specified');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File baselineApk = fs.directory(baselineDir).childFile('$buildNumber.apk');
|
||||||
|
if (!baselineApk.existsSync()) {
|
||||||
|
printError('Error: Could not find baseline package ${baselineApk.path}.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Archive baselinePkg = ZipDecoder().decodeBytes(baselineApk.readAsBytesSync());
|
||||||
|
|
||||||
|
{
|
||||||
|
final File f = fs.file(isolateSnapshotInstructions);
|
||||||
|
final ArchiveFile af = baselinePkg.findFile(
|
||||||
|
fs.path.join('assets/flutter_assets/isolate_snapshot_instr'));
|
||||||
|
if (af == null) {
|
||||||
|
printError('Error: Invalid baseline package ${baselineApk.path}.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When building an update, gen_snapshot expects to find the original isolate
|
||||||
|
// snapshot instructions from the previous full build, so we need to extract
|
||||||
|
// it from saves baseline APK.
|
||||||
|
if (!f.existsSync()) {
|
||||||
|
f.writeAsBytesSync(af.content, flush: true);
|
||||||
|
} else {
|
||||||
|
// But if this file is already extracted, we make sure that it's identical.
|
||||||
|
final Function contentEquals = const ListEquality<int>().equals;
|
||||||
|
if (!contentEquals(f.readAsBytesSync(), af.content)) {
|
||||||
|
printError('Error: Detected changes unsupported by dynamic patching.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
final File f = fs.file(engineVmSnapshotData);
|
||||||
|
final ArchiveFile af = baselinePkg.findFile(
|
||||||
|
fs.path.join('assets/flutter_assets/vm_snapshot_data'));
|
||||||
|
if (af == null) {
|
||||||
|
printError('Error: Invalid baseline package ${baselineApk.path}.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If engine snapshot artifact doesn't exist, gen_snapshot below will fail
|
||||||
|
// with a friendly error, so we don't need to handle this case here too.
|
||||||
|
if (f.existsSync()) {
|
||||||
|
// But if engine snapshot exists, its content must match the engine snapshot
|
||||||
|
// in baseline APK. Otherwise, we're trying to build an update at an engine
|
||||||
|
// version that might be binary incompatible with baseline APK.
|
||||||
|
final Function contentEquals = const ListEquality<int>().equals;
|
||||||
|
if (!contentEquals(f.readAsBytesSync(), af.content)) {
|
||||||
|
printError('Error: Detected engine changes unsupported by dynamic patching.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final String depfilePath = fs.path.join(outputDir.path, 'snapshot.d');
|
final String depfilePath = fs.path.join(outputDir.path, 'snapshot.d');
|
||||||
|
@ -385,7 +454,7 @@ class JITSnapshotter {
|
||||||
|
|
||||||
final Set<String> outputPaths = Set<String>();
|
final Set<String> outputPaths = Set<String>();
|
||||||
outputPaths.addAll(<String>[isolateSnapshotData]);
|
outputPaths.addAll(<String>[isolateSnapshotData]);
|
||||||
if (!buildHotUpdate) {
|
if (!createPatch) {
|
||||||
outputPaths.add(isolateSnapshotInstructions);
|
outputPaths.add(isolateSnapshotInstructions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +466,7 @@ class JITSnapshotter {
|
||||||
'--isolate_snapshot_data=$isolateSnapshotData',
|
'--isolate_snapshot_data=$isolateSnapshotData',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!buildHotUpdate) {
|
if (!createPatch) {
|
||||||
genSnapshotArgs.add('--isolate_snapshot_instructions=$isolateSnapshotInstructions');
|
genSnapshotArgs.add('--isolate_snapshot_instructions=$isolateSnapshotInstructions');
|
||||||
} else {
|
} else {
|
||||||
genSnapshotArgs.add('--reused_instructions=$isolateSnapshotInstructions');
|
genSnapshotArgs.add('--reused_instructions=$isolateSnapshotInstructions');
|
||||||
|
@ -429,7 +498,7 @@ class JITSnapshotter {
|
||||||
'buildMode': buildMode.toString(),
|
'buildMode': buildMode.toString(),
|
||||||
'targetPlatform': platform.toString(),
|
'targetPlatform': platform.toString(),
|
||||||
'entryPoint': mainPath,
|
'entryPoint': mainPath,
|
||||||
'buildHotUpdate': buildHotUpdate.toString(),
|
'createPatch': createPatch.toString(),
|
||||||
'extraGenSnapshotOptions': extraGenSnapshotOptions.join(' '),
|
'extraGenSnapshotOptions': extraGenSnapshotOptions.join(' '),
|
||||||
},
|
},
|
||||||
depfilePaths: <String>[],
|
depfilePaths: <String>[],
|
||||||
|
|
|
@ -13,7 +13,11 @@ class BuildInfo {
|
||||||
const BuildInfo(this.mode, this.flavor, {
|
const BuildInfo(this.mode, this.flavor, {
|
||||||
this.trackWidgetCreation = false,
|
this.trackWidgetCreation = false,
|
||||||
this.compilationTraceFilePath,
|
this.compilationTraceFilePath,
|
||||||
this.buildHotUpdate,
|
this.createBaseline,
|
||||||
|
this.createPatch,
|
||||||
|
this.patchNumber,
|
||||||
|
this.patchDir,
|
||||||
|
this.baselineDir,
|
||||||
this.extraFrontEndOptions,
|
this.extraFrontEndOptions,
|
||||||
this.extraGenSnapshotOptions,
|
this.extraGenSnapshotOptions,
|
||||||
this.buildSharedLibrary,
|
this.buildSharedLibrary,
|
||||||
|
@ -43,8 +47,24 @@ class BuildInfo {
|
||||||
/// Dart compilation trace file to use for JIT VM snapshot.
|
/// Dart compilation trace file to use for JIT VM snapshot.
|
||||||
final String compilationTraceFilePath;
|
final String compilationTraceFilePath;
|
||||||
|
|
||||||
|
/// Save baseline package.
|
||||||
|
final bool createBaseline;
|
||||||
|
|
||||||
/// Build differential snapshot.
|
/// Build differential snapshot.
|
||||||
final bool buildHotUpdate;
|
final bool createPatch;
|
||||||
|
|
||||||
|
/// Internal version number of dynamic patch (not displayed to users).
|
||||||
|
/// Each patch should have a unique number to differentiate from previous
|
||||||
|
/// patches for the same versionCode on Android or CFBundleVersion on iOS.
|
||||||
|
final int patchNumber;
|
||||||
|
|
||||||
|
/// The directory where to store generated dynamic patches.
|
||||||
|
final String patchDir;
|
||||||
|
|
||||||
|
/// The directory where to store generated baseline packages.
|
||||||
|
/// Built packages, such as APK files on Android, are saved and can be used
|
||||||
|
/// to generate dynamic patches in later builds.
|
||||||
|
final String baselineDir;
|
||||||
|
|
||||||
/// Extra command-line options for front-end.
|
/// Extra command-line options for front-end.
|
||||||
final String extraFrontEndOptions;
|
final String extraFrontEndOptions;
|
||||||
|
@ -92,6 +112,9 @@ class BuildInfo {
|
||||||
/// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
|
/// Exactly one of [isDebug], [isProfile], or [isRelease] is true.
|
||||||
bool get isRelease => mode == BuildMode.release || mode == BuildMode.dynamicRelease;
|
bool get isRelease => mode == BuildMode.release || mode == BuildMode.dynamicRelease;
|
||||||
|
|
||||||
|
/// Returns whether a dynamic build is requested.
|
||||||
|
bool get isDynamic => mode == BuildMode.dynamicProfile || mode == BuildMode.dynamicRelease;
|
||||||
|
|
||||||
bool get usesAot => isAotBuildMode(mode);
|
bool get usesAot => isAotBuildMode(mode);
|
||||||
bool get supportsEmulator => isEmulatorBuildMode(mode);
|
bool get supportsEmulator => isEmulatorBuildMode(mode);
|
||||||
bool get supportsSimulator => isEmulatorBuildMode(mode);
|
bool get supportsSimulator => isEmulatorBuildMode(mode);
|
||||||
|
@ -101,7 +124,7 @@ class BuildInfo {
|
||||||
BuildInfo(mode, flavor,
|
BuildInfo(mode, flavor,
|
||||||
trackWidgetCreation: trackWidgetCreation,
|
trackWidgetCreation: trackWidgetCreation,
|
||||||
compilationTraceFilePath: compilationTraceFilePath,
|
compilationTraceFilePath: compilationTraceFilePath,
|
||||||
buildHotUpdate: buildHotUpdate,
|
createPatch: createPatch,
|
||||||
extraFrontEndOptions: extraFrontEndOptions,
|
extraFrontEndOptions: extraFrontEndOptions,
|
||||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||||
buildSharedLibrary: buildSharedLibrary,
|
buildSharedLibrary: buildSharedLibrary,
|
||||||
|
|
|
@ -60,7 +60,9 @@ Future<void> build({
|
||||||
bool reportLicensedPackages = false,
|
bool reportLicensedPackages = false,
|
||||||
bool trackWidgetCreation = false,
|
bool trackWidgetCreation = false,
|
||||||
String compilationTraceFilePath,
|
String compilationTraceFilePath,
|
||||||
bool buildHotUpdate = false,
|
bool createPatch = false,
|
||||||
|
int buildNumber,
|
||||||
|
String baselineDir,
|
||||||
List<String> extraFrontEndOptions = const <String>[],
|
List<String> extraFrontEndOptions = const <String>[],
|
||||||
List<String> extraGenSnapshotOptions = const <String>[],
|
List<String> extraGenSnapshotOptions = const <String>[],
|
||||||
List<String> fileSystemRoots,
|
List<String> fileSystemRoots,
|
||||||
|
@ -108,7 +110,9 @@ Future<void> build({
|
||||||
packagesPath: packagesPath,
|
packagesPath: packagesPath,
|
||||||
compilationTraceFilePath: compilationTraceFilePath,
|
compilationTraceFilePath: compilationTraceFilePath,
|
||||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||||
buildHotUpdate: buildHotUpdate,
|
createPatch: createPatch,
|
||||||
|
buildNumber: buildNumber,
|
||||||
|
baselineDir: baselineDir,
|
||||||
);
|
);
|
||||||
if (snapshotExitCode != 0) {
|
if (snapshotExitCode != 0) {
|
||||||
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
|
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
|
||||||
|
|
|
@ -12,7 +12,9 @@ import 'build.dart';
|
||||||
class BuildApkCommand extends BuildSubCommand {
|
class BuildApkCommand extends BuildSubCommand {
|
||||||
BuildApkCommand({bool verboseHelp = false}) {
|
BuildApkCommand({bool verboseHelp = false}) {
|
||||||
usesTargetOption();
|
usesTargetOption();
|
||||||
addBuildModeFlags();
|
addBuildModeFlags(verboseHelp: verboseHelp);
|
||||||
|
addDynamicModeFlags(verboseHelp: verboseHelp);
|
||||||
|
addDynamicPatchingFlags(verboseHelp: verboseHelp);
|
||||||
usesFlavorOption();
|
usesFlavorOption();
|
||||||
usesPubOption();
|
usesPubOption();
|
||||||
usesBuildNumberOption();
|
usesBuildNumberOption();
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../build_info.dart';
|
import '../build_info.dart';
|
||||||
import '../bundle.dart';
|
import '../bundle.dart';
|
||||||
|
@ -14,7 +16,10 @@ class BuildBundleCommand extends BuildSubCommand {
|
||||||
BuildBundleCommand({bool verboseHelp = false}) {
|
BuildBundleCommand({bool verboseHelp = false}) {
|
||||||
usesTargetOption();
|
usesTargetOption();
|
||||||
usesFilesystemOptions(hide: !verboseHelp);
|
usesFilesystemOptions(hide: !verboseHelp);
|
||||||
addBuildModeFlags();
|
usesBuildNumberOption();
|
||||||
|
addBuildModeFlags(verboseHelp: verboseHelp);
|
||||||
|
addDynamicModeFlags(verboseHelp: verboseHelp);
|
||||||
|
addDynamicBaselineFlags(verboseHelp: verboseHelp);
|
||||||
argParser
|
argParser
|
||||||
..addFlag('precompiled', negatable: false)
|
..addFlag('precompiled', negatable: false)
|
||||||
// This option is still referenced by the iOS build scripts. We should
|
// This option is still referenced by the iOS build scripts. We should
|
||||||
|
@ -31,23 +36,6 @@ class BuildBundleCommand extends BuildSubCommand {
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Track widget creation locations. Requires Dart 2.0 functionality.',
|
help: 'Track widget creation locations. Requires Dart 2.0 functionality.',
|
||||||
)
|
)
|
||||||
..addOption('precompile',
|
|
||||||
hide: !verboseHelp,
|
|
||||||
help: 'Precompile functions specified in input file. This flag is only '
|
|
||||||
'allowed when using --dynamic. It takes a Dart compilation trace '
|
|
||||||
'file produced by the training run of the application. With this '
|
|
||||||
'flag, instead of using default Dart VM snapshot provided by the '
|
|
||||||
'engine, the application will use its own snapshot that includes '
|
|
||||||
'additional compiled functions.'
|
|
||||||
)
|
|
||||||
..addFlag('hotupdate',
|
|
||||||
hide: !verboseHelp,
|
|
||||||
help: 'Build differential snapshot based on the last state of the build '
|
|
||||||
'tree and any changes to the application source code since then. '
|
|
||||||
'This flag is only allowed when using --dynamic. With this flag, '
|
|
||||||
'a partial VM snapshot is generated that is loaded on top of the '
|
|
||||||
'original VM snapshot that contains precompiled code.'
|
|
||||||
)
|
|
||||||
..addMultiOption(FlutterOptions.kExtraFrontEndOptions,
|
..addMultiOption(FlutterOptions.kExtraFrontEndOptions,
|
||||||
splitCommas: true,
|
splitCommas: true,
|
||||||
hide: true,
|
hide: true,
|
||||||
|
@ -86,6 +74,15 @@ class BuildBundleCommand extends BuildSubCommand {
|
||||||
|
|
||||||
final BuildMode buildMode = getBuildMode();
|
final BuildMode buildMode = getBuildMode();
|
||||||
|
|
||||||
|
int buildNumber;
|
||||||
|
try {
|
||||||
|
buildNumber = argResults['build-number'] != null
|
||||||
|
? int.parse(argResults['build-number']) : null;
|
||||||
|
} catch (e) {
|
||||||
|
throw UsageException(
|
||||||
|
'--build-number (${argResults['build-number']}) must be an int.', null);
|
||||||
|
}
|
||||||
|
|
||||||
await build(
|
await build(
|
||||||
platform: platform,
|
platform: platform,
|
||||||
buildMode: buildMode,
|
buildMode: buildMode,
|
||||||
|
@ -98,7 +95,9 @@ class BuildBundleCommand extends BuildSubCommand {
|
||||||
reportLicensedPackages: argResults['report-licensed-packages'],
|
reportLicensedPackages: argResults['report-licensed-packages'],
|
||||||
trackWidgetCreation: argResults['track-widget-creation'],
|
trackWidgetCreation: argResults['track-widget-creation'],
|
||||||
compilationTraceFilePath: argResults['precompile'],
|
compilationTraceFilePath: argResults['precompile'],
|
||||||
buildHotUpdate: argResults['hotupdate'],
|
createPatch: argResults['patch'],
|
||||||
|
buildNumber: buildNumber,
|
||||||
|
baselineDir: argResults['baseline-dir'],
|
||||||
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
|
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
|
||||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||||
fileSystemScheme: argResults['filesystem-scheme'],
|
fileSystemScheme: argResults['filesystem-scheme'],
|
||||||
|
|
|
@ -24,6 +24,8 @@ abstract class RunCommandBase extends FlutterCommand {
|
||||||
// Used by run and drive commands.
|
// Used by run and drive commands.
|
||||||
RunCommandBase({ bool verboseHelp = false }) {
|
RunCommandBase({ bool verboseHelp = false }) {
|
||||||
addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
|
addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
|
||||||
|
addDynamicModeFlags(verboseHelp: verboseHelp);
|
||||||
|
addDynamicPatchingFlags(verboseHelp: verboseHelp);
|
||||||
usesFlavorOption();
|
usesFlavorOption();
|
||||||
argParser
|
argParser
|
||||||
..addFlag('trace-startup',
|
..addFlag('trace-startup',
|
||||||
|
@ -104,23 +106,6 @@ class RunCommand extends RunCommandBase {
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Specify a pre-built application binary to use when running.',
|
help: 'Specify a pre-built application binary to use when running.',
|
||||||
)
|
)
|
||||||
..addOption('precompile',
|
|
||||||
hide: !verboseHelp,
|
|
||||||
help: 'Precompile functions specified in input file. This flag is only '
|
|
||||||
'allowed when using --dynamic. It takes a Dart compilation trace '
|
|
||||||
'file produced by the training run of the application. With this '
|
|
||||||
'flag, instead of using default Dart VM snapshot provided by the '
|
|
||||||
'engine, the application will use its own snapshot that includes '
|
|
||||||
'additional functions.'
|
|
||||||
)
|
|
||||||
..addFlag('hotupdate',
|
|
||||||
hide: !verboseHelp,
|
|
||||||
help: 'Build differential snapshot based on the last state of the build '
|
|
||||||
'tree and any changes to the application source code since then. '
|
|
||||||
'This flag is only allowed when using --dynamic. With this flag, '
|
|
||||||
'a partial VM snapshot is generated that is loaded on top of the '
|
|
||||||
'original VM snapshot that contains precompiled code.'
|
|
||||||
)
|
|
||||||
..addFlag('track-widget-creation',
|
..addFlag('track-widget-creation',
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
help: 'Track widget creation locations. Requires Dart 2.0 functionality.',
|
help: 'Track widget creation locations. Requires Dart 2.0 functionality.',
|
||||||
|
|
|
@ -241,6 +241,64 @@ abstract class FlutterCommand extends Command<void> {
|
||||||
'--release or --profile; --debug always has this enabled.');
|
'--release or --profile; --debug always has this enabled.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addDynamicModeFlags({bool verboseHelp = false}) {
|
||||||
|
argParser.addOption('precompile',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
help: 'Precompile functions specified in input file. This flag is only '
|
||||||
|
'allowed when using --dynamic. It takes a Dart compilation trace '
|
||||||
|
'file produced by the training run of the application. With this '
|
||||||
|
'flag, instead of using default Dart VM snapshot provided by the '
|
||||||
|
'engine, the application will use its own snapshot that includes '
|
||||||
|
'additional compiled functions.'
|
||||||
|
);
|
||||||
|
argParser.addFlag('patch',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
negatable: false,
|
||||||
|
help: 'Generate dynamic patch for current changes from baseline.\n'
|
||||||
|
'Dynamic patch is generated relative to baseline package.\n'
|
||||||
|
'This flag is only allowed when using --dynamic.\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addDynamicPatchingFlags({bool verboseHelp = false}) {
|
||||||
|
argParser.addOption('patch-number',
|
||||||
|
defaultsTo: '1',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
help: 'An integer used as an internal version number for dynamic patch.\n'
|
||||||
|
'Each update should have a unique number to differentiate from previous '
|
||||||
|
'patches for same \'versionCode\' on Android or \'CFBundleVersion\' on iOS.\n'
|
||||||
|
'This flag is only used when --dynamic --patch is specified.\n'
|
||||||
|
);
|
||||||
|
argParser.addOption('patch-dir',
|
||||||
|
defaultsTo: 'public',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
help: 'The directory where to store generated dynamic patches.\n'
|
||||||
|
'This directory can be deployed to a CDN such as Firebase Hosting.\n'
|
||||||
|
'It is recommended to store this directory in version control.\n'
|
||||||
|
'This flag is only used when --dynamic --patch is specified.\n'
|
||||||
|
);
|
||||||
|
argParser.addFlag('baseline',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
negatable: false,
|
||||||
|
help: 'Save built package as baseline for future dynamic patching.\n'
|
||||||
|
'Built package, such as APK file on Android, is saved and '
|
||||||
|
'can be used to generate dynamic patches in later builds.\n'
|
||||||
|
'This flag is only allowed when using --dynamic.\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
addDynamicBaselineFlags(verboseHelp: verboseHelp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addDynamicBaselineFlags({bool verboseHelp = false}) {
|
||||||
|
argParser.addOption('baseline-dir',
|
||||||
|
defaultsTo: '.baseline',
|
||||||
|
hide: !verboseHelp,
|
||||||
|
help: 'The directory where to store and find generated baseline packages.\n'
|
||||||
|
'It is recommended to store this directory in version control.\n'
|
||||||
|
'This flag is only used when --dynamic --baseline is specified.\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void usesFuchsiaOptions({bool hide = false}) {
|
void usesFuchsiaOptions({bool hide = false}) {
|
||||||
argParser.addOption(
|
argParser.addOption(
|
||||||
'target-model',
|
'target-model',
|
||||||
|
@ -308,6 +366,16 @@ abstract class FlutterCommand extends Command<void> {
|
||||||
'--build-number (${argResults['build-number']}) must be an int.', null);
|
'--build-number (${argResults['build-number']}) must be an int.', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int patchNumber;
|
||||||
|
try {
|
||||||
|
patchNumber = argParser.options.containsKey('patch-number') && argResults['patch-number'] != null
|
||||||
|
? int.parse(argResults['patch-number'])
|
||||||
|
: null;
|
||||||
|
} catch (e) {
|
||||||
|
throw UsageException(
|
||||||
|
'--patch-number (${argResults['patch-number']}) must be an int.', null);
|
||||||
|
}
|
||||||
|
|
||||||
return BuildInfo(getBuildMode(),
|
return BuildInfo(getBuildMode(),
|
||||||
argParser.options.containsKey('flavor')
|
argParser.options.containsKey('flavor')
|
||||||
? argResults['flavor']
|
? argResults['flavor']
|
||||||
|
@ -316,9 +384,19 @@ abstract class FlutterCommand extends Command<void> {
|
||||||
compilationTraceFilePath: argParser.options.containsKey('precompile')
|
compilationTraceFilePath: argParser.options.containsKey('precompile')
|
||||||
? argResults['precompile']
|
? argResults['precompile']
|
||||||
: null,
|
: null,
|
||||||
buildHotUpdate: argParser.options.containsKey('hotupdate')
|
createBaseline: argParser.options.containsKey('baseline')
|
||||||
? argResults['hotupdate']
|
? argResults['baseline']
|
||||||
: false,
|
: false,
|
||||||
|
createPatch: argParser.options.containsKey('patch')
|
||||||
|
? argResults['patch']
|
||||||
|
: false,
|
||||||
|
patchNumber: patchNumber,
|
||||||
|
patchDir: argParser.options.containsKey('patch-dir')
|
||||||
|
? argResults['patch-dir']
|
||||||
|
: null,
|
||||||
|
baselineDir: argParser.options.containsKey('baseline-dir')
|
||||||
|
? argResults['baseline-dir']
|
||||||
|
: null,
|
||||||
extraFrontEndOptions: argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions)
|
extraFrontEndOptions: argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions)
|
||||||
? argResults[FlutterOptions.kExtraFrontEndOptions]
|
? argResults[FlutterOptions.kExtraFrontEndOptions]
|
||||||
: null,
|
: null,
|
||||||
|
@ -571,15 +649,21 @@ abstract class FlutterCommand extends Command<void> {
|
||||||
? argResults['dynamic'] : false;
|
? argResults['dynamic'] : false;
|
||||||
final String compilationTraceFilePath = argParser.options.containsKey('precompile')
|
final String compilationTraceFilePath = argParser.options.containsKey('precompile')
|
||||||
? argResults['precompile'] : null;
|
? argResults['precompile'] : null;
|
||||||
final bool buildHotUpdate = argParser.options.containsKey('hotupdate')
|
final bool createBaseline = argParser.options.containsKey('baseline')
|
||||||
? argResults['hotupdate'] : false;
|
? argResults['baseline'] : false;
|
||||||
|
final bool createPatch = argParser.options.containsKey('patch')
|
||||||
|
? argResults['patch'] : false;
|
||||||
|
|
||||||
if (compilationTraceFilePath != null && getBuildMode() == BuildMode.debug)
|
if (compilationTraceFilePath != null && getBuildMode() == BuildMode.debug)
|
||||||
throw ToolExit('Error: --precompile is not allowed when --debug is specified.');
|
throw ToolExit('Error: --precompile is not allowed when --debug is specified.');
|
||||||
if (compilationTraceFilePath != null && !dynamicFlag)
|
if (compilationTraceFilePath != null && !dynamicFlag)
|
||||||
throw ToolExit('Error: --precompile is allowed only when --dynamic is specified.');
|
throw ToolExit('Error: --precompile is allowed only when --dynamic is specified.');
|
||||||
if (buildHotUpdate && compilationTraceFilePath == null)
|
if (createBaseline && createPatch)
|
||||||
throw ToolExit('Error: --hotupdate is allowed only when --precompile is specified.');
|
throw ToolExit('Error: Only one of --baseline, --patch is allowed.');
|
||||||
|
if (createBaseline && compilationTraceFilePath == null)
|
||||||
|
throw ToolExit('Error: --baseline is allowed only when --precompile is specified.');
|
||||||
|
if (createPatch && compilationTraceFilePath == null)
|
||||||
|
throw ToolExit('Error: --patch is allowed only when --precompile is specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationPackageStore applicationPackages;
|
ApplicationPackageStore applicationPackages;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||||
import 'package:flutter_tools/src/artifacts.dart';
|
import 'package:flutter_tools/src/artifacts.dart';
|
||||||
|
@ -551,7 +552,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
), isNot(equals(0)));
|
), isNot(equals(0)));
|
||||||
}, overrides: contextOverrides);
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
|
@ -573,7 +574,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -614,7 +615,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -644,7 +645,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
), isNot(equals(0)));
|
), isNot(equals(0)));
|
||||||
}, overrides: contextOverrides);
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
|
@ -666,7 +667,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -706,7 +707,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -735,7 +736,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
), isNot(equals(0)));
|
), isNot(equals(0)));
|
||||||
}, overrides: contextOverrides);
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
|
@ -757,7 +758,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -797,7 +798,7 @@ void main() {
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: outputPath,
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: false,
|
createPatch: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -817,16 +818,30 @@ void main() {
|
||||||
]);
|
]);
|
||||||
}, overrides: contextOverrides);
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
testUsingContext('builds Android arm release JIT snapshot for hot update', () async {
|
testUsingContext('builds Android release JIT dynamic patch - existing snapshot', () async {
|
||||||
fs.file('main.dill').writeAsStringSync('binary magic');
|
fs.file('main.dill').writeAsStringSync('binary magic');
|
||||||
|
|
||||||
final String outputPath = fs.path.join('build', 'foo');
|
final Archive baselineApk = Archive()
|
||||||
fs.directory(outputPath).createSync(recursive: true);
|
..addFile(ArchiveFile('assets/flutter_assets/isolate_snapshot_instr',
|
||||||
fs.file(fs.path.join(outputPath, 'isolate_snapshot_instr')).createSync();
|
'isolateSnapshotInstr'.length, 'isolateSnapshotInstr'.codeUnits))
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/vm_snapshot_data',
|
||||||
|
'engineVmSnapshotData'.length, 'engineVmSnapshotData'.codeUnits));
|
||||||
|
|
||||||
|
fs.file('.baseline/100.apk')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytesSync(ZipEncoder().encode(baselineApk), flush: true);
|
||||||
|
|
||||||
|
fs.file('engine_vm_snapshot_data')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('engineVmSnapshotData', flush: true);
|
||||||
|
|
||||||
|
fs.file('build/foo/isolate_snapshot_instr')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('isolateSnapshotInstr', flush: true);
|
||||||
|
|
||||||
genSnapshot.outputs = <String, String>{
|
genSnapshot.outputs = <String, String>{
|
||||||
fs.path.join(outputPath, 'isolate_snapshot_data'): '',
|
'build/foo/isolate_snapshot_data': '',
|
||||||
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ',
|
'build/foo/snapshot.d': 'build/foo/vm_snapshot_data : ',
|
||||||
};
|
};
|
||||||
|
|
||||||
final int genSnapshotExitCode = await snapshotter.build(
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
|
@ -834,9 +849,11 @@ void main() {
|
||||||
buildMode: BuildMode.release,
|
buildMode: BuildMode.release,
|
||||||
mainPath: 'main.dill',
|
mainPath: 'main.dill',
|
||||||
packagesPath: '.packages',
|
packagesPath: '.packages',
|
||||||
outputPath: outputPath,
|
outputPath: 'build/foo',
|
||||||
compilationTraceFilePath: kTrace,
|
compilationTraceFilePath: kTrace,
|
||||||
buildHotUpdate: true,
|
createPatch: true,
|
||||||
|
buildNumber: 100,
|
||||||
|
baselineDir: '.baseline',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(genSnapshotExitCode, 0);
|
expect(genSnapshotExitCode, 0);
|
||||||
|
@ -858,5 +875,148 @@ void main() {
|
||||||
]);
|
]);
|
||||||
}, overrides: contextOverrides);
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
|
testUsingContext('builds Android release JIT dynamic patch - extracts snapshot', () async {
|
||||||
|
fs.file('main.dill').writeAsStringSync('binary magic');
|
||||||
|
|
||||||
|
final Archive baselineApk = Archive()
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/isolate_snapshot_instr',
|
||||||
|
'isolateSnapshotInstr'.length, 'isolateSnapshotInstr'.codeUnits))
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/vm_snapshot_data',
|
||||||
|
'engineVmSnapshotData'.length, 'engineVmSnapshotData'.codeUnits));
|
||||||
|
|
||||||
|
fs.file('.baseline/100.apk')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytesSync(ZipEncoder().encode(baselineApk), flush: true);
|
||||||
|
|
||||||
|
fs.file('engine_vm_snapshot_data')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('engineVmSnapshotData', flush: true);
|
||||||
|
|
||||||
|
genSnapshot.outputs = <String, String>{
|
||||||
|
'build/foo/isolate_snapshot_data': '',
|
||||||
|
'build/foo/snapshot.d': 'build/foo/vm_snapshot_data : ',
|
||||||
|
};
|
||||||
|
|
||||||
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
|
platform: TargetPlatform.android_arm,
|
||||||
|
buildMode: BuildMode.release,
|
||||||
|
mainPath: 'main.dill',
|
||||||
|
packagesPath: '.packages',
|
||||||
|
outputPath: 'build/foo',
|
||||||
|
compilationTraceFilePath: kTrace,
|
||||||
|
createPatch: true,
|
||||||
|
buildNumber: 100,
|
||||||
|
baselineDir: '.baseline',
|
||||||
|
);
|
||||||
|
|
||||||
|
// The file was extracted from baseline APK.
|
||||||
|
expect(fs.file('build/foo/isolate_snapshot_instr').existsSync(), true);
|
||||||
|
expect(fs.file('build/foo/isolate_snapshot_instr').readAsStringSync(), 'isolateSnapshotInstr');
|
||||||
|
|
||||||
|
expect(genSnapshotExitCode, 0);
|
||||||
|
expect(genSnapshot.callCount, 1);
|
||||||
|
expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
|
||||||
|
expect(genSnapshot.snapshotType.mode, BuildMode.release);
|
||||||
|
expect(genSnapshot.packagesPath, '.packages');
|
||||||
|
expect(genSnapshot.additionalArgs, <String>[
|
||||||
|
'--deterministic',
|
||||||
|
'--snapshot_kind=app-jit',
|
||||||
|
'--load_compilation_trace=$kTrace',
|
||||||
|
'--load_vm_snapshot_data=$kEngineVmSnapshotData',
|
||||||
|
'--load_isolate_snapshot_data=$kEngineIsolateSnapshotData',
|
||||||
|
'--isolate_snapshot_data=build/foo/isolate_snapshot_data',
|
||||||
|
'--reused_instructions=build/foo/isolate_snapshot_instr',
|
||||||
|
'--no-sim-use-hardfp',
|
||||||
|
'--no-use-integer-division',
|
||||||
|
'main.dill',
|
||||||
|
]);
|
||||||
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
|
testUsingContext('builds Android release JIT dynamic patch - mismatched snapshot 1', () async {
|
||||||
|
fs.file('main.dill').writeAsStringSync('binary magic');
|
||||||
|
|
||||||
|
final Archive baselineApk = Archive()
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/isolate_snapshot_instr',
|
||||||
|
'isolateSnapshotInstr'.length, 'isolateSnapshotInstr'.codeUnits))
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/vm_snapshot_data',
|
||||||
|
'engineVmSnapshotData'.length, 'engineVmSnapshotData'.codeUnits));
|
||||||
|
|
||||||
|
fs.file('.baseline/100.apk')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytesSync(ZipEncoder().encode(baselineApk), flush: true);
|
||||||
|
|
||||||
|
fs.file('engine_vm_snapshot_data')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('mismatchedEngineVmSnapshotData', flush: true);
|
||||||
|
|
||||||
|
fs.file('build/foo/isolate_snapshot_instr')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('isolateSnapshotInstr', flush: true);
|
||||||
|
|
||||||
|
genSnapshot.outputs = <String, String>{
|
||||||
|
'build/foo/isolate_snapshot_data': '',
|
||||||
|
'build/foo/snapshot.d': 'build/foo/vm_snapshot_data : ',
|
||||||
|
};
|
||||||
|
|
||||||
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
|
platform: TargetPlatform.android_arm,
|
||||||
|
buildMode: BuildMode.release,
|
||||||
|
mainPath: 'main.dill',
|
||||||
|
packagesPath: '.packages',
|
||||||
|
outputPath: 'build/foo',
|
||||||
|
compilationTraceFilePath: kTrace,
|
||||||
|
createPatch: true,
|
||||||
|
buildNumber: 100,
|
||||||
|
baselineDir: '.baseline',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(genSnapshotExitCode, 1);
|
||||||
|
expect(genSnapshot.callCount, 0);
|
||||||
|
|
||||||
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
|
testUsingContext('builds Android release JIT dynamic patch - mismatched snapshot 2', () async {
|
||||||
|
fs.file('main.dill').writeAsStringSync('binary magic');
|
||||||
|
|
||||||
|
final Archive baselineApk = Archive()
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/isolate_snapshot_instr',
|
||||||
|
'isolateSnapshotInstr'.length, 'isolateSnapshotInstr'.codeUnits))
|
||||||
|
..addFile(ArchiveFile('assets/flutter_assets/vm_snapshot_data',
|
||||||
|
'engineVmSnapshotData'.length, 'engineVmSnapshotData'.codeUnits));
|
||||||
|
|
||||||
|
fs.file('.baseline/100.apk')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsBytesSync(ZipEncoder().encode(baselineApk), flush: true);
|
||||||
|
|
||||||
|
fs.file('engine_vm_snapshot_data')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('engineVmSnapshotData', flush: true);
|
||||||
|
|
||||||
|
fs.file('build/foo/isolate_snapshot_instr')
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('mismatchedIsolateSnapshotInstr', flush: true);
|
||||||
|
|
||||||
|
genSnapshot.outputs = <String, String>{
|
||||||
|
'build/foo/isolate_snapshot_data': '',
|
||||||
|
'build/foo/snapshot.d': 'build/foo/vm_snapshot_data : ',
|
||||||
|
};
|
||||||
|
|
||||||
|
final int genSnapshotExitCode = await snapshotter.build(
|
||||||
|
platform: TargetPlatform.android_arm,
|
||||||
|
buildMode: BuildMode.release,
|
||||||
|
mainPath: 'main.dill',
|
||||||
|
packagesPath: '.packages',
|
||||||
|
outputPath: 'build/foo',
|
||||||
|
compilationTraceFilePath: kTrace,
|
||||||
|
createPatch: true,
|
||||||
|
buildNumber: 100,
|
||||||
|
baselineDir: '.baseline',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(genSnapshotExitCode, 1);
|
||||||
|
expect(genSnapshot.callCount, 0);
|
||||||
|
|
||||||
|
}, overrides: contextOverrides);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ class MockApplicationPackageStore extends ApplicationPackageStore {
|
||||||
android: AndroidApk(
|
android: AndroidApk(
|
||||||
id: 'io.flutter.android.mock',
|
id: 'io.flutter.android.mock',
|
||||||
file: fs.file('/mock/path/to/android/SkyShell.apk'),
|
file: fs.file('/mock/path/to/android/SkyShell.apk'),
|
||||||
|
versionCode: 1,
|
||||||
launchActivity: 'io.flutter.android.mock.MockActivity'
|
launchActivity: 'io.flutter.android.mock.MockActivity'
|
||||||
),
|
),
|
||||||
iOS: BuildableIOSApp(MockIosProject())
|
iOS: BuildableIOSApp(MockIosProject())
|
||||||
|
|
Loading…
Reference in a new issue