Check and give execute permission to Gradle if needed (#46748)

diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart
index b6c26d8..7aef19c 100644
--- a/packages/flutter_tools/lib/src/android/gradle_utils.dart
+++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart
@@ -46,14 +46,17 @@
     final Directory androidDir = project.android.hostAppGradleRoot;
     // Update the project if needed.
     // TODO(egarciad): https://github.com/flutter/flutter/issues/40460
-    migrateToR8(androidDir);
-    injectGradleWrapperIfNeeded(androidDir);
+    gradleUtils.migrateToR8(androidDir);
+    gradleUtils.injectGradleWrapperIfNeeded(androidDir);
 
     final File gradle = androidDir.childFile(
       platform.isWindows ? 'gradlew.bat' : 'gradlew',
     );
     if (gradle.existsSync()) {
       printTrace('Using gradle from ${gradle.absolute.path}.');
+      // If the Gradle executable doesn't have execute permission,
+      // then attempt to set it.
+      _giveExecutePermissionIfNeeded(gradle);
       return gradle.absolute.path;
     }
     throwToolExit(
@@ -62,71 +65,69 @@
     );
     return null;
   }
-}
 
-/// 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}. '
-      'Please ensure that this file exists or that ${gradleProperties.dirname} can be read.'
-    );
-  }
-  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 {
-    if (propertiesContent.isNotEmpty && !propertiesContent.endsWith('\n')) {
-      // Add a new line if the file doesn't end with a new line.
-      gradleProperties.writeAsStringSync('\n', mode: FileMode.append);
+  /// 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}. '
+        'Please ensure that this file exists or that ${gradleProperties.dirname} can be read.'
+      );
     }
-    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(
-    cache.getArtifactDirectory('gradle_wrapper'),
-    directory,
-    shouldCopyFile: (File sourceFile, File destinationFile) {
-      // Don't override the existing files in the project.
-      return !destinationFile.existsSync();
-    },
-    onFileCopied: (File sourceFile, File destinationFile) {
-      final String modes = sourceFile.statSync().modeString();
-      if (modes != null && modes.contains('x')) {
-        os.makeExecutable(destinationFile);
+    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 {
+      if (propertiesContent.isNotEmpty && !propertiesContent.endsWith('\n')) {
+        // Add a new line if the file doesn't end with a new line.
+        gradleProperties.writeAsStringSync('\n', mode: FileMode.append);
       }
-    },
-  );
-  // Add the `gradle-wrapper.properties` file if it doesn't exist.
-  final File propertiesFile = directory.childFile(
-      fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties'));
-  if (!propertiesFile.existsSync()) {
-    final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
-    propertiesFile.writeAsStringSync('''
+      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(
+      cache.getArtifactDirectory('gradle_wrapper'),
+      directory,
+      shouldCopyFile: (File sourceFile, File destinationFile) {
+        // Don't override the existing files in the project.
+        return !destinationFile.existsSync();
+      },
+      onFileCopied: (File sourceFile, File destinationFile) {
+        if (_hasExecutePermission(sourceFile)) {
+          _giveExecutePermissionIfNeeded(destinationFile);
+        }
+      },
+    );
+    // Add the `gradle-wrapper.properties` file if it doesn't exist.
+    final File propertiesFile = directory.childFile(
+        fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties'));
+    if (!propertiesFile.existsSync()) {
+      final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
+      propertiesFile.writeAsStringSync('''
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersion-all.zip
 ''', flush: true,
-    );
+      );
+    }
   }
 }
-
 const String _defaultGradleVersion = '5.6.2';
 
 final RegExp _androidPluginRegExp = RegExp('com\.android\.tools\.build\:gradle\:(\\d+\.\\d+\.\\d+\)');
@@ -150,6 +151,24 @@
   return getGradleVersionFor(androidPluginVersion);
 }
 
+const int _kExecPermissionMask = 0x49; // a+x
+
+/// Returns [true] if [executable] has execute permission.
+bool _hasExecutePermission(File executable) {
+  final FileStat stat = executable.statSync();
+  assert(stat.type != FileSystemEntityType.notFound);
+  printTrace('${executable.path} mode: ${stat.mode} ${stat.modeString()}.');
+  return stat.mode & _kExecPermissionMask == _kExecPermissionMask;
+}
+
+/// Gives execute permission to [executable] if it doesn't have it already.
+void _giveExecutePermissionIfNeeded(File executable) {
+  if (!_hasExecutePermission(executable)) {
+    printTrace('Trying to give execute permission to ${executable.path}.');
+    os.makeExecutable(executable);
+  }
+}
+
 /// Returns true if [targetVersion] is within the range [min] and [max] inclusive.
 bool _isWithinVersionRange(
   String targetVersion, {
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 3fb0dc1..db0dd78 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -904,16 +904,13 @@
   Future<void> update() async {
     final Directory tempDir =
         fs.systemTempDirectory.createTempSync('flutter_gradle_wrapper.');
-    injectGradleWrapperIfNeeded(tempDir);
+    gradleUtils.injectGradleWrapperIfNeeded(tempDir);
 
     final Status status = logger.startProgress('Downloading Android Maven dependencies...',
         timeout: timeoutConfiguration.slowOperation);
     final File gradle = tempDir.childFile(
         platform.isWindows ? 'gradlew.bat' : 'gradlew',
       );
-    assert(gradle.existsSync());
-    os.makeExecutable(gradle);
-
     try {
       final String gradleExecutable = gradle.absolute.path;
       final String flutterSdk = escapePath(Cache.flutterRoot);
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 833bc6e..bc9dba0 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -630,7 +630,7 @@
     _overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
     _overwriteFromTemplate(fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
     _overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
-    gradle.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
+    gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
     gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
     await injectPlugins(parent);
   }
@@ -647,7 +647,7 @@
       featureFlags.isAndroidEmbeddingV2Enabled ? 'library_new_embedding' : 'library',
     ), ephemeralDirectory);
     _overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
-    gradle.injectGradleWrapperIfNeeded(ephemeralDirectory);
+    gradle.gradleUtils.injectGradleWrapperIfNeeded(ephemeralDirectory);
   }
 
   void _overwriteFromTemplate(String path, Directory target) {
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 3f161bf..8317c06 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart
@@ -814,107 +814,6 @@
     });
   });
 
-  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,
-      ProcessManager: () => FakeProcessManager.any(),
-    });
-
-    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,
-      ProcessManager: () => FakeProcessManager.any(),
-    });
-
-    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,
-      ProcessManager: () => FakeProcessManager.any(),
-    });
-
-    testUsingContext('appends android.enableR8=true to the new line', () {
-      final Directory sampleAppAndroid = fs.directory('/sample-app/android');
-      sampleAppAndroid.createSync(recursive: true);
-      sampleAppAndroid.childFile('gradle.properties')
-        .writeAsStringSync('org.gradle.jvmargs=-Xmx1536M');
-
-      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,
-      ProcessManager: () => FakeProcessManager.any()
-    });
-  });
-
   group('isAppUsingAndroidX', () {
     FileSystem fs;
 
diff --git a/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart
index e53fe29..b5cdc97 100644
--- a/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart
@@ -6,7 +6,10 @@
 import 'package:flutter_tools/src/android/gradle_utils.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/os.dart';
+import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:mockito/mockito.dart';
 import 'package:process/process.dart';
 
 import '../../src/common.dart';
@@ -38,11 +41,11 @@
         .writeAsStringSync('irrelevant');
     });
 
-    testUsingContext('Inject the wrapper when all files are missing', () {
+    testUsingContext('injects the wrapper when all files are missing', () {
       final Directory sampleAppAndroid = fs.directory('/sample-app/android');
       sampleAppAndroid.createSync(recursive: true);
 
-      injectGradleWrapperIfNeeded(sampleAppAndroid);
+      gradleUtils.injectGradleWrapperIfNeeded(sampleAppAndroid);
 
       expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
 
@@ -74,14 +77,14 @@
       ProcessManager: () => FakeProcessManager.any(),
     });
 
-    testUsingContext('Inject the wrapper when some files are missing', () {
+    testUsingContext('injects the wrapper when some files are missing', () {
       final Directory sampleAppAndroid = fs.directory('/sample-app/android');
       sampleAppAndroid.createSync(recursive: true);
 
       // There's an existing gradlew
       sampleAppAndroid.childFile('gradlew').writeAsStringSync('existing gradlew');
 
-      injectGradleWrapperIfNeeded(sampleAppAndroid);
+      gradleUtils.injectGradleWrapperIfNeeded(sampleAppAndroid);
 
       expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
       expect(sampleAppAndroid.childFile('gradlew').readAsStringSync(),
@@ -114,24 +117,211 @@
       FileSystem: () => memoryFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
     });
+  });
 
-    testUsingContext('Gives executable permission to gradle', () {
+
+  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);
 
-      // Make gradlew in the wrapper executable.
-      os.makeExecutable(gradleWrapperDirectory.childFile('gradlew'));
+      expect(() {
+        gradleUtils.migrateToR8(sampleAppAndroid);
+      }, throwsToolExit(message: 'Expected file ${sampleAppAndroid.path}'));
 
-      injectGradleWrapperIfNeeded(sampleAppAndroid);
-
-      final File gradlew = sampleAppAndroid.childFile('gradlew');
-      expect(gradlew.existsSync(), isTrue);
-      expect(gradlew.statSync().modeString().contains('x'), isTrue);
     }, overrides: <Type, Generator>{
-      Cache: () => Cache(rootOverride: tempDir),
       FileSystem: () => memoryFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
-      OperatingSystemUtils: () => OperatingSystemUtils(),
+    });
+
+    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(() {
+        gradleUtils.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');
+
+      gradleUtils.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,
+      ProcessManager: () => FakeProcessManager.any(),
+    });
+
+    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');
+
+      gradleUtils.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,
+      ProcessManager: () => FakeProcessManager.any(),
+    });
+
+    testUsingContext('appends android.enableR8=true to the new line', () {
+      final Directory sampleAppAndroid = fs.directory('/sample-app/android');
+      sampleAppAndroid.createSync(recursive: true);
+      sampleAppAndroid.childFile('gradle.properties')
+        .writeAsStringSync('org.gradle.jvmargs=-Xmx1536M');
+
+      gradleUtils.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,
+      ProcessManager: () => FakeProcessManager.any()
     });
   });
-}
\ No newline at end of file
+
+  group('GradleUtils.getExecutable', () {
+    final String gradlewFilename = platform.isWindows ? 'gradlew.bat' : 'gradlew';
+
+    MemoryFileSystem memoryFileSystem;
+    OperatingSystemUtils operatingSystemUtils;
+    MockGradleUtils gradleUtils;
+
+    setUp(() {
+      memoryFileSystem = MemoryFileSystem();
+      operatingSystemUtils = MockOperatingSystemUtils();
+      gradleUtils = MockGradleUtils();
+    });
+
+    testUsingContext('returns the gradlew path', () {
+      final Directory androidDirectory = fs.directory('/android')..createSync();
+      androidDirectory.childFile('gradlew')..createSync();
+      androidDirectory.childFile('gradlew.bat')..createSync();
+      androidDirectory.childFile('gradle.properties').createSync();
+
+      when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null);
+      when(gradleUtils.migrateToR8(any)).thenReturn(null);
+
+      expect(
+        GradleUtils().getExecutable(FlutterProject.current()),
+        androidDirectory.childFile(gradlewFilename).path,
+      );
+    }, overrides: <Type, Generator>{
+      FileSystem: () => memoryFileSystem,
+      ProcessManager: () => FakeProcessManager.any(),
+      OperatingSystemUtils: () => operatingSystemUtils,
+      GradleUtils: () => gradleUtils,
+    });
+
+    testUsingContext('gives execute permission to gradle', () {
+      final FlutterProject flutterProject = MockFlutterProject();
+      final AndroidProject androidProject = MockAndroidProject();
+      when(flutterProject.android).thenReturn(androidProject);
+
+      final FileStat gradleStat = MockFileStat();
+      when(gradleStat.mode).thenReturn(444);
+
+      final File gradlew = MockFile();
+      when(gradlew.path).thenReturn('gradlew');
+      when(gradlew.absolute).thenReturn(gradlew);
+      when(gradlew.statSync()).thenReturn(gradleStat);
+      when(gradlew.existsSync()).thenReturn(true);
+
+      final Directory androidDirectory = MockDirectory();
+      when(androidDirectory.childFile(gradlewFilename)).thenReturn(gradlew);
+      when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory);
+
+      when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null);
+      when(gradleUtils.migrateToR8(any)).thenReturn(null);
+
+      GradleUtils().getExecutable(flutterProject);
+
+      verify(operatingSystemUtils.makeExecutable(gradlew)).called(1);
+    }, overrides: <Type, Generator>{
+      FileSystem: () => memoryFileSystem,
+      ProcessManager: () => FakeProcessManager.any(),
+      OperatingSystemUtils: () => operatingSystemUtils,
+      GradleUtils: () => gradleUtils,
+    });
+
+    testUsingContext('doesn\'t give execute permission to gradle if not needed', () {
+      final FlutterProject flutterProject = MockFlutterProject();
+      final AndroidProject androidProject = MockAndroidProject();
+      when(flutterProject.android).thenReturn(androidProject);
+
+      final FileStat gradleStat = MockFileStat();
+      when(gradleStat.mode).thenReturn(0x49 /* a+x */);
+
+      final File gradlew = MockFile();
+      when(gradlew.path).thenReturn('gradlew');
+      when(gradlew.absolute).thenReturn(gradlew);
+      when(gradlew.statSync()).thenReturn(gradleStat);
+      when(gradlew.existsSync()).thenReturn(true);
+
+      final Directory androidDirectory = MockDirectory();
+      when(androidDirectory.childFile(gradlewFilename)).thenReturn(gradlew);
+      when(androidProject.hostAppGradleRoot).thenReturn(androidDirectory);
+
+      when(gradleUtils.injectGradleWrapperIfNeeded(any)).thenReturn(null);
+      when(gradleUtils.migrateToR8(any)).thenReturn(null);
+
+      GradleUtils().getExecutable(flutterProject);
+
+      verifyNever(operatingSystemUtils.makeExecutable(gradlew));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => memoryFileSystem,
+      ProcessManager: () => FakeProcessManager.any(),
+      OperatingSystemUtils: () => operatingSystemUtils,
+      GradleUtils: () => gradleUtils,
+    });
+  });
+}
+
+class MockAndroidProject extends Mock implements AndroidProject {}
+class MockDirectory extends Mock implements Directory {}
+class MockFile extends Mock implements File {}
+class MockFileStat extends Mock implements FileStat {}
+class MockFlutterProject extends Mock implements FlutterProject {}
+class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
+class MockGradleUtils extends Mock implements GradleUtils {}