Skip to content

Commit f098de1

Browse files
author
Emmanuel Garcia
authored
Enable Proguard by default on release mode (#39986)
1 parent 362cde4 commit f098de1

File tree

13 files changed

+586
-122
lines changed

13 files changed

+586
-122
lines changed

packages/flutter_tools/gradle/flutter.gradle

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,6 @@ class FlutterPlugin implements Plugin<Project> {
132132
}
133133
}
134134

135-
// Add custom build types
136-
project.android.buildTypes {
137-
profile {
138-
initWith debug
139-
if (it.hasProperty('matchingFallbacks')) {
140-
matchingFallbacks = ['debug', 'release']
141-
}
142-
}
143-
}
144-
145135
String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
146136
if (flutterRootPath == null) {
147137
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
@@ -154,6 +144,30 @@ class FlutterPlugin implements Plugin<Project> {
154144
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
155145
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
156146

147+
// Add custom build types.
148+
project.android.buildTypes {
149+
profile {
150+
initWith debug
151+
if (it.hasProperty("matchingFallbacks")) {
152+
matchingFallbacks = ["debug", "release"]
153+
}
154+
}
155+
}
156+
157+
if (useProguard(project)) {
158+
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
159+
"gradle", "flutter_proguard_rules.pro")
160+
project.android.buildTypes {
161+
release {
162+
minifyEnabled true
163+
useProguard true
164+
// Fallback to `android/app/proguard-rules.pro`.
165+
// This way, custom Proguard rules can be configured as needed.
166+
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
167+
}
168+
}
169+
}
170+
157171
if (useLocalEngine(project)) {
158172
String engineOutPath = project.property('localEngineOut')
159173
File engineOut = project.file(engineOutPath)
@@ -375,6 +389,14 @@ class FlutterPlugin implements Plugin<Project> {
375389
return false
376390
}
377391

392+
393+
private static Boolean useProguard(Project project) {
394+
if (project.hasProperty('proguard')) {
395+
return project.property('proguard').toBoolean()
396+
}
397+
return false
398+
}
399+
378400
private static Boolean buildPluginAsAar() {
379401
return System.getProperty('build-plugins-as-aars') == 'true'
380402
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Prevents `Fragment and FragmentActivity not found`.
2+
# TODO(blasten): Remove once we bring the Maven dependencies.
3+
-dontwarn io.flutter.embedding.**
4+
5+
# Build the ephemeral app in a module project.
6+
# Prevents: Warning: library class <plugin-package> depends on program class io.flutter.plugin.**
7+
# This is due to plugins (libraries) depending on the embedding (the program jar)
8+
-dontwarn io.flutter.plugin.**
9+
10+
# The android.** package is provided by the OS at runtime.
11+
-dontwarn android.**

packages/flutter_tools/lib/src/android/gradle.dart

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
1010
import '../android/android_sdk.dart';
1111
import '../artifacts.dart';
1212
import '../base/common.dart';
13+
import '../base/context.dart';
1314
import '../base/file_system.dart';
1415
import '../base/logger.dart';
1516
import '../base/os.dart';
@@ -28,11 +29,39 @@ import '../reporting/reporting.dart';
2829
import 'android_sdk.dart';
2930
import 'android_studio.dart';
3031

31-
final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
32+
/// Gradle utils in the current [AppContext].
33+
GradleUtils get gradleUtils => context.get<GradleUtils>();
34+
35+
/// Provides utilities to run a Gradle task,
36+
/// such as finding the Gradle executable or constructing a Gradle project.
37+
class GradleUtils {
38+
/// Empty constructor.
39+
GradleUtils();
40+
41+
String _cachedExecutable;
42+
/// Gets the Gradle executable path.
43+
/// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
44+
Future<String> getExecutable(FlutterProject project) async {
45+
_cachedExecutable ??= await _initializeGradle(project);
46+
return _cachedExecutable;
47+
}
48+
49+
GradleProject _cachedAppProject;
50+
/// Gets the [GradleProject] for the current [FlutterProject] if built as an app.
51+
Future<GradleProject> get appProject async {
52+
_cachedAppProject ??= await _readGradleProject(isLibrary: false);
53+
return _cachedAppProject;
54+
}
3255

33-
GradleProject _cachedGradleAppProject;
34-
GradleProject _cachedGradleLibraryProject;
35-
String _cachedGradleExecutable;
56+
GradleProject _cachedLibraryProject;
57+
/// Gets the [GradleProject] for the current [FlutterProject] if built as a library.
58+
Future<GradleProject> get libraryProject async {
59+
_cachedLibraryProject ??= await _readGradleProject(isLibrary: true);
60+
return _cachedLibraryProject;
61+
}
62+
}
63+
64+
final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
3665

3766
enum FlutterPluginVersion {
3867
none,
@@ -103,29 +132,20 @@ Future<File> getGradleAppOut(AndroidProject androidProject) async {
103132
case FlutterPluginVersion.managed:
104133
// Fall through. The managed plugin matches plugin v2 for now.
105134
case FlutterPluginVersion.v2:
106-
return fs.file((await _gradleAppProject()).apkDirectory.childFile('app.apk'));
135+
final GradleProject gradleProject = await gradleUtils.appProject;
136+
return fs.file(gradleProject.apkDirectory.childFile('app.apk'));
107137
}
108138
return null;
109139
}
110140

111-
Future<GradleProject> _gradleAppProject() async {
112-
_cachedGradleAppProject ??= await _readGradleProject(isLibrary: false);
113-
return _cachedGradleAppProject;
114-
}
115-
116-
Future<GradleProject> _gradleLibraryProject() async {
117-
_cachedGradleLibraryProject ??= await _readGradleProject(isLibrary: true);
118-
return _cachedGradleLibraryProject;
119-
}
120-
121141
/// Runs `gradlew dependencies`, ensuring that dependencies are resolved and
122142
/// potentially downloaded.
123143
Future<void> checkGradleDependencies() async {
124144
final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation);
125145
final FlutterProject flutterProject = FlutterProject.current();
126-
final String gradle = await _ensureGradle(flutterProject);
146+
final String gradlew = await gradleUtils.getExecutable(flutterProject);
127147
await runCheckedAsync(
128-
<String>[gradle, 'dependencies'],
148+
<String>[gradlew, 'dependencies'],
129149
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
130150
environment: _gradleEnv,
131151
);
@@ -189,7 +209,8 @@ void createSettingsAarGradle(Directory androidDirectory) {
189209
// of calculating the app properties using Gradle. This may take minutes.
190210
Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
191211
final FlutterProject flutterProject = FlutterProject.current();
192-
final String gradle = await _ensureGradle(flutterProject);
212+
final String gradlew = await gradleUtils.getExecutable(flutterProject);
213+
193214
updateLocalProperties(project: flutterProject);
194215

195216
final FlutterManifest manifest = flutterProject.manifest;
@@ -213,12 +234,12 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
213234
// flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
214235
try {
215236
final RunResult propertiesRunResult = await runCheckedAsync(
216-
<String>[gradle, isLibrary ? 'properties' : 'app:properties'],
237+
<String>[gradlew, isLibrary ? 'properties' : 'app:properties'],
217238
workingDirectory: hostAppGradleRoot.path,
218239
environment: _gradleEnv,
219240
);
220241
final RunResult tasksRunResult = await runCheckedAsync(
221-
<String>[gradle, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
242+
<String>[gradlew, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
222243
workingDirectory: hostAppGradleRoot.path,
223244
environment: _gradleEnv,
224245
);
@@ -274,11 +295,6 @@ String _locateGradlewExecutable(Directory directory) {
274295
return null;
275296
}
276297

277-
Future<String> _ensureGradle(FlutterProject project) async {
278-
_cachedGradleExecutable ??= await _initializeGradle(project);
279-
return _cachedGradleExecutable;
280-
}
281-
282298
// Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
283299
// of validating the Gradle executable. This may take several seconds.
284300
Future<String> _initializeGradle(FlutterProject project) async {
@@ -492,17 +508,15 @@ Future<void> buildGradleProject({
492508
// from the local.properties file.
493509
updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
494510

495-
final String gradle = await _ensureGradle(project);
496-
497511
switch (getFlutterPluginVersion(project.android)) {
498512
case FlutterPluginVersion.none:
499513
// Fall through. Pretend it's v1, and just go for it.
500514
case FlutterPluginVersion.v1:
501-
return _buildGradleProjectV1(project, gradle);
515+
return _buildGradleProjectV1(project);
502516
case FlutterPluginVersion.managed:
503517
// Fall through. Managed plugin builds the same way as plugin v2.
504518
case FlutterPluginVersion.v2:
505-
return _buildGradleProjectV2(project, gradle, androidBuildInfo, target, isBuildingBundle);
519+
return _buildGradleProjectV2(project, androidBuildInfo, target, isBuildingBundle);
506520
}
507521
}
508522

@@ -516,9 +530,9 @@ Future<void> buildGradleAar({
516530

517531
GradleProject gradleProject;
518532
if (manifest.isModule) {
519-
gradleProject = await _gradleAppProject();
533+
gradleProject = await gradleUtils.appProject;
520534
} else if (manifest.isPlugin) {
521-
gradleProject = await _gradleLibraryProject();
535+
gradleProject = await gradleUtils.libraryProject;
522536
} else {
523537
throwToolExit('AARs can only be built for plugin or module projects.');
524538
}
@@ -538,12 +552,11 @@ Future<void> buildGradleAar({
538552
multilineOutput: true,
539553
);
540554

541-
final String gradle = await _ensureGradle(project);
542-
final String gradlePath = fs.file(gradle).absolute.path;
555+
final String gradlew = await gradleUtils.getExecutable(project);
543556
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
544557
final String initScript = fs.path.join(flutterRoot, 'packages','flutter_tools', 'gradle', 'aar_init_script.gradle');
545558
final List<String> command = <String>[
546-
gradlePath,
559+
gradlew,
547560
'-I=$initScript',
548561
'-Pflutter-root=$flutterRoot',
549562
'-Poutput-dir=${gradleProject.buildDirectory}',
@@ -601,7 +614,8 @@ Future<void> buildGradleAar({
601614
printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green);
602615
}
603616

604-
Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async {
617+
Future<void> _buildGradleProjectV1(FlutterProject project) async {
618+
final String gradlew = await gradleUtils.getExecutable(project);
605619
// Run 'gradlew build'.
606620
final Status status = logger.startProgress(
607621
'Running \'gradlew build\'...',
@@ -610,7 +624,7 @@ Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async
610624
);
611625
final Stopwatch sw = Stopwatch()..start();
612626
final int exitCode = await runCommandAndStreamOutput(
613-
<String>[fs.file(gradle).absolute.path, 'build'],
627+
<String>[fs.file(gradlew).absolute.path, 'build'],
614628
workingDirectory: project.android.hostAppGradleRoot.path,
615629
allowReentrantFlutter: true,
616630
environment: _gradleEnv,
@@ -661,12 +675,12 @@ void printUndefinedTask(GradleProject project, BuildInfo buildInfo) {
661675

662676
Future<void> _buildGradleProjectV2(
663677
FlutterProject flutterProject,
664-
String gradle,
665678
AndroidBuildInfo androidBuildInfo,
666679
String target,
667680
bool isBuildingBundle,
668681
) async {
669-
final GradleProject project = await _gradleAppProject();
682+
final String gradlew = await gradleUtils.getExecutable(flutterProject);
683+
final GradleProject project = await gradleUtils.appProject;
670684
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
671685

672686
String assembleTask;
@@ -685,8 +699,7 @@ Future<void> _buildGradleProjectV2(
685699
timeout: timeoutConfiguration.slowOperation,
686700
multilineOutput: true,
687701
);
688-
final String gradlePath = fs.file(gradle).absolute.path;
689-
final List<String> command = <String>[gradlePath];
702+
final List<String> command = <String>[gradlew];
690703
if (logger.isVerbose) {
691704
command.add('-Pverbose=true');
692705
} else {
@@ -712,6 +725,8 @@ Future<void> _buildGradleProjectV2(
712725
command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
713726
if (androidBuildInfo.splitPerAbi)
714727
command.add('-Psplit-per-abi=true');
728+
if (androidBuildInfo.proguard)
729+
command.add('-Pproguard=true');
715730
if (androidBuildInfo.targetArchs.isNotEmpty) {
716731
final String targetPlatforms = androidBuildInfo.targetArchs
717732
.map(getPlatformNameForAndroidArch).join(',');
@@ -727,6 +742,7 @@ Future<void> _buildGradleProjectV2(
727742
}
728743
command.add(assembleTask);
729744
bool potentialAndroidXFailure = false;
745+
bool potentialProguardFailure = false;
730746
final Stopwatch sw = Stopwatch()..start();
731747
int exitCode = 1;
732748
try {
@@ -743,13 +759,17 @@ Future<void> _buildGradleProjectV2(
743759
if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) {
744760
potentialAndroidXFailure = true;
745761
}
762+
// Proguard errors include this url.
763+
if (!potentialProguardFailure && androidBuildInfo.proguard &&
764+
line.contains('http://proguard.sourceforge.net')) {
765+
potentialProguardFailure = true;
766+
}
746767
// Always print the full line in verbose mode.
747768
if (logger.isVerbose) {
748769
return line;
749770
} else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) {
750771
return null;
751772
}
752-
753773
return line;
754774
},
755775
);
@@ -758,7 +778,13 @@ Future<void> _buildGradleProjectV2(
758778
}
759779

760780
if (exitCode != 0) {
761-
if (potentialAndroidXFailure) {
781+
if (potentialProguardFailure) {
782+
final String exclamationMark = terminal.color('[!]', TerminalColor.red);
783+
printStatus('$exclamationMark Proguard may have failed to optimize the Java bytecode.', emphasis: true);
784+
printStatus('To disable proguard, pass the `--no-proguard` flag to this command.', indent: 4);
785+
printStatus('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard', indent: 4);
786+
BuildEvent('proguard-failure').send();
787+
} else if (potentialAndroidXFailure) {
762788
printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.');
763789
BuildEvent('android-x-failure').send();
764790
}

packages/flutter_tools/lib/src/build_info.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class AndroidBuildInfo {
9292
AndroidArch.arm64_v8a,
9393
],
9494
this.splitPerAbi = false,
95+
this.proguard = false,
9596
});
9697

9798
// The build info containing the mode and flavor.
@@ -104,6 +105,9 @@ class AndroidBuildInfo {
104105
/// will be produced.
105106
final bool splitPerAbi;
106107

108+
/// Whether to enable Proguard on release mode.
109+
final bool proguard;
110+
107111
/// The target platforms for the build.
108112
final Iterable<AndroidArch> targetArchs;
109113
}

packages/flutter_tools/lib/src/commands/build_apk.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ class BuildApkCommand extends BuildSubCommand {
2525
argParser
2626
..addFlag('split-per-abi',
2727
negatable: false,
28-
help: 'Whether to split the APKs per ABIs.'
28+
help: 'Whether to split the APKs per ABIs. '
2929
'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',
3030
)
31+
..addFlag('proguard',
32+
negatable: true,
33+
defaultsTo: true,
34+
help: 'Whether to enable Proguard on release mode. '
35+
'To learn more, see: https://flutter.dev/docs/deployment/android#enabling-proguard',
36+
)
3137
..addMultiOption('target-platform',
3238
splitCommas: true,
3339
defaultsTo: <String>['android-arm', 'android-arm64'],
@@ -79,7 +85,8 @@ class BuildApkCommand extends BuildSubCommand {
7985
final BuildInfo buildInfo = getBuildInfo();
8086
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
8187
splitPerAbi: argResults['split-per-abi'],
82-
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName)
88+
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName),
89+
proguard: argResults['proguard'],
8390
);
8491

8592
if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) {

0 commit comments

Comments
 (0)