Fix Flutter Android builds with Gradle 8.9+ (#412907)

authored by

Maciej Krüger and committed by
GitHub
526945c5 6369abb4

+342 -12
+171 -6
pkgs/development/compilers/flutter/versions/3_29/patches/gradle-flutter-tools-wrapper.patch
··· 15 15 This Gradle project will build the actual `packages/flutter_tools/gradle` project by setting 16 16 `rootProject.projectDir = new File("$settingsDir")` and `apply from: new File("$settingsDir/settings.gradle.kts")`. 17 17 18 - Now the `.gradle` will be built in `$HOME/.cache/flutter/nix-flutter-tools-gradle/<short engine rev>/`, but `build` doesn't. 19 - To move `build` to `$HOME/.cache/flutter/nix-flutter-tools-gradle/<short engine rev>/` as well, we need to set `buildDirectory`. 20 - diff --git a/packages/flutter_tools/gradle/settings.gradle b/packages/flutter_tools/gradle/settings.gradle 21 - new file mode 100644 22 - index 0000000000..b2485c94b4 18 + To move `build` to `$HOME/.cache/flutter/nix-flutter-tools-gradle/<short engine rev>/`, we need to set `buildDirectory`. 19 + To move `.gradle` as well, the `--project-cache-dir` argument must be passed to the Gradle wrapper. 20 + Changing the `GradleUtils.getExecutable` function signature is a delibarate choice, to ensure that no new unpatched usages slip in. 23 21 --- /dev/null 24 22 +++ b/packages/flutter_tools/gradle/settings.gradle 25 23 @@ -0,0 +1,19 @@ ··· 44 42 +includeBuild(dir) 45 43 --- a/packages/flutter_tools/gradle/build.gradle.kts 46 44 +++ b/packages/flutter_tools/gradle/build.gradle.kts 47 - @@ -4,6 +4,8 @@ 45 + @@ -4,6 +4,11 @@ 48 46 49 47 import org.jetbrains.kotlin.gradle.dsl.JvmTarget 50 48 49 + +// While flutter_tools runs Gradle with a --project-cache-dir, this startParameter 50 + +// is not passed correctly to the Kotlin Gradle plugin for some reason, and so 51 + +// must be set here as well. 51 52 +gradle.startParameter.projectCacheDir = layout.buildDirectory.dir("cache").get().asFile 52 53 + 53 54 plugins { 54 55 `java-gradle-plugin` 55 56 groovy 57 + --- a/packages/flutter_tools/lib/src/android/gradle.dart 58 + +++ b/packages/flutter_tools/lib/src/android/gradle.dart 59 + @@ -456,9 +456,9 @@ class AndroidGradleBuilder implements AndroidBuilder { 60 + // from the local.properties file. 61 + updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); 62 + 63 + - final List<String> options = <String>[]; 64 + - 65 + - final String gradleExecutablePath = _gradleUtils.getExecutable(project); 66 + + final [String gradleExecutablePath, ...List<String> options] = _gradleUtils.getExecutable( 67 + + project, 68 + + ); 69 + 70 + // All automatically created files should exist. 71 + if (configOnly) { 72 + @@ -781,7 +781,7 @@ class AndroidGradleBuilder implements AndroidBuilder { 73 + 'aar_init_script.gradle', 74 + ); 75 + final List<String> command = <String>[ 76 + - _gradleUtils.getExecutable(project), 77 + + ..._gradleUtils.getExecutable(project), 78 + '-I=$initScript', 79 + '-Pflutter-root=$flutterRoot', 80 + '-Poutput-dir=${outputDirectory.path}', 81 + @@ -896,6 +896,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 82 + final List<String> results = <String>[]; 83 + 84 + try { 85 + + final [String gradleExecutablePath, ...List<String> options] = _gradleUtils.getExecutable( 86 + + project, 87 + + ); 88 + + 89 + exitCode = await _runGradleTask( 90 + _kBuildVariantTaskName, 91 + preRunTask: () { 92 + @@ -911,10 +915,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 93 + ), 94 + ); 95 + }, 96 + - options: const <String>['-q'], 97 + + options: <String>[...options, '-q'], 98 + project: project, 99 + localGradleErrors: gradleErrors, 100 + - gradleExecutablePath: _gradleUtils.getExecutable(project), 101 + + gradleExecutablePath: gradleExecutablePath, 102 + outputParser: (String line) { 103 + if (_kBuildVariantRegex.firstMatch(line) case final RegExpMatch match) { 104 + results.add(match.namedGroup(_kBuildVariantRegexGroupName)!); 105 + @@ -948,6 +952,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 106 + late Stopwatch sw; 107 + int exitCode = 1; 108 + try { 109 + + final [String gradleExecutablePath, ...List<String> options] = _gradleUtils.getExecutable( 110 + + project, 111 + + ); 112 + + 113 + exitCode = await _runGradleTask( 114 + taskName, 115 + preRunTask: () { 116 + @@ -963,10 +971,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 117 + ), 118 + ); 119 + }, 120 + - options: <String>['-q', '-PoutputPath=$outputPath'], 121 + + options: <String>[...options, '-q', '-PoutputPath=$outputPath'], 122 + project: project, 123 + localGradleErrors: gradleErrors, 124 + - gradleExecutablePath: _gradleUtils.getExecutable(project), 125 + + gradleExecutablePath: gradleExecutablePath, 126 + ); 127 + } on Error catch (error) { 128 + _logger.printError(error.toString()); 129 + --- a/packages/flutter_tools/lib/src/android/gradle_errors.dart 130 + +++ b/packages/flutter_tools/lib/src/android/gradle_errors.dart 131 + @@ -240,7 +240,12 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError( 132 + required bool usesAndroidX, 133 + }) async { 134 + final RunResult tasksRunResult = await globals.processUtils.run( 135 + - <String>[globals.gradleUtils!.getExecutable(project), 'app:tasks', '--all', '--console=auto'], 136 + + <String>[ 137 + + ...globals.gradleUtils!.getExecutable(project), 138 + + 'app:tasks', 139 + + '--all', 140 + + '--console=auto', 141 + + ], 142 + throwOnError: true, 143 + workingDirectory: project.android.hostAppGradleRoot.path, 144 + environment: globals.java?.environment, 145 + --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart 146 + +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart 147 + @@ -3,6 +3,7 @@ 148 + // found in the LICENSE file. 149 + 150 + import 'package:meta/meta.dart'; 151 + +import 'package:path/path.dart'; 152 + import 'package:process/process.dart'; 153 + import 'package:unified_analytics/unified_analytics.dart'; 154 + 155 + @@ -154,9 +155,29 @@ class GradleUtils { 156 + final Logger _logger; 157 + final OperatingSystemUtils _operatingSystemUtils; 158 + 159 + + List<String> get _requiredArguments { 160 + + final String cacheDir = join( 161 + + switch (globals.platform.environment['XDG_CACHE_HOME']) { 162 + + final String cacheHome => cacheHome, 163 + + _ => join( 164 + + globals.fsUtils.homeDirPath ?? throwToolExit('No cache directory has been specified.'), 165 + + '.cache', 166 + + ), 167 + + }, 168 + + 'flutter', 169 + + 'nix-flutter-tools-gradle', 170 + + globals.flutterVersion.engineRevision.substring(0, 10), 171 + + ); 172 + + 173 + + return <String>[ 174 + + '--project-cache-dir=${join(cacheDir, 'cache')}', 175 + + '-Pkotlin.project.persistent.dir=${join(cacheDir, 'kotlin')}', 176 + + ]; 177 + + } 178 + + 179 + /// Gets the Gradle executable path and prepares the Gradle project. 180 + /// This is the `gradlew` or `gradlew.bat` script in the `android/` directory. 181 + - String getExecutable(FlutterProject project) { 182 + + List<String> getExecutable(FlutterProject project) { 183 + final Directory androidDir = project.android.hostAppGradleRoot; 184 + injectGradleWrapperIfNeeded(androidDir); 185 + 186 + @@ -167,7 +188,7 @@ class GradleUtils { 187 + // If the Gradle executable doesn't have execute permission, 188 + // then attempt to set it. 189 + _operatingSystemUtils.makeExecutable(gradle); 190 + - return gradle.absolute.path; 191 + + return <String>[gradle.absolute.path, ..._requiredArguments]; 192 + } 193 + throwToolExit( 194 + 'Unable to locate gradlew script. Please check that ${gradle.path} ' 195 + --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart 196 + +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart 197 + @@ -2740,8 +2740,8 @@ Gradle Crashed 198 + 199 + class FakeGradleUtils extends Fake implements GradleUtils { 200 + @override 201 + - String getExecutable(FlutterProject project) { 202 + - return 'gradlew'; 203 + + List<String> getExecutable(FlutterProject project) { 204 + + return const <String>['gradlew']; 205 + } 206 + } 207 + 208 + --- a/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart 209 + +++ b/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart 210 + @@ -1580,8 +1580,8 @@ Platform fakePlatform(String name) { 211 + 212 + class FakeGradleUtils extends Fake implements GradleUtils { 213 + @override 214 + - String getExecutable(FlutterProject project) { 215 + - return 'gradlew'; 216 + + List<String> getExecutable(FlutterProject project) { 217 + + return const <String>['gradlew']; 218 + } 219 + } 220 +
+171 -6
pkgs/development/compilers/flutter/versions/3_32/patches/gradle-flutter-tools-wrapper.patch
··· 15 15 This Gradle project will build the actual `packages/flutter_tools/gradle` project by setting 16 16 `rootProject.projectDir = new File("$settingsDir")` and `apply from: new File("$settingsDir/settings.gradle.kts")`. 17 17 18 - Now the `.gradle` will be built in `$HOME/.cache/flutter/nix-flutter-tools-gradle/<short engine rev>/`, but `build` doesn't. 19 - To move `build` to `$HOME/.cache/flutter/nix-flutter-tools-gradle/<short engine rev>/` as well, we need to set `buildDirectory`. 20 - diff --git a/packages/flutter_tools/gradle/settings.gradle b/packages/flutter_tools/gradle/settings.gradle 21 - new file mode 100644 22 - index 0000000000..b2485c94b4 18 + To move `build` to `$HOME/.cache/flutter/nix-flutter-tools-gradle/<short engine rev>/`, we need to set `buildDirectory`. 19 + To move `.gradle` as well, the `--project-cache-dir` argument must be passed to the Gradle wrapper. 20 + Changing the `GradleUtils.getExecutable` function signature is a delibarate choice, to ensure that no new unpatched usages slip in. 23 21 --- /dev/null 24 22 +++ b/packages/flutter_tools/gradle/settings.gradle 25 23 @@ -0,0 +1,19 @@ ··· 44 42 +includeBuild(dir) 45 43 --- a/packages/flutter_tools/gradle/build.gradle.kts 46 44 +++ b/packages/flutter_tools/gradle/build.gradle.kts 47 - @@ -4,6 +4,8 @@ 45 + @@ -4,6 +4,11 @@ 48 46 49 47 import org.jetbrains.kotlin.gradle.dsl.JvmTarget 50 48 49 + +// While flutter_tools runs Gradle with a --project-cache-dir, this startParameter 50 + +// is not passed correctly to the Kotlin Gradle plugin for some reason, and so 51 + +// must be set here as well. 51 52 +gradle.startParameter.projectCacheDir = layout.buildDirectory.dir("cache").get().asFile 52 53 + 53 54 plugins { 54 55 `java-gradle-plugin` 55 56 groovy 57 + --- a/packages/flutter_tools/lib/src/android/gradle.dart 58 + +++ b/packages/flutter_tools/lib/src/android/gradle.dart 59 + @@ -456,9 +456,9 @@ class AndroidGradleBuilder implements AndroidBuilder { 60 + // from the local.properties file. 61 + updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); 62 + 63 + - final List<String> options = <String>[]; 64 + - 65 + - final String gradleExecutablePath = _gradleUtils.getExecutable(project); 66 + + final [String gradleExecutablePath, ...List<String> options] = _gradleUtils.getExecutable( 67 + + project, 68 + + ); 69 + 70 + // All automatically created files should exist. 71 + if (configOnly) { 72 + @@ -781,7 +781,7 @@ class AndroidGradleBuilder implements AndroidBuilder { 73 + 'aar_init_script.gradle', 74 + ); 75 + final List<String> command = <String>[ 76 + - _gradleUtils.getExecutable(project), 77 + + ..._gradleUtils.getExecutable(project), 78 + '-I=$initScript', 79 + '-Pflutter-root=$flutterRoot', 80 + '-Poutput-dir=${outputDirectory.path}', 81 + @@ -896,6 +896,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 82 + final List<String> results = <String>[]; 83 + 84 + try { 85 + + final [String gradleExecutablePath, ...List<String> options] = _gradleUtils.getExecutable( 86 + + project, 87 + + ); 88 + + 89 + exitCode = await _runGradleTask( 90 + _kBuildVariantTaskName, 91 + preRunTask: () { 92 + @@ -911,10 +915,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 93 + ), 94 + ); 95 + }, 96 + - options: const <String>['-q'], 97 + + options: <String>[...options, '-q'], 98 + project: project, 99 + localGradleErrors: gradleErrors, 100 + - gradleExecutablePath: _gradleUtils.getExecutable(project), 101 + + gradleExecutablePath: gradleExecutablePath, 102 + outputParser: (String line) { 103 + if (_kBuildVariantRegex.firstMatch(line) case final RegExpMatch match) { 104 + results.add(match.namedGroup(_kBuildVariantRegexGroupName)!); 105 + @@ -948,6 +952,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 106 + late Stopwatch sw; 107 + int exitCode = 1; 108 + try { 109 + + final [String gradleExecutablePath, ...List<String> options] = _gradleUtils.getExecutable( 110 + + project, 111 + + ); 112 + + 113 + exitCode = await _runGradleTask( 114 + taskName, 115 + preRunTask: () { 116 + @@ -963,10 +971,10 @@ class AndroidGradleBuilder implements AndroidBuilder { 117 + ), 118 + ); 119 + }, 120 + - options: <String>['-q', '-PoutputPath=$outputPath'], 121 + + options: <String>[...options, '-q', '-PoutputPath=$outputPath'], 122 + project: project, 123 + localGradleErrors: gradleErrors, 124 + - gradleExecutablePath: _gradleUtils.getExecutable(project), 125 + + gradleExecutablePath: gradleExecutablePath, 126 + ); 127 + } on Error catch (error) { 128 + _logger.printError(error.toString()); 129 + --- a/packages/flutter_tools/lib/src/android/gradle_errors.dart 130 + +++ b/packages/flutter_tools/lib/src/android/gradle_errors.dart 131 + @@ -240,7 +240,12 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError( 132 + required bool usesAndroidX, 133 + }) async { 134 + final RunResult tasksRunResult = await globals.processUtils.run( 135 + - <String>[globals.gradleUtils!.getExecutable(project), 'app:tasks', '--all', '--console=auto'], 136 + + <String>[ 137 + + ...globals.gradleUtils!.getExecutable(project), 138 + + 'app:tasks', 139 + + '--all', 140 + + '--console=auto', 141 + + ], 142 + throwOnError: true, 143 + workingDirectory: project.android.hostAppGradleRoot.path, 144 + environment: globals.java?.environment, 145 + --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart 146 + +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart 147 + @@ -3,6 +3,7 @@ 148 + // found in the LICENSE file. 149 + 150 + import 'package:meta/meta.dart'; 151 + +import 'package:path/path.dart'; 152 + import 'package:process/process.dart'; 153 + import 'package:unified_analytics/unified_analytics.dart'; 154 + 155 + @@ -154,9 +155,29 @@ class GradleUtils { 156 + final Logger _logger; 157 + final OperatingSystemUtils _operatingSystemUtils; 158 + 159 + + List<String> get _requiredArguments { 160 + + final String cacheDir = join( 161 + + switch (globals.platform.environment['XDG_CACHE_HOME']) { 162 + + final String cacheHome => cacheHome, 163 + + _ => join( 164 + + globals.fsUtils.homeDirPath ?? throwToolExit('No cache directory has been specified.'), 165 + + '.cache', 166 + + ), 167 + + }, 168 + + 'flutter', 169 + + 'nix-flutter-tools-gradle', 170 + + globals.flutterVersion.engineRevision.substring(0, 10), 171 + + ); 172 + + 173 + + return <String>[ 174 + + '--project-cache-dir=${join(cacheDir, 'cache')}', 175 + + '-Pkotlin.project.persistent.dir=${join(cacheDir, 'kotlin')}', 176 + + ]; 177 + + } 178 + + 179 + /// Gets the Gradle executable path and prepares the Gradle project. 180 + /// This is the `gradlew` or `gradlew.bat` script in the `android/` directory. 181 + - String getExecutable(FlutterProject project) { 182 + + List<String> getExecutable(FlutterProject project) { 183 + final Directory androidDir = project.android.hostAppGradleRoot; 184 + injectGradleWrapperIfNeeded(androidDir); 185 + 186 + @@ -167,7 +188,7 @@ class GradleUtils { 187 + // If the Gradle executable doesn't have execute permission, 188 + // then attempt to set it. 189 + _operatingSystemUtils.makeExecutable(gradle); 190 + - return gradle.absolute.path; 191 + + return <String>[gradle.absolute.path, ..._requiredArguments]; 192 + } 193 + throwToolExit( 194 + 'Unable to locate gradlew script. Please check that ${gradle.path} ' 195 + --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart 196 + +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart 197 + @@ -2740,8 +2740,8 @@ Gradle Crashed 198 + 199 + class FakeGradleUtils extends Fake implements GradleUtils { 200 + @override 201 + - String getExecutable(FlutterProject project) { 202 + - return 'gradlew'; 203 + + List<String> getExecutable(FlutterProject project) { 204 + + return const <String>['gradlew']; 205 + } 206 + } 207 + 208 + --- a/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart 209 + +++ b/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart 210 + @@ -1580,8 +1580,8 @@ Platform fakePlatform(String name) { 211 + 212 + class FakeGradleUtils extends Fake implements GradleUtils { 213 + @override 214 + - String getExecutable(FlutterProject project) { 215 + - return 'gradlew'; 216 + + List<String> getExecutable(FlutterProject project) { 217 + + return const <String>['gradlew']; 218 + } 219 + } 220 +