Unverified Commit e2a3c2ee authored by Stanislav Baranov's avatar Stanislav Baranov Committed by GitHub

Remove support for building dynamic patches on Android. (#31359)

parent 0fb6a050
......@@ -4,12 +4,9 @@
import 'dart:async';
import 'package:archive/archive.dart';
import 'package:bsdiff/bsdiff.dart';
import 'package:meta/meta.dart';
import '../android/android_sdk.dart';
import '../application_package.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart';
......@@ -20,7 +17,6 @@ import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../convert.dart';
import '../flutter_manifest.dart';
import '../globals.dart';
import '../project.dart';
......@@ -425,8 +421,6 @@ Future<void> _buildGradleProjectV2(
command.add('-Ptrack-widget-creation=${buildInfo.trackWidgetCreation}');
if (buildInfo.compilationTraceFilePath != null)
command.add('-Pcompilation-trace-file=${buildInfo.compilationTraceFilePath}');
if (buildInfo.createPatch)
command.add('-Ppatch=true');
if (buildInfo.extraFrontEndOptions != null)
command.add('-Pextra-front-end-options=${buildInfo.extraFrontEndOptions}');
if (buildInfo.extraGenSnapshotOptions != null)
......@@ -497,105 +491,6 @@ Future<void> _buildGradleProjectV2(
}
printStatus('Built ${fs.path.relative(apkFile.path)}$appSize.');
if (buildInfo.createBaseline) {
// Save baseline apk for generating dynamic patches in later builds.
final AndroidApk package = AndroidApk.fromApk(apkFile);
final Directory baselineDir = fs.directory(buildInfo.baselineDir);
final File baselineApkFile = baselineDir.childFile('${package.versionCode}.apk');
baselineApkFile.parent.createSync(recursive: true);
apkFile.copySync(baselineApkFile.path);
printStatus('Saved baseline package ${baselineApkFile.path}.');
}
if (buildInfo.createPatch) {
final AndroidApk package = AndroidApk.fromApk(apkFile);
final Directory baselineDir = fs.directory(buildInfo.baselineDir);
final File baselineApkFile = baselineDir.childFile('${package.versionCode}.apk');
if (!baselineApkFile.existsSync())
throwToolExit('Error: Could not find baseline package ${baselineApkFile.path}.');
printStatus('Found baseline package ${baselineApkFile.path}.');
printStatus('Creating dynamic patch...');
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)
continue;
// Ignore changes to signature manifests.
if (newFile.name.startsWith('META-INF/'))
continue;
final ArchiveFile oldFile = oldApk.findFile(newFile.name);
if (oldFile != null && oldFile.crc32 == newFile.crc32)
continue;
// Only allow certain changes.
if (!newFile.name.startsWith('assets/') &&
!(buildInfo.usesAot && newFile.name.endsWith('.so')))
throwToolExit("Error: Dynamic patching doesn't support changes to ${newFile.name}.");
final String name = newFile.name;
if (name.contains('_snapshot_') || name.endsWith('.so')) {
final List<int> diff = bsdiff(oldFile.content, newFile.content);
final int ratio = 100 * diff.length ~/ newFile.content.length;
printStatus('Deflated $name by ${ratio == 0 ? 99 : 100 - ratio}%');
update.addFile(ArchiveFile(name + '.bzdiff40', diff.length, diff));
} else {
update.addFile(ArchiveFile(name, newFile.content.length, newFile.content));
}
}
File updateFile;
if (buildInfo.patchNumber != null) {
updateFile = fs.directory(buildInfo.patchDir)
.childFile('${package.versionCode}-${buildInfo.patchNumber}.zip');
} else {
updateFile = fs.directory(buildInfo.patchDir)
.childFile('${package.versionCode}.zip');
}
if (update.files.isEmpty) {
printStatus('No changes detected, creating rollback patch.');
}
final List<String> checksumFiles = <String>[
'assets/isolate_snapshot_data',
'assets/isolate_snapshot_instr',
'assets/flutter_assets/isolate_snapshot_data',
];
int baselineChecksum = 0;
for (String fn in checksumFiles) {
final ArchiveFile oldFile = oldApk.findFile(fn);
if (oldFile != null)
baselineChecksum = getCrc32(oldFile.content, baselineChecksum);
}
if (baselineChecksum == 0)
throwToolExit('Error: Could not find baseline VM snapshot.');
final Map<String, dynamic> manifest = <String, dynamic>{
'baselineChecksum': baselineChecksum,
'buildNumber': package.versionCode,
};
if (buildInfo.patchNumber != null) {
manifest.addAll(<String, dynamic>{
'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);
final String patchSize = getSizeAsMB(updateFile.lengthSync());
printStatus('Created dynamic patch ${updateFile.path} ($patchSize).');
}
} else {
final File bundleFile = _findBundleFile(project, buildInfo);
if (bundleFile == null)
......
......@@ -4,8 +4,6 @@
import 'dart:async';
import 'package:archive/archive.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import '../android/android_sdk.dart';
......@@ -378,9 +376,6 @@ class JITSnapshotter {
@required String packagesPath,
@required String outputPath,
@required String compilationTraceFilePath,
@required bool createPatch,
String buildNumber,
String baselineDir,
List<String> extraGenSnapshotOptions = const <String>[],
}) async {
if (!_isValidJitPlatform(platform)) {
......@@ -400,83 +395,6 @@ class JITSnapshotter {
mainPath, compilationTraceFilePath, engineVmSnapshotData, engineIsolateSnapshotData,
];
if (createPatch) {
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 ArchiveFile af = baselinePkg.findFile(
fs.path.join('assets/flutter_assets/vm_snapshot_instr'));
if (af != null) {
printError('Error: Invalid baseline package ${baselineApk.path}.');
return 1;
}
}
}
final String depfilePath = fs.path.join(outputDir.path, 'snapshot.d');
final List<String> genSnapshotArgs = <String>[
'--deterministic',
......@@ -490,10 +408,7 @@ class JITSnapshotter {
}
final Set<String> outputPaths = <String>{};
outputPaths.addAll(<String>[isolateSnapshotData]);
if (!createPatch) {
outputPaths.add(isolateSnapshotInstructions);
}
outputPaths.addAll(<String>[isolateSnapshotData, isolateSnapshotInstructions]);
// There are a couple special cases below where we create a snapshot
// with only the data section, which only contains interpreted code.
......@@ -520,11 +435,7 @@ class JITSnapshotter {
'--isolate_snapshot_data=$isolateSnapshotData',
]);
if (!createPatch) {
genSnapshotArgs.add('--isolate_snapshot_instructions=$isolateSnapshotInstructions');
} else {
genSnapshotArgs.add('--reused_instructions=$isolateSnapshotInstructions');
}
genSnapshotArgs.add('--isolate_snapshot_instructions=$isolateSnapshotInstructions');
if (platform == TargetPlatform.android_arm) {
// Use softfp for Android armv7 devices.
......@@ -552,7 +463,6 @@ class JITSnapshotter {
'buildMode': buildMode.toString(),
'targetPlatform': platform.toString(),
'entryPoint': mainPath,
'createPatch': createPatch.toString(),
'extraGenSnapshotOptions': extraGenSnapshotOptions.join(' '),
},
depfilePaths: <String>[],
......
......@@ -15,11 +15,6 @@ class BuildInfo {
this.flavor, {
this.trackWidgetCreation = false,
this.compilationTraceFilePath,
this.createBaseline = false,
this.createPatch = false,
this.patchNumber,
this.patchDir,
this.baselineDir,
this.extraFrontEndOptions,
this.extraGenSnapshotOptions,
this.buildSharedLibrary,
......@@ -49,25 +44,6 @@ class BuildInfo {
/// Dart compilation trace file to use for JIT VM snapshot.
final String compilationTraceFilePath;
/// Save baseline package.
final bool createBaseline;
/// Build differential snapshot.
final bool createPatch;
/// Internal version number of dynamic patch (not displayed to users).
/// Each patch may 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.
final String extraFrontEndOptions;
......@@ -127,7 +103,6 @@ class BuildInfo {
BuildInfo(mode, flavor,
trackWidgetCreation: trackWidgetCreation,
compilationTraceFilePath: compilationTraceFilePath,
createPatch: createPatch,
extraFrontEndOptions: extraFrontEndOptions,
extraGenSnapshotOptions: extraGenSnapshotOptions,
buildSharedLibrary: buildSharedLibrary,
......
......@@ -61,9 +61,6 @@ Future<void> build({
bool reportLicensedPackages = false,
bool trackWidgetCreation = false,
String compilationTraceFilePath,
bool createPatch = false,
String buildNumber,
String baselineDir,
List<String> extraFrontEndOptions = const <String>[],
List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots,
......@@ -134,9 +131,6 @@ Future<void> build({
packagesPath: packagesPath,
compilationTraceFilePath: compilationTraceFilePath,
extraGenSnapshotOptions: extraGenSnapshotOptions,
createPatch: createPatch,
buildNumber: buildNumber,
baselineDir: baselineDir,
);
if (snapshotExitCode != 0) {
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
......
......@@ -14,7 +14,6 @@ class BuildApkCommand extends BuildSubCommand {
usesTargetOption();
addBuildModeFlags(verboseHelp: verboseHelp);
addDynamicModeFlags(verboseHelp: verboseHelp);
addDynamicPatchingFlags(verboseHelp: verboseHelp);
usesFlavorOption();
usesPubOption();
usesBuildNumberOption();
......
......@@ -17,7 +17,6 @@ class BuildBundleCommand extends BuildSubCommand {
usesBuildNumberOption();
addBuildModeFlags(verboseHelp: verboseHelp);
addDynamicModeFlags(verboseHelp: verboseHelp);
addDynamicBaselineFlags(verboseHelp: verboseHelp);
argParser
..addFlag('precompiled', negatable: false)
// This option is still referenced by the iOS build scripts. We should
......@@ -70,8 +69,6 @@ class BuildBundleCommand extends BuildSubCommand {
final BuildMode buildMode = getBuildMode();
final String buildNumber = argResults['build-number'] != null ? argResults['build-number'] : null;
await build(
platform: platform,
buildMode: buildMode,
......@@ -84,9 +81,6 @@ class BuildBundleCommand extends BuildSubCommand {
reportLicensedPackages: argResults['report-licensed-packages'],
trackWidgetCreation: argResults['track-widget-creation'],
compilationTraceFilePath: argResults['compilation-trace-file'],
createPatch: argResults['patch'],
buildNumber: buildNumber,
baselineDir: argResults['baseline-dir'],
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
fileSystemScheme: argResults['filesystem-scheme'],
......
......@@ -25,7 +25,6 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
RunCommandBase({ bool verboseHelp = false }) {
addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
addDynamicModeFlags(verboseHelp: verboseHelp);
addDynamicPatchingFlags(verboseHelp: verboseHelp);
usesFlavorOption();
argParser
..addFlag('trace-startup',
......
......@@ -259,54 +259,6 @@ abstract class FlutterCommand extends Command<void> {
'--dynamic builds such as \'flutter build apk --dynamic\' to precompile\n'
'some code by the offline compiler.',
);
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',
hide: !verboseHelp,
help: 'An integer used as an internal version number for dynamic patch.\n'
'Each update may have a unique number to differentiate from previous\n'
'patches for same \'versionCode\' on Android or \'CFBundleVersion\' on iOS.\n'
'This optional setting allows several dynamic patches to coexist\n'
'for same baseline build, and is useful for canary and A-B testing\n'
'of dynamic patches.\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 }) {
......@@ -382,16 +334,6 @@ abstract class FlutterCommand extends Command<void> {
? argResults['build-number']
: 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);
}
String extraFrontEndOptions =
argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions)
? argResults[FlutterOptions.kExtraFrontEndOptions]
......@@ -416,19 +358,6 @@ abstract class FlutterCommand extends Command<void> {
compilationTraceFilePath: argParser.options.containsKey('compilation-trace-file')
? argResults['compilation-trace-file']
: null,
createBaseline: argParser.options.containsKey('baseline')
? argResults['baseline']
: 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: extraFrontEndOptions,
extraGenSnapshotOptions: argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions)
? argResults[FlutterOptions.kExtraGenSnapshotOptions]
......@@ -670,20 +599,6 @@ abstract class FlutterCommand extends Command<void> {
if (!fs.isFileSync(targetPath))
throw ToolExit(userMessages.flutterTargetFileMissing(targetPath));
}
final String compilationTraceFilePath = argParser.options.containsKey('compilation-trace-file')
? argResults['compilation-trace-file'] : null;
final bool createBaseline = argParser.options.containsKey('baseline')
? argResults['baseline'] : false;
final bool createPatch = argParser.options.containsKey('patch')
? argResults['patch'] : false;
if (createBaseline && createPatch)
throw ToolExit(userMessages.flutterBasePatchFlagsExclusive);
if (createBaseline && compilationTraceFilePath == null)
throw ToolExit(userMessages.flutterBaselineRequiresTraceFile);
if (createPatch && compilationTraceFilePath == null)
throw ToolExit(userMessages.flutterPatchRequiresTraceFile);
}
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