Enable R8 (#40453)

diff --git a/packages/flutter_tools/test/general.shard/android/gradle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
index e583a46..b650ab0 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
@@ -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/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 @@
       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 @@
     });
   });
 
+  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 @@
       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 @@
   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 {}
diff --git a/packages/flutter_tools/test/general.shard/application_package_test.dart b/packages/flutter_tools/test/general.shard/application_package_test.dart
index a374142..05855b0 100644
--- a/packages/flutter_tools/test/general.shard/application_package_test.dart
+++ b/packages/flutter_tools/test/general.shard/application_package_test.dart
@@ -103,6 +103,10 @@
         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);
 
diff --git a/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart b/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart
index f3d19b8..1c961ad 100644
--- a/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart
@@ -137,7 +137,7 @@
       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 @@
           '-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 @@
       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 @@
       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 @@
           '-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 @@
       }, 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 @@
       ProcessManager: () => mockProcessManager,
       Usage: () => mockUsage,
     },
-    skip: true,
     timeout: allowForCreateFlutterProject);
   });
 }
diff --git a/packages/flutter_tools/test/general.shard/commands/build_appbundle_test.dart b/packages/flutter_tools/test/general.shard/commands/build_appbundle_test.dart
index 4548e3c..4a7d98f 100644
--- a/packages/flutter_tools/test/general.shard/commands/build_appbundle_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/build_appbundle_test.dart
@@ -75,7 +75,7 @@
     }, timeout: allowForCreateFlutterProject);
   });
 
-  group('Flags', () {
+  group('Gradle', () {
     Directory tempDir;
     ProcessManager mockProcessManager;
     MockAndroidSdk mockAndroidSdk;
@@ -122,7 +122,7 @@
       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 @@
           '-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 @@
       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 @@
       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 @@
       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 @@
           '-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 @@
       }, 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 @@
       ProcessManager: () => mockProcessManager,
       Usage: () => mockUsage,
     },
-    skip: true,
     timeout: allowForCreateFlutterProject);
   });
 }