Skip to content

Commit

Permalink
Enable Proguard by default on release mode (#39986)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emmanuel Garcia authored Sep 11, 2019
1 parent 362cde4 commit f098de1
Show file tree
Hide file tree
Showing 13 changed files with 586 additions and 122 deletions.
42 changes: 32 additions & 10 deletions packages/flutter_tools/gradle/flutter.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,6 @@ class FlutterPlugin implements Plugin<Project> {
}
}

// Add custom build types
project.android.buildTypes {
profile {
initWith debug
if (it.hasProperty('matchingFallbacks')) {
matchingFallbacks = ['debug', 'release']
}
}
}

String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
if (flutterRootPath == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
Expand All @@ -154,6 +144,30 @@ class FlutterPlugin implements Plugin<Project> {
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();

// Add custom build types.
project.android.buildTypes {
profile {
initWith debug
if (it.hasProperty("matchingFallbacks")) {
matchingFallbacks = ["debug", "release"]
}
}
}

if (useProguard(project)) {
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
"gradle", "flutter_proguard_rules.pro")
project.android.buildTypes {
release {
minifyEnabled true
useProguard 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"
}
}
}

if (useLocalEngine(project)) {
String engineOutPath = project.property('localEngineOut')
File engineOut = project.file(engineOutPath)
Expand Down Expand Up @@ -375,6 +389,14 @@ class FlutterPlugin implements Plugin<Project> {
return false
}


private static Boolean useProguard(Project project) {
if (project.hasProperty('proguard')) {
return project.property('proguard').toBoolean()
}
return false
}

private static Boolean buildPluginAsAar() {
return System.getProperty('build-plugins-as-aars') == 'true'
}
Expand Down
11 changes: 11 additions & 0 deletions packages/flutter_tools/gradle/flutter_proguard_rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Prevents `Fragment and FragmentActivity not found`.
# TODO(blasten): Remove once we bring the Maven dependencies.
-dontwarn io.flutter.embedding.**

# Build the ephemeral app in a module project.
# Prevents: Warning: library class <plugin-package> depends on program class io.flutter.plugin.**
# This is due to plugins (libraries) depending on the embedding (the program jar)
-dontwarn io.flutter.plugin.**

# The android.** package is provided by the OS at runtime.
-dontwarn android.**
110 changes: 68 additions & 42 deletions packages/flutter_tools/lib/src/android/gradle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
import '../android/android_sdk.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/os.dart';
Expand All @@ -28,11 +29,39 @@ import '../reporting/reporting.dart';
import 'android_sdk.dart';
import 'android_studio.dart';

final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
/// Gradle utils in the current [AppContext].
GradleUtils get gradleUtils => context.get<GradleUtils>();

/// Provides utilities to run a Gradle task,
/// such as finding the Gradle executable or constructing a Gradle project.
class GradleUtils {
/// Empty constructor.
GradleUtils();

String _cachedExecutable;
/// Gets the Gradle executable path.
/// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
Future<String> getExecutable(FlutterProject project) async {
_cachedExecutable ??= await _initializeGradle(project);
return _cachedExecutable;
}

GradleProject _cachedAppProject;
/// Gets the [GradleProject] for the current [FlutterProject] if built as an app.
Future<GradleProject> get appProject async {
_cachedAppProject ??= await _readGradleProject(isLibrary: false);
return _cachedAppProject;
}

GradleProject _cachedGradleAppProject;
GradleProject _cachedGradleLibraryProject;
String _cachedGradleExecutable;
GradleProject _cachedLibraryProject;
/// Gets the [GradleProject] for the current [FlutterProject] if built as a library.
Future<GradleProject> get libraryProject async {
_cachedLibraryProject ??= await _readGradleProject(isLibrary: true);
return _cachedLibraryProject;
}
}

final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');

enum FlutterPluginVersion {
none,
Expand Down Expand Up @@ -103,29 +132,20 @@ Future<File> getGradleAppOut(AndroidProject androidProject) async {
case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2:
return fs.file((await _gradleAppProject()).apkDirectory.childFile('app.apk'));
final GradleProject gradleProject = await gradleUtils.appProject;
return fs.file(gradleProject.apkDirectory.childFile('app.apk'));
}
return null;
}

Future<GradleProject> _gradleAppProject() async {
_cachedGradleAppProject ??= await _readGradleProject(isLibrary: false);
return _cachedGradleAppProject;
}

Future<GradleProject> _gradleLibraryProject() async {
_cachedGradleLibraryProject ??= await _readGradleProject(isLibrary: true);
return _cachedGradleLibraryProject;
}

/// Runs `gradlew dependencies`, ensuring that dependencies are resolved and
/// potentially downloaded.
Future<void> checkGradleDependencies() async {
final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation);
final FlutterProject flutterProject = FlutterProject.current();
final String gradle = await _ensureGradle(flutterProject);
final String gradlew = await gradleUtils.getExecutable(flutterProject);
await runCheckedAsync(
<String>[gradle, 'dependencies'],
<String>[gradlew, 'dependencies'],
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: _gradleEnv,
);
Expand Down Expand Up @@ -189,7 +209,8 @@ void createSettingsAarGradle(Directory androidDirectory) {
// of calculating the app properties using Gradle. This may take minutes.
Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
final FlutterProject flutterProject = FlutterProject.current();
final String gradle = await _ensureGradle(flutterProject);
final String gradlew = await gradleUtils.getExecutable(flutterProject);

updateLocalProperties(project: flutterProject);

final FlutterManifest manifest = flutterProject.manifest;
Expand All @@ -213,12 +234,12 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
// flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
try {
final RunResult propertiesRunResult = await runCheckedAsync(
<String>[gradle, isLibrary ? 'properties' : 'app:properties'],
<String>[gradlew, isLibrary ? 'properties' : 'app:properties'],
workingDirectory: hostAppGradleRoot.path,
environment: _gradleEnv,
);
final RunResult tasksRunResult = await runCheckedAsync(
<String>[gradle, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
<String>[gradlew, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
workingDirectory: hostAppGradleRoot.path,
environment: _gradleEnv,
);
Expand Down Expand Up @@ -274,11 +295,6 @@ String _locateGradlewExecutable(Directory directory) {
return null;
}

Future<String> _ensureGradle(FlutterProject project) async {
_cachedGradleExecutable ??= await _initializeGradle(project);
return _cachedGradleExecutable;
}

// Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
// of validating the Gradle executable. This may take several seconds.
Future<String> _initializeGradle(FlutterProject project) async {
Expand Down Expand Up @@ -492,17 +508,15 @@ Future<void> buildGradleProject({
// from the local.properties file.
updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);

final String gradle = await _ensureGradle(project);

switch (getFlutterPluginVersion(project.android)) {
case FlutterPluginVersion.none:
// Fall through. Pretend it's v1, and just go for it.
case FlutterPluginVersion.v1:
return _buildGradleProjectV1(project, gradle);
return _buildGradleProjectV1(project);
case FlutterPluginVersion.managed:
// Fall through. Managed plugin builds the same way as plugin v2.
case FlutterPluginVersion.v2:
return _buildGradleProjectV2(project, gradle, androidBuildInfo, target, isBuildingBundle);
return _buildGradleProjectV2(project, androidBuildInfo, target, isBuildingBundle);
}
}

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

GradleProject gradleProject;
if (manifest.isModule) {
gradleProject = await _gradleAppProject();
gradleProject = await gradleUtils.appProject;
} else if (manifest.isPlugin) {
gradleProject = await _gradleLibraryProject();
gradleProject = await gradleUtils.libraryProject;
} else {
throwToolExit('AARs can only be built for plugin or module projects.');
}
Expand All @@ -538,12 +552,11 @@ Future<void> buildGradleAar({
multilineOutput: true,
);

final String gradle = await _ensureGradle(project);
final String gradlePath = fs.file(gradle).absolute.path;
final String gradlew = await gradleUtils.getExecutable(project);
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
final String initScript = fs.path.join(flutterRoot, 'packages','flutter_tools', 'gradle', 'aar_init_script.gradle');
final List<String> command = <String>[
gradlePath,
gradlew,
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${gradleProject.buildDirectory}',
Expand Down Expand Up @@ -601,7 +614,8 @@ Future<void> buildGradleAar({
printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green);
}

Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async {
Future<void> _buildGradleProjectV1(FlutterProject project) async {
final String gradlew = await gradleUtils.getExecutable(project);
// Run 'gradlew build'.
final Status status = logger.startProgress(
'Running \'gradlew build\'...',
Expand All @@ -610,7 +624,7 @@ Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async
);
final Stopwatch sw = Stopwatch()..start();
final int exitCode = await runCommandAndStreamOutput(
<String>[fs.file(gradle).absolute.path, 'build'],
<String>[fs.file(gradlew).absolute.path, 'build'],
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: _gradleEnv,
Expand Down Expand Up @@ -661,12 +675,12 @@ void printUndefinedTask(GradleProject project, BuildInfo buildInfo) {

Future<void> _buildGradleProjectV2(
FlutterProject flutterProject,
String gradle,
AndroidBuildInfo androidBuildInfo,
String target,
bool isBuildingBundle,
) async {
final GradleProject project = await _gradleAppProject();
final String gradlew = await gradleUtils.getExecutable(flutterProject);
final GradleProject project = await gradleUtils.appProject;
final BuildInfo buildInfo = androidBuildInfo.buildInfo;

String assembleTask;
Expand All @@ -685,8 +699,7 @@ Future<void> _buildGradleProjectV2(
timeout: timeoutConfiguration.slowOperation,
multilineOutput: true,
);
final String gradlePath = fs.file(gradle).absolute.path;
final List<String> command = <String>[gradlePath];
final List<String> command = <String>[gradlew];
if (logger.isVerbose) {
command.add('-Pverbose=true');
} else {
Expand All @@ -712,6 +725,8 @@ Future<void> _buildGradleProjectV2(
command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
if (androidBuildInfo.splitPerAbi)
command.add('-Psplit-per-abi=true');
if (androidBuildInfo.proguard)
command.add('-Pproguard=true');
if (androidBuildInfo.targetArchs.isNotEmpty) {
final String targetPlatforms = androidBuildInfo.targetArchs
.map(getPlatformNameForAndroidArch).join(',');
Expand All @@ -727,6 +742,7 @@ Future<void> _buildGradleProjectV2(
}
command.add(assembleTask);
bool potentialAndroidXFailure = false;
bool potentialProguardFailure = false;
final Stopwatch sw = Stopwatch()..start();
int exitCode = 1;
try {
Expand All @@ -743,13 +759,17 @@ 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;
}
// Always print the full line in verbose mode.
if (logger.isVerbose) {
return line;
} else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) {
return null;
}

return line;
},
);
Expand All @@ -758,7 +778,13 @@ Future<void> _buildGradleProjectV2(
}

if (exitCode != 0) {
if (potentialAndroidXFailure) {
if (potentialProguardFailure) {
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();
} else if (potentialAndroidXFailure) {
printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.');
BuildEvent('android-x-failure').send();
}
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_tools/lib/src/build_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class AndroidBuildInfo {
AndroidArch.arm64_v8a,
],
this.splitPerAbi = false,
this.proguard = false,
});

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

/// Whether to enable Proguard on release mode.
final bool proguard;

/// The target platforms for the build.
final Iterable<AndroidArch> targetArchs;
}
Expand Down
11 changes: 9 additions & 2 deletions packages/flutter_tools/lib/src/commands/build_apk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ class BuildApkCommand extends BuildSubCommand {
argParser
..addFlag('split-per-abi',
negatable: false,
help: 'Whether to split the APKs per ABIs.'
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: true,
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'],
Expand Down Expand Up @@ -79,7 +85,8 @@ class BuildApkCommand extends BuildSubCommand {
final BuildInfo buildInfo = getBuildInfo();
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
splitPerAbi: argResults['split-per-abi'],
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName)
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName),
proguard: argResults['proguard'],
);

if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) {
Expand Down
Loading

0 comments on commit f098de1

Please sign in to comment.