Unverified Commit 2c857b93 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Enable R8 (#40453)

parent e3c4609a
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
......@@ -159,8 +159,12 @@ class FlutterPlugin implements Plugin<Project> {
"gradle", "flutter_proguard_rules.pro")
project.android.buildTypes {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
useProguard true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
shrinkResources true
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
......
......@@ -306,6 +306,10 @@ Future<String> _initializeGradle(FlutterProject project) async {
final Status status = logger.startProgress('Initializing gradle...',
timeout: timeoutConfiguration.slowOperation);
// Update the project if needed.
// TODO(egarciad): https://github.com/flutter/flutter/issues/40460.
migrateToR8(android);
injectGradleWrapperIfNeeded(android);
final String gradle = _locateGradlewExecutable(android);
......@@ -335,6 +339,31 @@ Future<String> _initializeGradle(FlutterProject project) async {
return gradle;
}
/// Migrates the Android's [directory] to R8.
/// https://developer.android.com/studio/build/shrink-code
@visibleForTesting
void migrateToR8(Directory directory) {
final File gradleProperties = directory.childFile('gradle.properties');
if (!gradleProperties.existsSync()) {
throwToolExit('Expected file ${gradleProperties.path}.');
}
final String propertiesContent = gradleProperties.readAsStringSync();
if (propertiesContent.contains('android.enableR8')) {
printTrace('gradle.properties already sets `android.enableR8`');
return;
}
printTrace('set `android.enableR8=true` in gradle.properties');
try {
gradleProperties
.writeAsStringSync('android.enableR8=true\n', mode: FileMode.append);
} on FileSystemException {
throwToolExit(
'The tool failed to add `android.enableR8=true` to ${gradleProperties.path}. '
'Please update the file manually and try this command again.'
);
}
}
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
void injectGradleWrapperIfNeeded(Directory directory) {
copyDirectorySync(
......@@ -754,8 +783,8 @@ Future<void> _buildGradleProjectV2(
if (androidBuildInfo.splitPerAbi) {
command.add('-Psplit-per-abi=true');
}
if (androidBuildInfo.proguard) {
command.add('-Pproguard=true');
if (androidBuildInfo.shrink) {
command.add('-Pshrink=true');
}
if (androidBuildInfo.targetArchs.isNotEmpty) {
final String targetPlatforms = androidBuildInfo.targetArchs
......@@ -772,7 +801,7 @@ Future<void> _buildGradleProjectV2(
}
command.add(assembleTask);
bool potentialAndroidXFailure = false;
bool potentialProguardFailure = false;
bool potentialR8Failure = false;
final Stopwatch sw = Stopwatch()..start();
int exitCode = 1;
try {
......@@ -789,10 +818,10 @@ Future<void> _buildGradleProjectV2(
if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) {
potentialAndroidXFailure = true;
}
// Proguard errors include this url.
if (!potentialProguardFailure && androidBuildInfo.proguard &&
line.contains('http://proguard.sourceforge.net')) {
potentialProguardFailure = true;
// R8 errors include references to this package.
if (!potentialR8Failure && androidBuildInfo.shrink &&
line.contains('com.android.tools.r8')) {
potentialR8Failure = true;
}
// Always print the full line in verbose mode.
if (logger.isVerbose) {
......@@ -808,12 +837,12 @@ Future<void> _buildGradleProjectV2(
}
if (exitCode != 0) {
if (potentialProguardFailure) {
if (potentialR8Failure) {
final String exclamationMark = terminal.color('[!]', TerminalColor.red);
printStatus('$exclamationMark Proguard may have failed to optimize the Java bytecode.', emphasis: true);
printStatus('To disable proguard, pass the `--no-proguard` flag to this command.', indent: 4);
printStatus('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard', indent: 4);
BuildEvent('proguard-failure').send();
printStatus('$exclamationMark The shrinker may have failed to optimize the Java bytecode.', emphasis: true);
printStatus('To disable the shrinker, pass the `--no-shrink` flag to this command.', indent: 4);
printStatus('To learn more, see: https://developer.android.com/studio/build/shrink-code', indent: 4);
BuildEvent('r8-failure').send();
} else if (potentialAndroidXFailure) {
printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.');
BuildEvent('android-x-failure').send();
......
......@@ -92,7 +92,7 @@ class AndroidBuildInfo {
AndroidArch.arm64_v8a,
],
this.splitPerAbi = false,
this.proguard = false,
this.shrink = false,
});
// The build info containing the mode and flavor.
......@@ -105,8 +105,8 @@ class AndroidBuildInfo {
/// will be produced.
final bool splitPerAbi;
/// Whether to enable Proguard on release mode.
final bool proguard;
/// Whether to enable code shrinking on release mode.
final bool shrink;
/// The target platforms for the build.
final Iterable<AndroidArch> targetArchs;
......
......@@ -21,6 +21,7 @@ class BuildApkCommand extends BuildSubCommand {
usesPubOption();
usesBuildNumberOption();
usesBuildNameOption();
addShrinkingFlag();
argParser
..addFlag('split-per-abi',
......@@ -28,12 +29,6 @@ class BuildApkCommand extends BuildSubCommand {
help: 'Whether to split the APKs per ABIs. '
'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',
)
..addFlag('proguard',
negatable: true,
defaultsTo: false,
help: 'Whether to enable Proguard on release mode. '
'To learn more, see: https://flutter.dev/docs/deployment/android#enabling-proguard',
)
..addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64'],
......@@ -83,10 +78,11 @@ class BuildApkCommand extends BuildSubCommand {
@override
Future<FlutterCommandResult> runCommand() async {
final BuildInfo buildInfo = getBuildInfo();
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(
buildInfo,
splitPerAbi: argResults['split-per-abi'],
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName),
proguard: argResults['proguard'],
shrink: argResults['shrink'],
);
if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) {
......
......@@ -19,15 +19,10 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesPubOption();
usesBuildNumberOption();
usesBuildNameOption();
addShrinkingFlag();
argParser
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
..addFlag('proguard',
negatable: true,
defaultsTo: false,
help: 'Whether to enable Proguard on release mode. '
'To learn more, see: https://flutter.dev/docs/deployment/android#enabling-proguard',
)
..addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64'],
......@@ -70,7 +65,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
Future<FlutterCommandResult> runCommand() async {
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(getBuildInfo(),
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName),
proguard: argResults['proguard'],
shrink: argResults['shrink'],
);
await androidBuilder.buildAab(
project: FlutterProject.current(),
......
......@@ -277,6 +277,19 @@ abstract class FlutterCommand extends Command<void> {
help: 'Build a release version of your app${defaultToRelease ? ' (default mode)' : ''}.');
}
void addShrinkingFlag() {
argParser.addFlag('shrink',
negatable: true,
defaultsTo: true,
help: 'Whether to enable code shrinking on release mode.'
'When enabling shrinking, you also benefit from obfuscation, '
'which shortens the names of your app’s classes and members, '
'and optimization, which applies more aggressive strategies to '
'further reduce the size of your app.'
'To learn more, see: https://developer.android.com/studio/build/shrink-code'
);
}
void usesFuchsiaOptions({ bool hide = false }) {
argParser.addOption(
'target-model',
......
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
{{#androidX}}
android.useAndroidX=true
android.enableJetifier=true
......
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
{{#androidX}}
android.useAndroidX=true
android.enableJetifier=true
......
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
{{#androidX}}
android.useAndroidX=true
android.enableJetifier=true
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
......@@ -13,6 +12,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
......@@ -867,6 +867,13 @@ flutter:
gradleWrapperDirectory
.childFile(gradleBinary)
.writeAsStringSync('irrelevant');
fs.currentDirectory
.childDirectory('android')
.createSync();
fs.currentDirectory
.childDirectory('android')
.childFile('gradle.properties')
.writeAsStringSync('irrelevant');
gradleWrapperDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
......@@ -1072,6 +1079,82 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
});
});
group('migrateToR8', () {
MemoryFileSystem memoryFileSystem;
setUp(() {
memoryFileSystem = MemoryFileSystem();
});
testUsingContext('throws ToolExit if gradle.properties doesn\'t exist', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
expect(() {
migrateToR8(sampleAppAndroid);
}, throwsToolExit(message: 'Expected file ${sampleAppAndroid.path}'));
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
});
testUsingContext('throws ToolExit if it cannot write gradle.properties', () {
final MockDirectory sampleAppAndroid = MockDirectory();
final MockFile gradleProperties = MockFile();
when(gradleProperties.path).thenReturn('foo/gradle.properties');
when(gradleProperties.existsSync()).thenReturn(true);
when(gradleProperties.readAsStringSync()).thenReturn('');
when(gradleProperties.writeAsStringSync('android.enableR8=true\n', mode: FileMode.append))
.thenThrow(const FileSystemException());
when(sampleAppAndroid.childFile('gradle.properties'))
.thenReturn(gradleProperties);
expect(() {
migrateToR8(sampleAppAndroid);
},
throwsToolExit(message:
'The tool failed to add `android.enableR8=true` to foo/gradle.properties. '
'Please update the file manually and try this command again.'));
});
testUsingContext('does not update gradle.properties if it already uses R8', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
sampleAppAndroid.childFile('gradle.properties')
.writeAsStringSync('android.enableR8=true');
migrateToR8(sampleAppAndroid);
expect(testLogger.traceText,
contains('gradle.properties already sets `android.enableR8`'));
expect(sampleAppAndroid.childFile('gradle.properties').readAsStringSync(),
equals('android.enableR8=true'));
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
});
testUsingContext('sets android.enableR8=true', () {
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
sampleAppAndroid.createSync(recursive: true);
sampleAppAndroid.childFile('gradle.properties')
.writeAsStringSync('org.gradle.jvmargs=-Xmx1536M\n');
migrateToR8(sampleAppAndroid);
expect(testLogger.traceText, contains('set `android.enableR8=true` in gradle.properties'));
expect(sampleAppAndroid.childFile('gradle.properties').readAsStringSync(),
equals(
'org.gradle.jvmargs=-Xmx1536M\n'
'android.enableR8=true\n'
)
);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
});
});
group('gradle build', () {
MockAndroidSdk mockAndroidSdk;
MockAndroidStudio mockAndroidStudio;
......@@ -1136,6 +1219,9 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
final File gradlew = fs.file('path/to/project/.android/gradlew');
gradlew.createSync(recursive: true);
fs.file('path/to/project/.android/gradle.properties')
.writeAsStringSync('irrelevant');
when(mockProcessManager.run(
<String> ['/path/to/project/.android/gradlew', '-v'],
workingDirectory: anyNamed('workingDirectory'),
......@@ -1198,9 +1284,11 @@ Platform fakePlatform(String name) {
return FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name;
}
class MockAndroidStudio extends Mock implements AndroidStudio {}
class MockDirectory extends Mock implements Directory {}
class MockFile extends Mock implements File {}
class MockGradleProject extends Mock implements GradleProject {}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
class MockGradleProject extends Mock implements GradleProject {}
class MockitoAndroidSdk extends Mock implements AndroidSdk {}
class MockAndroidStudio extends Mock implements AndroidStudio {}
......@@ -103,6 +103,10 @@ void main() {
platform.isWindows ? 'gradlew.bat' : 'gradlew',
)..createSync(recursive: true);
project.android.hostAppGradleRoot
.childFile('gradle.properties')
.writeAsStringSync('irrelevant');
final Directory gradleWrapperDir = fs.systemTempDirectory.createTempSync('gradle_wrapper.');
when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(gradleWrapperDir);
......
......@@ -137,7 +137,7 @@ void main() {
tryToDelete(tempDir);
});
testUsingContext('proguard is enabled by default on release mode', () async {
testUsingContext('shrinking is enabled by default on release mode', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
......@@ -151,7 +151,7 @@ void main() {
'-q',
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Pproguard=true',
'-Pshrink=true',
'-Ptarget-platform=android-arm,android-arm64',
'assembleRelease',
],
......@@ -165,17 +165,16 @@ void main() {
GradleUtils: () => GradleUtils(),
ProcessManager: () => mockProcessManager,
},
skip: true,
timeout: allowForCreateFlutterProject);
testUsingContext('proguard is disabled when --no-proguard is passed', () async {
testUsingContext('shrinking is disabled when --no-shrink is passed', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
await expectLater(() async {
await runBuildApkCommand(
projectPath,
arguments: <String>['--no-proguard'],
arguments: <String>['--no-shrink'],
);
}, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'));
......@@ -198,10 +197,9 @@ void main() {
GradleUtils: () => GradleUtils(),
ProcessManager: () => mockProcessManager,
},
skip: true,
timeout: allowForCreateFlutterProject);
testUsingContext('guides the user when proguard fails', () async {
testUsingContext('guides the user when the shrinker fails', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
......@@ -211,22 +209,20 @@ void main() {
'-q',
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Pproguard=true',
'-Pshrink=true',
'-Ptarget-platform=android-arm,android-arm64',
'assembleRelease',
],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) {
const String proguardStdoutWarning =
'Warning: there were 6 unresolved references to program class members.'
'Your input classes appear to be inconsistent.'
'You may need to recompile the code.'
'(http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedprogramclassmember)';
const String r8StdoutWarning =
'Execution failed for task \':app:transformClassesAndResourcesWithR8ForStageInternal\'.'
'> com.android.tools.r8.CompilationFailedException: Compilation failed to complete';
return Future<Process>.value(
createMockProcess(
exitCode: 1,
stdout: proguardStdoutWarning,
stdout: r8StdoutWarning,
)
);
});
......@@ -238,15 +234,15 @@ void main() {
}, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'));
expect(testLogger.statusText,
contains('Proguard may have failed to optimize the Java bytecode.'));
contains('The shrinker may have failed to optimize the Java bytecode.'));
expect(testLogger.statusText,
contains('To disable proguard, pass the `--no-proguard` flag to this command.'));
contains('To disable the shrinker, pass the `--no-shrink` flag to this command.'));
expect(testLogger.statusText,
contains('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard'));
contains('To learn more, see: https://developer.android.com/studio/build/shrink-code'));
verify(mockUsage.sendEvent(
'build-apk',
'proguard-failure',
'r8-failure',
parameters: anyNamed('parameters'),
)).called(1);
},
......@@ -257,7 +253,6 @@ void main() {
ProcessManager: () => mockProcessManager,
Usage: () => mockUsage,
},
skip: true,
timeout: allowForCreateFlutterProject);
});
}
......
......@@ -75,7 +75,7 @@ void main() {
}, timeout: allowForCreateFlutterProject);
});
group('Flags', () {
group('Gradle', () {
Directory tempDir;
ProcessManager mockProcessManager;
MockAndroidSdk mockAndroidSdk;
......@@ -122,7 +122,7 @@ void main() {
tryToDelete(tempDir);
});
testUsingContext('proguard is enabled by default on release mode', () async {
testUsingContext('shrinking is enabled by default on release mode', () async {
final String projectPath = await createProject(
tempDir,
arguments: <String>['--no-pub', '--template=app'],
......@@ -138,7 +138,7 @@ void main() {
'-q',
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Pproguard=true',
'-Pshrink=true',
'-Ptarget-platform=android-arm,android-arm64',
'bundleRelease',
],
......@@ -152,10 +152,9 @@ void main() {
GradleUtils: () => GradleUtils(),
ProcessManager: () => mockProcessManager,
},
skip: true,
timeout: allowForCreateFlutterProject);
testUsingContext('proguard is disabled when --no-proguard is passed', () async {
testUsingContext('shrinking is disabled when --no-shrink is passed', () async {
final String projectPath = await createProject(
tempDir,
arguments: <String>['--no-pub', '--template=app'],
......@@ -164,7 +163,7 @@ void main() {
await expectLater(() async {
await runBuildAppBundleCommand(
projectPath,
arguments: <String>['--no-proguard'],
arguments: <String>['--no-shrink'],
);
}, throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'));
......@@ -187,10 +186,9 @@ void main() {
GradleUtils: () => GradleUtils(),
ProcessManager: () => mockProcessManager,
},
skip: true,
timeout: allowForCreateFlutterProject);
testUsingContext('guides the user when proguard fails', () async {
testUsingContext('guides the user when the shrinker fails', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
......@@ -200,22 +198,20 @@ void main() {
'-q',
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=false',
'-Pproguard=true',
'-Pshrink=true',
'-Ptarget-platform=android-arm,android-arm64',
'bundleRelease',
],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) {
const String proguardStdoutWarning =
'Warning: there were 6 unresolved references to program class members.'
'Your input classes appear to be inconsistent.'
'You may need to recompile the code.'
'(http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedprogramclassmember)';
const String r8StdoutWarning =
'Execution failed for task \':app:transformClassesAndResourcesWithR8ForStageInternal\'.'
'> com.android.tools.r8.CompilationFailedException: Compilation failed to complete';
return Future<Process>.value(
createMockProcess(
exitCode: 1,
stdout: proguardStdoutWarning,
stdout: r8StdoutWarning,
)
);
});
......@@ -227,15 +223,15 @@ void main() {
}, throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'));
expect(testLogger.statusText,
contains('Proguard may have failed to optimize the Java bytecode.'));
contains('The shrinker may have failed to optimize the Java bytecode.'));
expect(testLogger.statusText,
contains('To disable proguard, pass the `--no-proguard` flag to this command.'));
contains('To disable the shrinker, pass the `--no-shrink` flag to this command.'));
expect(testLogger.statusText,
contains('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard'));
contains('To learn more, see: https://developer.android.com/studio/build/shrink-code'));
verify(mockUsage.sendEvent(
'build-appbundle',
'proguard-failure',
'r8-failure',
parameters: anyNamed('parameters'),
)).called(1);
},
......@@ -246,7 +242,6 @@ void main() {
ProcessManager: () => mockProcessManager,
Usage: () => mockUsage,
},
skip: true,
timeout: allowForCreateFlutterProject);
});
}
......
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