1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:meta/meta.dart';
6import 'package:process/process.dart';
7import 'package:unified_analytics/unified_analytics.dart';
8
9import '../base/common.dart';
10import '../base/file_system.dart';
11import '../base/io.dart';
12import '../base/logger.dart';
13import '../base/os.dart';
14import '../base/platform.dart';
15import '../base/utils.dart';
16import '../base/version.dart';
17import '../base/version_range.dart';
18import '../build_info.dart';
19import '../cache.dart';
20import '../globals.dart' as globals;
21import '../project.dart';
22import 'android_sdk.dart';
23
24// These are the versions used in the project templates.
25//
26// In general, Flutter aims to default to the latest stable version.
27// However, this currently requires to migrate existing integration tests to the
28// latest supported values.
29//
30// Please see the README before changing any of these values.
31
32// See https://gradle.org/releases
33const templateDefaultGradleVersion = '8.12';
34
35// When bumping, also update:
36// * ndkVersion constant in this file
37// * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt
38// * AGP version constants in packages/flutter_tools/gradle/build.gradle.kts
39// * AGP test constants in packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt
40// See https://mvnrepository.com/artifact/com.android.tools.build/gradle
41const templateAndroidGradlePluginVersion = '8.9.1';
42const templateAndroidGradlePluginVersionForModule = '8.9.1';
43
44// See https://kotlinlang.org/docs/releases.html#release-details
45const templateKotlinGradlePluginVersion = '2.1.0';
46
47// The Flutter Gradle Plugin is only applied to app projects, and modules that
48// are built from source using (`include_flutter.groovy`). The remaining
49// projects are: plugins, and modules compiled as AARs. In modules, the
50// ephemeral directory `.android` is always regenerated after `flutter pub get`,
51// so new versions are picked up after a Flutter upgrade.
52//
53// Please see the README before changing any of these values.
54const compileSdkVersion = '36';
55const minSdkVersion = '24';
56const targetSdkVersion = '36';
57const ndkVersion = '27.0.12077973';
58
59// Update these when new major versions of Java are supported by new Gradle
60// versions that we support.
61// Source of truth: https://docs.gradle.org/current/userguide/compatibility.html
62const oneMajorVersionHigherJavaVersion = '24';
63
64// Update this when new versions of Gradle come out including minor versions
65// and should correspond to the maximum Gradle version we test in CI.
66//
67// Supported here means supported by the tooling for
68// flutter analyze --suggestions and does not imply broader flutter support.
69const maxKnownAndSupportedGradleVersion = '8.12';
70
71// Update this with new KGP versions come out including minor versions.
72//
73// Supported here means supported by the tooling for
74// flutter analyze --suggestions and does not imply broader flutter support.
75const maxKnownAndSupportedKgpVersion = '2.1.20';
76
77// Update this when new versions of AGP come out.
78//
79// Supported here means tooling is aware of this version's Java <-> AGP
80// compatibility.
81@visibleForTesting
82const maxKnownAndSupportedAgpVersion = '8.9.1';
83
84// Update this when new versions of AGP come out.
85const maxKnownAgpVersion = '8.9.1';
86
87// Supported here means tooling is aware of this versions
88// Java <-> AGP compatibility and does not imply broader flutter support.
89// For use in flutter see the code in:
90// flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt
91@visibleForTesting
92const oldestConsideredAgpVersion = '3.3.0';
93
94// Supported here means tooling is aware of this versions
95// gradle compatibility and does not imply broader flutter support.
96@visibleForTesting
97const oldestConsideredGradleVersion = '4.10.1';
98
99// Supported here means tooling is aware of this versions
100// gradle/AGP compatibility and does not imply broader flutter support.
101@visibleForTesting
102const oldestDocumentedKgpCompatabilityVersion = '1.6.20';
103
104// Oldest documented version of AGP that has a listed minimum
105// compatible Java version.
106const oldestDocumentedJavaAgpCompatibilityVersion = '4.2';
107
108// Constant used in [_buildAndroidGradlePluginRegExp] and
109// [_settingsAndroidGradlePluginRegExp] and [_kotlinGradlePluginRegExpFromId]
110// to identify the version section.
111const _versionGroupName = 'version';
112
113// AGP can be defined in the dependencies block of [build.gradle] or [build.gradle.kts].
114// Expected content (covers both classpath and compileOnly cases):
115// Groovy DSL with single quotes - 'com.android.tools.build:gradle:{{agpVersion}}'
116// Groovy DSL with double quotes - "com.android.tools.build:gradle:{{agpVersion}}"
117// Kotlin DSL - ("com.android.tools.build.gradle:{{agpVersion}}")
118// ? is used to name the version group which helps with extraction.
119final _androidGradlePluginRegExpFromDependencies = RegExp(
120 r"""[^\/]*\s*((\bclasspath\b)|(\bcompileOnly\b))\s*\(?['"]com\.android\.tools\.build:gradle:(?<version>\d+(\.\d+){1,2})\)?""",
121 multiLine: true,
122);
123
124// AGP can be defined in the plugins block of [build.gradle],
125// [build.gradle.kts], [settings.gradle], or [settings.gradle.kts].
126// Expected content:
127// Groovy DSL with single quotes - id 'com.android.application' version '{{agpVersion}}'
128// Groovy DSL with double quotes - id "com.android.application" version "{{agpVersion}}"
129// Kotlin DSL - id("com.android.application") version "{{agpVersion}}"
130// ? is used to name the version group which helps with extraction.
131final _androidGradlePluginRegExpFromId = RegExp(
132 r"""[^\/]*s*id\s*\(?['"]com\.android\.application['"]\)?\s+version\s+['"](?<version>\d+(\.\d+){1,2})\)?""",
133 multiLine: true,
134);
135
136// KGP is defined in several places this code only checks in plugins block
137// of [settings.gradle] and [settings.gradle.kts].
138// Expected content:
139// Groovy DSL - id "org.jetbrains.kotlin.android" version "{{kgpVersion}}"
140// Kotlin DSL - id("org.jetbrains.kotlin.android") version "{{kgpVersion}}"
141// ? is used to name the version group which helps with extraction.
142final _kotlinGradlePluginRegExpFromId = RegExp(
143 r"""[^\/]*s*id\s*\(?['"]org\.jetbrains\.kotlin\.android['"]\)?\s+version\s+['"](?<version>\d+(\.\d+){1,2})\)?""",
144 multiLine: true,
145);
146
147// Expected content format (with lines above and below).
148// Version can have 2 or 3 numbers.
149// 'distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip'
150// '^\s*' protects against commented out lines.
151final distributionUrlRegex = RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true);
152
153// Modified version of the gradle distribution url match designed to only match
154// gradle.org urls so that we can guarantee any modifications to the url
155// still points to a hosted zip.
156final gradleOrgVersionMatch = RegExp(
157 r'^\s*distributionUrl\s*=\s*https\\://services\.gradle\.org/distributions/gradle-((?:\d|\.)+)-(.*)\.zip',
158 multiLine: true,
159);
160
161// This matches uncommented minSdkVersion lines in the module-level build.gradle
162// file which have minSdkVersion 16, 17, 18, 19, 20, 21, 22, 23 set with space sytax,
163// equals syntax and when using minSdk or minSdkVersion.
164final tooOldMinSdkVersionMatch = RegExp(
165 r'(?<=^\s*)minSdk(Version)?\s=?\s?(1[6789]|2[0123])(?=\s*(?://|$))',
166 multiLine: true,
167);
168
169// From https://docs.gradle.org/current/userguide/command_line_interface.html#command_line_interface
170// Flag to print the versions for gradle, kotlin dsl, groovy, etc.
171const gradleVersionsFlag = r'--version';
172
173// Directory under android/ that gradle uses to store gradle information.
174// Regularly used with [gradleWrapperDirectory] and
175// [gradleWrapperPropertiesFilename].
176// Different from the directory of gradle files stored in
177// `_cache.getArtifactDirectory('gradle_wrapper')`
178const gradleDirectoryName = 'gradle';
179const gradleWrapperDirectoryName = 'wrapper';
180const gradleWrapperPropertiesFilename = 'gradle-wrapper.properties';
181
182/// Provides utilities to run a Gradle task, such as finding the Gradle executable
183/// or constructing a Gradle project.
184class GradleUtils {
185 GradleUtils({
186 required Platform platform,
187 required Logger logger,
188 required Cache cache,
189 required OperatingSystemUtils operatingSystemUtils,
190 }) : _platform = platform,
191 _logger = logger,
192 _cache = cache,
193 _operatingSystemUtils = operatingSystemUtils;
194
195 final Cache _cache;
196 final Platform _platform;
197 final Logger _logger;
198 final OperatingSystemUtils _operatingSystemUtils;
199
200 /// Gets the Gradle executable path and prepares the Gradle project.
201 /// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
202 String getExecutable(FlutterProject project) {
203 final Directory androidDir = project.android.hostAppGradleRoot;
204 injectGradleWrapperIfNeeded(androidDir);
205
206 final File gradle = androidDir.childFile(getGradlewFileName(_platform));
207
208 if (gradle.existsSync()) {
209 _logger.printTrace('Using gradle from ${gradle.absolute.path}.');
210 // If the Gradle executable doesn't have execute permission,
211 // then attempt to set it.
212 _operatingSystemUtils.makeExecutable(gradle);
213 return gradle.absolute.path;
214 }
215 throwToolExit(
216 'Unable to locate gradlew script. Please check that ${gradle.path} '
217 'exists or that ${gradle.dirname} can be read.',
218 );
219 }
220
221 /// Injects the Gradle wrapper files if any of these files don't exist in [directory].
222 void injectGradleWrapperIfNeeded(Directory directory) {
223 copyDirectory(
224 _cache.getArtifactDirectory('gradle_wrapper'),
225 directory,
226 shouldCopyFile: (File sourceFile, File destinationFile) {
227 // Don't override the existing files in the project.
228 return !destinationFile.existsSync();
229 },
230 onFileCopied: (File source, File dest) {
231 _operatingSystemUtils.makeExecutable(dest);
232 },
233 );
234 // Add the `gradle-wrapper.properties` file if it doesn't exist.
235 final Directory propertiesDirectory = directory
236 .childDirectory(gradleDirectoryName)
237 .childDirectory(gradleWrapperDirectoryName);
238 final File propertiesFile = propertiesDirectory.childFile(gradleWrapperPropertiesFilename);
239
240 if (propertiesFile.existsSync()) {
241 return;
242 }
243 propertiesDirectory.createSync(recursive: true);
244 final String gradleVersion = getGradleVersionForAndroidPlugin(directory, _logger);
245 final propertyContents =
246 '''
247distributionBase=GRADLE_USER_HOME
248distributionPath=wrapper/dists
249zipStoreBase=GRADLE_USER_HOME
250zipStorePath=wrapper/dists
251distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersion-all.zip
252''';
253 propertiesFile.writeAsStringSync(propertyContents);
254 }
255}
256
257/// Returns the Gradle version that the current Android plugin depends on when found,
258/// otherwise it returns a default version.
259///
260/// The Android plugin version is specified in the `build.gradle`,
261/// `build.gradle.kts`, `settings.gradle`, or `settings.gradle.kts` file within
262/// the project's Android directory.
263String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
264 final String? androidPluginVersion = getAgpVersion(directory, logger);
265 if (androidPluginVersion == null) {
266 logger.printTrace(
267 'AGP version cannot be determined, assuming Gradle version: $templateDefaultGradleVersion',
268 );
269 return templateDefaultGradleVersion;
270 }
271 return getGradleVersionFor(androidPluginVersion);
272}
273
274/// Returns the gradle file from the top level directory.
275/// The returned file is not guaranteed to be present.
276File getGradleWrapperFile(Directory directory) {
277 return directory
278 .childDirectory(gradleDirectoryName)
279 .childDirectory(gradleWrapperDirectoryName)
280 .childFile(gradleWrapperPropertiesFilename);
281}
282
283/// Parses the gradle wrapper distribution url to return a string containing
284/// the version number.
285///
286/// Expected input is of the form '...gradle-7.4.2-all.zip', and the output
287/// would be of the form '7.4.2'.
288String? parseGradleVersionFromDistributionUrl(String? distributionUrl) {
289 if (distributionUrl == null) {
290 return null;
291 }
292 final List<String> zipParts = distributionUrl.split('-');
293 if (zipParts.length < 2) {
294 return null;
295 }
296 return zipParts[1];
297}
298
299/// Returns either the gradle-wrapper.properties value from the passed in
300/// [directory] or if not present the version available in local path.
301///
302/// If gradle version is not found null is returned.
303/// [directory] should be an android directory with a build.gradle file.
304Future<String?> getGradleVersion(
305 Directory directory,
306 Logger logger,
307 ProcessManager processManager,
308) async {
309 final File propertiesFile = getGradleWrapperFile(directory);
310
311 if (propertiesFile.existsSync()) {
312 final String wrapperFileContent = propertiesFile.readAsStringSync();
313
314 final RegExpMatch? distributionUrl = distributionUrlRegex.firstMatch(wrapperFileContent);
315 if (distributionUrl != null) {
316 final String? gradleVersion = parseGradleVersionFromDistributionUrl(distributionUrl.group(0));
317 if (gradleVersion != null) {
318 return gradleVersion;
319 } else {
320 // Did not find gradle zip url. Likely this is a bug in our parsing.
321 logger.printWarning(_formatParseWarning(wrapperFileContent, type: 'gradle'));
322 }
323 } else {
324 // If no distributionUrl log then treat as if there was no propertiesFile.
325 logger.printTrace(
326 '$propertiesFile does not provide a Gradle version falling back to system gradle.',
327 );
328 }
329 } else {
330 // Could not find properties file.
331 logger.printTrace('$propertiesFile does not exist falling back to system gradle');
332 }
333 // System installed Gradle version.
334 // TODO(reidbaker): Modify this gradle execution to use gradlew.
335 if (processManager.canRun('gradle')) {
336 final gradleVersionsVerbose =
337 (await processManager.run(<String>['gradle', gradleVersionsFlag])).stdout as String;
338 // Expected format:
339 /*
340
341------------------------------------------------------------
342Gradle 7.6
343------------------------------------------------------------
344
345Build time: 2022-11-25 13:35:10 UTC
346Revision: daece9dbc5b79370cc8e4fd6fe4b2cd400e150a8
347
348Kotlin: 1.7.10
349Groovy: 3.0.13
350Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
351JVM: 17.0.6 (Homebrew 17.0.6+0)
352OS: Mac OS X 13.2.1 aarch64
353 */
354 // Observation shows that the version can have 2 or 3 numbers.
355 // Inner parentheticals `(\.\d+)?` denote the optional third value.
356 // Outer parentheticals `Gradle (...)` denote a grouping used to extract
357 // the version number.
358 final gradleVersionRegex = RegExp(r'Gradle\s+(\d+\.\d+(?:\.\d+)?)');
359 final RegExpMatch? version = gradleVersionRegex.firstMatch(gradleVersionsVerbose);
360 if (version == null) {
361 // Most likely a bug in our parse implementation/regex.
362 logger.printWarning(_formatParseWarning(gradleVersionsVerbose, type: 'gradle'));
363 return null;
364 }
365 return version.group(1);
366 } else {
367 logger.printTrace('Could not run system gradle');
368 return null;
369 }
370}
371
372/// Returns the Kotlin Gradle Plugin (KGP) version that the current project
373/// depends on if found, `null` otherwise.
374/// [androidDirectory] should be an android directory with a `build.gradle` file.
375Future<String?> getKgpVersion(
376 Directory androidDirectory,
377 Logger logger,
378 ProcessManager processManager,
379) async {
380 // Maintainers of the kotlin dsl and the kotlin gradle plugin are different.
381 //
382 // Android Docs refer to the kotlin gradle plugin with either the full name or KGP.
383 // Kotlin docs refer to the kotlin gradle plugin as kotlin android plugin.
384 //
385 // gradle --version or ./gradlew --version will print the kotlin dsl version.
386 // This version normally changes with the version of gradle.
387 // https://github.com/gradle/gradle/blob/cefbee263181a924ac4efcaace6bda97a55bc0f7/platforms/core-runtime/gradle-cli/src/main/java/org/gradle/launcher/cli/DefaultCommandLineActionFactory.java#L260
388 // This vesion is NOT the version of KGP that the project uses.
389 //
390 // Instead the kgpVersion task is a custom flutter task dynamiclly added that can
391 // print the kgp version if gradle can run successfuly.
392
393 if (processManager.canRun('./gradlew', workingDirectory: androidDirectory.path)) {
394 final ProcessResult command = await processManager.run(<String>[
395 './gradlew',
396 'kgpVersion',
397 '-q',
398 ], workingDirectory: androidDirectory.path);
399 if (command.exitCode == 0) {
400 final kgpVersionOutput = command.stdout as String;
401
402 // See expected output defined in
403 // flutter/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt addTaskForKGPVersion
404 final kotlinVersionRegex = RegExp(r'KGP Version:\s+(\d+\.\d+(?:\.\d+)?)');
405 final RegExpMatch? version = kotlinVersionRegex.firstMatch(kgpVersionOutput);
406 if (version != null) {
407 return version.group(1);
408 }
409 // Most likely a bug in our parse implementation/regex.
410 logger.printWarning(_formatParseWarning(kgpVersionOutput, type: 'kotlin'));
411 } else {
412 logger.printTrace('Non zero exit code from gradle task kgpVersion.');
413 }
414 } else {
415 logger.printTrace('Could not run gradle task kgpVersion.');
416 }
417
418 // Project valiation code is regularly run on projects that can not build.
419 // Because of that this code also attempts to search through known template
420 // locations for kotlin versions.
421
422 logger.printTrace('Checking settings for kgp version.');
423 File settingsFile = androidDirectory.childFile('settings.gradle');
424 if (!settingsFile.existsSync()) {
425 settingsFile = androidDirectory.childFile('settings.gradle.kts');
426 }
427
428 if (settingsFile.existsSync()) {
429 final String settingsFileContent = settingsFile.readAsStringSync();
430 final RegExpMatch? settingsMatch = _kotlinGradlePluginRegExpFromId.firstMatch(
431 settingsFileContent,
432 );
433
434 if (settingsMatch != null) {
435 final String? kgpVersion = settingsMatch.namedGroup(_versionGroupName);
436 logger.printTrace('$settingsFile provides KGP version: $kgpVersion');
437 return kgpVersion;
438 }
439 } else {
440 logger.printTrace('No settings.gradle.kts');
441 }
442
443 return null;
444}
445
446/// Returns the Android Gradle Plugin (AGP) version that the current project
447/// depends on when found, null otherwise.
448///
449/// The Android plugin version is specified in the `build.gradle`,
450/// `build.gradle.kts`, `settings.gradle, or `settings.gradle.kts`
451/// files within the project's Android directory ([androidDirectory]).
452String? getAgpVersion(Directory androidDirectory, Logger logger) {
453 File buildFile = androidDirectory.childFile('build.gradle');
454 if (!buildFile.existsSync()) {
455 buildFile = androidDirectory.childFile('build.gradle.kts');
456 }
457 if (!buildFile.existsSync()) {
458 logger.printTrace('Cannot find build.gradle/build.gradle.kts in $androidDirectory');
459 return null;
460 }
461 final String buildFileContent = buildFile.readAsStringSync();
462 final RegExpMatch? buildMatchClasspath = _androidGradlePluginRegExpFromDependencies.firstMatch(
463 buildFileContent,
464 );
465 if (buildMatchClasspath != null) {
466 final String? androidPluginVersion = buildMatchClasspath.namedGroup(_versionGroupName);
467 logger.printTrace('$buildFile provides AGP version from classpath: $androidPluginVersion');
468 return androidPluginVersion;
469 }
470 final RegExpMatch? buildMatchId = _androidGradlePluginRegExpFromId.firstMatch(buildFileContent);
471 if (buildMatchId != null) {
472 final String? androidPluginVersion = buildMatchId.namedGroup(_versionGroupName);
473 logger.printTrace('$buildFile provides AGP version from plugin id: $androidPluginVersion');
474 return androidPluginVersion;
475 }
476
477 logger.printTrace("$buildFile doesn't provide an AGP version. Checking settings.");
478 File settingsFile = androidDirectory.childFile('settings.gradle');
479 if (!settingsFile.existsSync()) {
480 settingsFile = androidDirectory.childFile('settings.gradle.kts');
481 }
482 if (!settingsFile.existsSync()) {
483 logger.printTrace('Cannot find settings.gradle/settings.gradle.kts in $androidDirectory');
484 return null;
485 }
486 final String settingsFileContent = settingsFile.readAsStringSync();
487 final RegExpMatch? settingsMatch = _androidGradlePluginRegExpFromId.firstMatch(
488 settingsFileContent,
489 );
490
491 if (settingsMatch != null) {
492 final String? androidPluginVersion = settingsMatch.namedGroup(_versionGroupName);
493 logger.printTrace('$settingsFile provides AGP version: $androidPluginVersion');
494 return androidPluginVersion;
495 }
496 logger.printTrace("$settingsFile doesn't provide an AGP version.");
497 return null;
498}
499
500String _formatParseWarning(String content, {required String type}) {
501 return 'Could not parse $type version from: \n'
502 '$content \n'
503 'If there is a version please look for an existing bug '
504 'https://github.com/flutter/flutter/issues/'
505 ' and if one does not exist file a new issue.';
506}
507
508// Validate that KGP and Gradle are compatible with each other.
509//
510// Returns true if versions are compatible.
511// Null or empty Gradle or KGP version returns false.
512// If compatibility cannot be evaluated returns false.
513// If versions are newer than the max known version a warning is logged and true
514// returned.
515//
516// Source of truth found here:
517// https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
518bool validateGradleAndKGP(Logger logger, {required String? kgpV, required String? gradleV}) {
519 if (gradleV == null || kgpV == null || gradleV.isEmpty || kgpV.isEmpty) {
520 logger.printTrace('Gradle or KGP version unknown ($gradleV, $kgpV).');
521 return false;
522 }
523
524 if (isWithinVersionRange(gradleV, min: '0.0', max: oldestConsideredGradleVersion)) {
525 logger.printTrace(
526 'Gradle version $gradleV older than oldest considered $oldestConsideredGradleVersion',
527 );
528 return false;
529 }
530
531 if (isWithinVersionRange(
532 kgpV,
533 min: maxKnownAndSupportedKgpVersion,
534 max: '100.100',
535 inclusiveMin: false,
536 )) {
537 logger.printTrace(
538 'Newer than known KGP version ($kgpV), gradle ($gradleV).'
539 '\n Treating as valid configuration.',
540 );
541 return true;
542 }
543
544 // https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
545 // Documenation is non continuous, past versions are known to the
546 // publishers of KGP. When covering version ranges beyond what is documented
547 // add a comment with the documented value.
548 // Continuous KGP version handling is prefered in case an emergency patch to a
549 // past release is shipped this code will assume the version range that is closest.
550 if (isWithinVersionRange(kgpV, min: '2.1.20', max: '2.1.20')) {
551 // Documented max is 8.11, using 8.12 non inclusive covers patch versions.
552 return isWithinVersionRange(gradleV, min: '7.6.3', max: '8.12', inclusiveMax: false);
553 }
554 if (isWithinVersionRange(kgpV, min: '2.1.0', max: '2.1.10')) {
555 // Documented max is 8.10, using 8.11 non inclusive covers patch versions.
556 return isWithinVersionRange(gradleV, min: '7.6.3', max: '8.11', inclusiveMax: false);
557 }
558 // Documented max is 2.0.21.
559 if (isWithinVersionRange(kgpV, min: '2.0.20', max: '2.1', inclusiveMax: false)) {
560 // Documented max is 8.5, using 8.9 non inclusive covers patch versions.
561 // Kotlin Multiplatform can throw warnings on 8.8.
562 return isWithinVersionRange(gradleV, min: '6.8.3', max: '8.9', inclusiveMax: false);
563 }
564 if (isWithinVersionRange(kgpV, min: '2.0', max: '2.0.20', inclusiveMax: false)) {
565 // Documented max is 8.5, using 8.6 non inclusive covers patch versions.
566 return isWithinVersionRange(gradleV, min: '6.8.3', max: '8.6', inclusiveMax: false);
567 }
568 // Documented max is 1.9.25.
569 if (isWithinVersionRange(kgpV, min: '1.9.20', max: '2.0', inclusiveMax: false)) {
570 return isWithinVersionRange(gradleV, min: '6.8.3', max: '8.1.1');
571 }
572 // Documented max is 1.9.10.
573 if (isWithinVersionRange(kgpV, min: '1.8.20', max: '1.9.20', inclusiveMax: false)) {
574 return isWithinVersionRange(gradleV, min: '6.8.3', max: '7.6.0');
575 }
576 // Documented max is 1.8.11.
577 if (isWithinVersionRange(kgpV, min: '1.8.0', max: '1.8.20', inclusiveMax: false)) {
578 return isWithinVersionRange(gradleV, min: '6.8.3', max: '7.3.3');
579 }
580 // Documented max is 1.7.22.
581 if (isWithinVersionRange(kgpV, min: '1.7.20', max: '1.8.0', inclusiveMax: false)) {
582 return isWithinVersionRange(gradleV, min: '6.7.1', max: '7.1.1');
583 }
584 // Documented max is 1.7.10.
585 if (isWithinVersionRange(kgpV, min: '1.7.0', max: '1.7.20', inclusiveMax: false)) {
586 return isWithinVersionRange(gradleV, min: '6.7.1', max: '7.0.2');
587 }
588 // Documented max is 1.6.21.
589 if (isWithinVersionRange(
590 kgpV,
591 min: oldestDocumentedKgpCompatabilityVersion,
592 max: '1.7.0',
593 inclusiveMax: false,
594 )) {
595 return isWithinVersionRange(gradleV, min: '6.1.1', max: '7.0.2');
596 }
597
598 logger.printTrace('Unknown KGP-Gradle compatibility, KGP: $kgpV, Gradle: $gradleV');
599 return false;
600}
601
602// Validate that KGP and AGP are compatible with each other.
603//
604// Returns true if versions are compatible.
605// Null or empty KGP or AGP version returns false.
606// If compatibility cannot be evaluated returns false.
607// If versions are newer than the max known version a warning is logged and true
608// returned.
609//
610// Source of truth found here:
611// https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
612bool validateAgpAndKgp(Logger logger, {required String? kgpV, required String? agpV}) {
613 if (agpV == null || kgpV == null || agpV.isEmpty || kgpV.isEmpty) {
614 logger.printTrace('KGP or AGP version unknown ($kgpV, $agpV).');
615 return false;
616 }
617
618 if (isWithinVersionRange(agpV, min: '0.0', max: oldestConsideredAgpVersion)) {
619 logger.printTrace(
620 'AGP version ($agpV) older than oldest supported $oldestConsideredAgpVersion.',
621 );
622 }
623 const maxKnownAgpVersionWithFullKotinSupport = '8.7.2';
624
625 if (isWithinVersionRange(
626 kgpV,
627 min: maxKnownAndSupportedKgpVersion,
628 max: '100.100',
629 inclusiveMin: false,
630 ) ||
631 isWithinVersionRange(
632 agpV,
633 min: maxKnownAgpVersionWithFullKotinSupport,
634 max: '100.100',
635 inclusiveMin: false,
636 )) {
637 logger.printTrace(
638 'Newer than known KGP version ($kgpV), AGP ($agpV).'
639 '\n Treating as valid configuration.',
640 );
641 return true;
642 }
643
644 // https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
645 // Documenation is non continuous, past versions are known to the
646 // publishers of KGP. When covering version ranges beyond what is documented
647 // add a comment with the documented value.
648 // Continuous KGP version handling is prefered in case an emergency patch to a
649 // past release is shipped this code will assume the version range that is closest.
650 if (isWithinVersionRange(kgpV, min: '2.1.0', max: '2.1.20')) {
651 return isWithinVersionRange(agpV, min: '7.3.1', max: '8.7.2');
652 }
653 // Documented max is 2.0.21
654 if (isWithinVersionRange(kgpV, min: '2.0.20', max: '2.1.0', inclusiveMax: false)) {
655 // Documented max is 8.5.
656 return isWithinVersionRange(agpV, min: '7.1.3', max: '8.6', inclusiveMax: false);
657 }
658 // Documented max is 2.0.0.
659 if (isWithinVersionRange(kgpV, min: '2.0.0', max: '2.0.20', inclusiveMax: false)) {
660 return isWithinVersionRange(agpV, min: '7.1.3', max: '8.3.1');
661 }
662 // Documented max is 1.9.25
663 if (isWithinVersionRange(kgpV, min: '1.9.20', max: '2.0.0', inclusiveMax: false)) {
664 return isWithinVersionRange(agpV, min: '4.2.2', max: '8.1.0');
665 }
666 // Documented max is 1.9.10
667 if (isWithinVersionRange(kgpV, min: '1.9.0', max: '1.9.20', inclusiveMax: false)) {
668 return isWithinVersionRange(agpV, min: '4.2.2', max: '7.4.0');
669 }
670 // Documented max is 1.8.22
671 if (isWithinVersionRange(kgpV, min: '1.8.20', max: '1.9', inclusiveMax: false)) {
672 return isWithinVersionRange(agpV, min: '4.1.3', max: '7.4.0');
673 }
674 // Documented max is 1.8.11
675 if (isWithinVersionRange(kgpV, min: '1.8.0', max: '1.8.20', inclusiveMax: false)) {
676 return isWithinVersionRange(agpV, min: '4.1.3', max: '7.2.1');
677 }
678 // Documented max is 1.7.22
679 if (isWithinVersionRange(kgpV, min: '1.7.20', max: '1.8.0', inclusiveMax: false)) {
680 return isWithinVersionRange(agpV, min: '3.6.4', max: '7.0.4');
681 }
682 // Documented max is 1.7.10
683 // Documented gap between 1.6.21 and 1.7.0.
684 if (isWithinVersionRange(kgpV, min: '1.6.20', max: '1.7.20', inclusiveMax: false)) {
685 return isWithinVersionRange(agpV, min: '3.4.3', max: '7.0.2');
686 }
687
688 logger.printTrace('Unknown KGP-Gradle compatibility, KGP: $kgpV, AGP: $agpV');
689 return false;
690}
691
692// Validate that Gradle version and AGP are compatible with each other.
693//
694// Returns true if versions are compatible.
695// Null Gradle version or AGP version returns false.
696// If compatibility cannot be evaluated returns false.
697// If versions are newer than the max known version a warning is logged and true
698// returned.
699//
700// Source of truth found here:
701// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
702// AGP has a minimum version of gradle required but no max starting at
703// AGP version 2.3.0+.
704bool validateGradleAndAgp(Logger logger, {required String? gradleV, required String? agpV}) {
705 if (gradleV == null || agpV == null) {
706 logger.printTrace('Gradle version or AGP version unknown ($gradleV, $agpV).');
707 return false;
708 }
709
710 // First check if versions are too old.
711 if (isWithinVersionRange(
712 agpV,
713 min: '0.0',
714 max: oldestConsideredAgpVersion,
715 inclusiveMax: false,
716 )) {
717 logger.printTrace('AGP Version: $agpV is too old.');
718 return false;
719 }
720 if (isWithinVersionRange(
721 gradleV,
722 min: '0.0',
723 max: oldestConsideredGradleVersion,
724 inclusiveMax: false,
725 )) {
726 logger.printTrace('Gradle Version: $gradleV is too old.');
727 return false;
728 }
729
730 // Check highest supported version before checking unknown versions.
731 if (isWithinVersionRange(agpV, min: '8.0', max: maxKnownAndSupportedAgpVersion)) {
732 return isWithinVersionRange(gradleV, min: '8.0', max: maxKnownAndSupportedGradleVersion);
733 }
734 // Check if versions are newer than the max known versions.
735 if (isWithinVersionRange(agpV, min: maxKnownAndSupportedAgpVersion, max: '100.100')) {
736 // Assume versions we do not know about are valid but log.
737 final bool validGradle = isWithinVersionRange(gradleV, min: '8.0', max: '100.00');
738 logger.printTrace(
739 'Newer than known AGP version ($agpV), gradle ($gradleV).'
740 '\n Treating as valid configuration.',
741 );
742 return validGradle;
743 }
744
745 // Begin Known Gradle <-> AGP validation.
746 if (isWithinVersionRange(agpV, min: '8.4.0', max: '8.4.99')) {
747 return isWithinVersionRange(gradleV, min: '8.6', max: maxKnownAndSupportedGradleVersion);
748 }
749 if (isWithinVersionRange(agpV, min: '8.3.0', max: '8.3.99')) {
750 return isWithinVersionRange(gradleV, min: '8.4', max: maxKnownAndSupportedGradleVersion);
751 }
752 if (isWithinVersionRange(agpV, min: '8.2.0', max: '8.2.99')) {
753 return isWithinVersionRange(gradleV, min: '8.2', max: maxKnownAndSupportedGradleVersion);
754 }
755 if (isWithinVersionRange(agpV, min: '8.0.0', max: '8.1.99')) {
756 return isWithinVersionRange(gradleV, min: '8.0', max: maxKnownAndSupportedGradleVersion);
757 }
758 // Max agp here is a made up version to contain all 7.4 changes.
759 if (isWithinVersionRange(agpV, min: '7.4', max: '7.5')) {
760 return isWithinVersionRange(gradleV, min: '7.5', max: maxKnownAndSupportedGradleVersion);
761 }
762 if (isWithinVersionRange(agpV, min: '7.3', max: '7.4', inclusiveMax: false)) {
763 return isWithinVersionRange(gradleV, min: '7.4', max: maxKnownAndSupportedGradleVersion);
764 }
765 if (isWithinVersionRange(agpV, min: '7.2', max: '7.3', inclusiveMax: false)) {
766 return isWithinVersionRange(gradleV, min: '7.3.3', max: maxKnownAndSupportedGradleVersion);
767 }
768 if (isWithinVersionRange(agpV, min: '7.1', max: '7.2', inclusiveMax: false)) {
769 return isWithinVersionRange(gradleV, min: '7.2', max: maxKnownAndSupportedGradleVersion);
770 }
771 if (isWithinVersionRange(agpV, min: '7.0', max: '7.1', inclusiveMax: false)) {
772 return isWithinVersionRange(gradleV, min: '7.0', max: maxKnownAndSupportedGradleVersion);
773 }
774 if (isWithinVersionRange(agpV, min: '4.2.0', max: '7.0', inclusiveMax: false)) {
775 return isWithinVersionRange(gradleV, min: '6.7.1', max: maxKnownAndSupportedGradleVersion);
776 }
777 if (isWithinVersionRange(agpV, min: '4.1.0', max: '4.2.0', inclusiveMax: false)) {
778 return isWithinVersionRange(gradleV, min: '6.5', max: maxKnownAndSupportedGradleVersion);
779 }
780 if (isWithinVersionRange(agpV, min: '4.0.0', max: '4.1.0', inclusiveMax: false)) {
781 return isWithinVersionRange(gradleV, min: '6.1.1', max: maxKnownAndSupportedGradleVersion);
782 }
783 if (isWithinVersionRange(agpV, min: '3.6.0', max: '3.6.4')) {
784 return isWithinVersionRange(gradleV, min: '5.6.4', max: maxKnownAndSupportedGradleVersion);
785 }
786 if (isWithinVersionRange(agpV, min: '3.5.0', max: '3.5.4')) {
787 return isWithinVersionRange(gradleV, min: '5.4.1', max: maxKnownAndSupportedGradleVersion);
788 }
789 if (isWithinVersionRange(agpV, min: '3.4.0', max: '3.4.3')) {
790 return isWithinVersionRange(gradleV, min: '5.1.1', max: maxKnownAndSupportedGradleVersion);
791 }
792 if (isWithinVersionRange(agpV, min: '3.3.0', max: '3.3.3')) {
793 return isWithinVersionRange(gradleV, min: '4.10.1', max: maxKnownAndSupportedGradleVersion);
794 }
795
796 logger.printTrace('Unknown Gradle-AGP compatibility, Gradle: $gradleV, AGP: $agpV');
797 return false;
798}
799
800/// Validate that the [javaVersion] and [gradleVersion] are compatible with
801/// each other.
802///
803/// Source of truth:
804/// https://docs.gradle.org/current/userguide/compatibility.html#java
805bool validateJavaAndGradle(
806 Logger logger, {
807 required String? javaVersion,
808 required String? gradleVersion,
809}) {
810 // https://docs.gradle.org/current/userguide/compatibility.html#java
811 const oldestConsideredJavaVersion = '1.8';
812 const oldestDocumentedJavaGradleCompatibility = '2.0';
813
814 // Begin Java <-> Gradle validation.
815
816 if (javaVersion == null || gradleVersion == null) {
817 logger.printTrace('Java version or Gradle version unknown ($javaVersion, $gradleVersion).');
818 return false;
819 }
820
821 // First check if versions are too old.
822 if (isWithinVersionRange(
823 javaVersion,
824 min: '1.1',
825 max: oldestConsideredJavaVersion,
826 inclusiveMax: false,
827 )) {
828 logger.printTrace('Java Version: $javaVersion is too old.');
829 return false;
830 }
831 if (isWithinVersionRange(
832 gradleVersion,
833 min: '0.0',
834 max: oldestDocumentedJavaGradleCompatibility,
835 inclusiveMax: false,
836 )) {
837 logger.printTrace('Gradle Version: $gradleVersion is too old.');
838 return false;
839 }
840
841 // Check if versions are newer than the max supported versions.
842 if (isWithinVersionRange(javaVersion, min: oneMajorVersionHigherJavaVersion, max: '100.100')) {
843 // Assume versions Java versions newer than [maxSupportedJavaVersion]
844 // required a higher gradle version.
845 final bool validGradle = isWithinVersionRange(
846 gradleVersion,
847 min: maxKnownAndSupportedGradleVersion,
848 max: '100.00',
849 );
850 logger.printWarning(
851 'Newer than known valid Java version ($javaVersion), gradle ($gradleVersion).'
852 '\n Treating as valid configuration.',
853 );
854 return validGradle;
855 }
856
857 // Begin known Java <-> Gradle evaluation.
858 for (final JavaGradleCompat data in _javaGradleCompatList) {
859 if (isWithinVersionRange(
860 javaVersion,
861 min: data.javaMin,
862 max: data.javaMax,
863 inclusiveMax: false,
864 )) {
865 return isWithinVersionRange(gradleVersion, min: data.gradleMin, max: data.gradleMax);
866 }
867 }
868
869 logger.printTrace(
870 'Unknown Java-Gradle compatibility, Java: $javaVersion, Gradle: $gradleVersion',
871 );
872 return false;
873}
874
875/// Returns compatibility information for the valid range of Gradle versions for
876/// the specified Java version.
877///
878/// Returns null when the tooling has not documented the compatible Gradle
879/// versions for the Java version (either the version is too old or too new). If
880/// this seems like a mistake, the caller may need to update the
881/// [_javaGradleCompatList] detailing Java/Gradle compatibility.
882JavaGradleCompat? getValidGradleVersionRangeForJavaVersion(Logger logger, {required String javaV}) {
883 for (final JavaGradleCompat data in _javaGradleCompatList) {
884 if (isWithinVersionRange(javaV, min: data.javaMin, max: data.javaMax, inclusiveMax: false)) {
885 return data;
886 }
887 }
888
889 logger.printTrace('Unable to determine valid Gradle version range for Java version $javaV.');
890 return null;
891}
892
893/// Validate that the specified Java and Android Gradle Plugin (AGP) versions are
894/// compatible with each other.
895///
896/// Returns true when the specified Java and AGP versions are
897/// definitely compatible; otherwise, false is assumed by default. In addition,
898/// this will return false when either a null Java or AGP version is provided.
899///
900/// Source of truth are the AGP release notes:
901/// https://developer.android.com/build/releases/gradle-plugin
902bool validateJavaAndAgp(Logger logger, {required String? javaV, required String? agpV}) {
903 if (javaV == null || agpV == null) {
904 logger.printTrace('Java version or AGP version unknown ($javaV, $agpV).');
905 return false;
906 }
907
908 // Check if AGP version is too old to perform validation.
909 if (isWithinVersionRange(
910 agpV,
911 min: '1.0',
912 max: oldestDocumentedJavaAgpCompatibilityVersion,
913 inclusiveMax: false,
914 )) {
915 logger.printTrace('AGP Version: $agpV is too old to determine Java compatibility.');
916 return false;
917 }
918
919 if (isWithinVersionRange(
920 agpV,
921 min: maxKnownAndSupportedAgpVersion,
922 max: '100.100',
923 inclusiveMin: false,
924 )) {
925 logger.printTrace('AGP Version: $agpV is too new to determine Java compatibility.');
926 return false;
927 }
928
929 // Begin known Java <-> AGP evaluation.
930 for (final JavaAgpCompat data in _javaAgpCompatList) {
931 if (isWithinVersionRange(agpV, min: data.agpMin, max: data.agpMax)) {
932 return isWithinVersionRange(javaV, min: data.javaMin, max: '100.100');
933 }
934 }
935
936 logger.printTrace('Unknown Java-AGP compatibility $javaV, $agpV');
937 return false;
938}
939
940/// Returns compatibility information concerning the minimum AGP
941/// version for the specified Java version.
942JavaAgpCompat? getMinimumAgpVersionForJavaVersion(Logger logger, {required String javaV}) {
943 for (final JavaAgpCompat data in _javaAgpCompatList) {
944 if (isWithinVersionRange(javaV, min: data.javaMin, max: '100.100')) {
945 return data;
946 }
947 }
948
949 logger.printTrace('Unable to determine minimum AGP version for specified Java version.');
950 return null;
951}
952
953/// Returns valid Java range for specified Gradle and AGP versions.
954///
955/// Assumes that gradleV and agpV are compatible versions.
956VersionRange getJavaVersionFor({required String gradleV, required String agpV}) {
957 // Find minimum Java version based on AGP compatibility.
958 String? minJavaVersion;
959 for (final JavaAgpCompat data in _javaAgpCompatList) {
960 if (isWithinVersionRange(agpV, min: data.agpMin, max: data.agpMax)) {
961 minJavaVersion = data.javaMin;
962 }
963 }
964
965 // Find maximum Java version based on Gradle compatibility.
966 String? maxJavaVersion;
967 for (final JavaGradleCompat data in _javaGradleCompatList.reversed) {
968 if (isWithinVersionRange(
969 gradleV,
970 min: data.gradleMin,
971 max: maxKnownAndSupportedGradleVersion,
972 )) {
973 maxJavaVersion = data.javaMax;
974 }
975 }
976
977 return VersionRange(minJavaVersion, maxJavaVersion);
978}
979
980/// Returns the Gradle version that is required by the given Android Gradle plugin version
981/// by picking the largest compatible version from
982/// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
983String getGradleVersionFor(String androidPluginVersion) {
984 final compatList = <GradleForAgp>[
985 GradleForAgp(agpMin: '1.0.0', agpMax: '1.1.3', minRequiredGradle: '2.3'),
986 GradleForAgp(agpMin: '1.2.0', agpMax: '1.3.1', minRequiredGradle: '2.9'),
987 GradleForAgp(agpMin: '1.5.0', agpMax: '1.5.0', minRequiredGradle: '2.2.1'),
988 GradleForAgp(agpMin: '2.0.0', agpMax: '2.1.2', minRequiredGradle: '2.13'),
989 GradleForAgp(agpMin: '2.1.3', agpMax: '2.2.3', minRequiredGradle: '2.14.1'),
990 GradleForAgp(agpMin: '2.3.0', agpMax: '2.9.9', minRequiredGradle: '3.3'),
991 GradleForAgp(agpMin: '3.0.0', agpMax: '3.0.9', minRequiredGradle: '4.1'),
992 GradleForAgp(agpMin: '3.1.0', agpMax: '3.1.9', minRequiredGradle: '4.4'),
993 GradleForAgp(agpMin: '3.2.0', agpMax: '3.2.1', minRequiredGradle: '4.6'),
994 GradleForAgp(agpMin: '3.3.0', agpMax: '3.3.2', minRequiredGradle: '4.10.2'),
995 GradleForAgp(agpMin: '3.4.0', agpMax: '3.5.0', minRequiredGradle: '5.6.2'),
996 GradleForAgp(agpMin: '4.0.0', agpMax: '4.1.0', minRequiredGradle: '6.7'),
997 // 7.5 is a made up value to include everything through 7.4.*
998 GradleForAgp(agpMin: '7.0.0', agpMax: '7.5', minRequiredGradle: '7.5'),
999 // Use 0 and 99 as a patch values to signify every AGP patch version with
1000 // that major and minor version.
1001 GradleForAgp(agpMin: '8.0.0', agpMax: '8.1.99', minRequiredGradle: '8.0'),
1002 GradleForAgp(agpMin: '8.2.0', agpMax: '8.2.99', minRequiredGradle: '8.2'),
1003 GradleForAgp(agpMin: '8.3.0', agpMax: '8.3.99', minRequiredGradle: '8.4'),
1004 GradleForAgp(agpMin: '8.4.0', agpMax: '8.4.99', minRequiredGradle: '8.6'),
1005 GradleForAgp(agpMin: '8.5.0', agpMax: '8.6.99', minRequiredGradle: '8.7'),
1006 GradleForAgp(agpMin: '8.7.0', agpMax: '8.7.99', minRequiredGradle: '8.9'),
1007 GradleForAgp(agpMin: '8.8.0', agpMax: '8.8.99', minRequiredGradle: '8.10.2'),
1008 GradleForAgp(agpMin: '8.9.0', agpMax: '8.9.99', minRequiredGradle: '8.11.1'),
1009 // Assume if AGP is newer than this code know about return the highest gradle
1010 // version we know about.
1011 GradleForAgp(
1012 agpMin: maxKnownAgpVersion,
1013 agpMax: maxKnownAgpVersion,
1014 minRequiredGradle: maxKnownAndSupportedGradleVersion,
1015 ),
1016 ];
1017 for (final data in compatList) {
1018 if (isWithinVersionRange(androidPluginVersion, min: data.agpMin, max: data.agpMax)) {
1019 return data.minRequiredGradle;
1020 }
1021 }
1022 if (isWithinVersionRange(androidPluginVersion, min: maxKnownAgpVersion, max: '100.100')) {
1023 return maxKnownAndSupportedGradleVersion;
1024 }
1025 throwToolExit('Unsupported Android Plugin version: $androidPluginVersion.');
1026}
1027
1028/// Overwrite local.properties in the specified Flutter project's Android
1029/// sub-project, if needed.
1030///
1031/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
1032/// this will fail with a [ToolExit].
1033void updateLocalProperties({
1034 required FlutterProject project,
1035 BuildInfo? buildInfo,
1036 bool requireAndroidSdk = true,
1037}) {
1038 if (requireAndroidSdk && globals.androidSdk == null) {
1039 exitWithNoSdkMessage();
1040 }
1041 final File localProperties = project.android.localPropertiesFile;
1042 var changed = false;
1043
1044 SettingsFile settings;
1045 if (localProperties.existsSync()) {
1046 settings = SettingsFile.parseFromFile(localProperties);
1047 } else {
1048 settings = SettingsFile();
1049 changed = true;
1050 }
1051
1052 void changeIfNecessary(String key, String? value) {
1053 if (settings.values[key] == value) {
1054 return;
1055 }
1056 if (value == null) {
1057 settings.values.remove(key);
1058 } else {
1059 settings.values[key] = value;
1060 }
1061 changed = true;
1062 }
1063
1064 final AndroidSdk? androidSdk = globals.androidSdk;
1065 if (androidSdk != null) {
1066 changeIfNecessary('sdk.dir', globals.fsUtils.escapePath(androidSdk.directory.path));
1067 }
1068
1069 changeIfNecessary('flutter.sdk', globals.fsUtils.escapePath(Cache.flutterRoot!));
1070 if (buildInfo != null) {
1071 changeIfNecessary('flutter.buildMode', buildInfo.modeName);
1072 final String? buildName = validatedBuildNameForPlatform(
1073 TargetPlatform.android_arm,
1074 buildInfo.buildName ?? project.manifest.buildName,
1075 globals.logger,
1076 );
1077 changeIfNecessary('flutter.versionName', buildName);
1078 final String? buildNumber = validatedBuildNumberForPlatform(
1079 TargetPlatform.android_arm,
1080 buildInfo.buildNumber ?? project.manifest.buildNumber,
1081 globals.logger,
1082 );
1083 changeIfNecessary('flutter.versionCode', buildNumber);
1084 }
1085
1086 if (changed) {
1087 settings.writeContents(localProperties);
1088 }
1089}
1090
1091/// Writes standard Android local properties to the specified [properties] file.
1092///
1093/// Writes the path to the Android SDK, if known.
1094void writeLocalProperties(File properties) {
1095 final settings = SettingsFile();
1096 final AndroidSdk? androidSdk = globals.androidSdk;
1097 if (androidSdk != null) {
1098 settings.values['sdk.dir'] = globals.fsUtils.escapePath(androidSdk.directory.path);
1099 }
1100 settings.writeContents(properties);
1101}
1102
1103void exitWithNoSdkMessage() {
1104 globals.analytics.send(
1105 Event.flutterBuildInfo(
1106 label: 'unsupported-project',
1107 buildType: 'gradle',
1108 error: 'android-sdk-not-found',
1109 ),
1110 );
1111 throwToolExit(
1112 '${globals.logger.terminal.warningMark} No Android SDK found. '
1113 'Try setting the ANDROID_HOME environment variable.',
1114 );
1115}
1116
1117// Data class to hold normal/defined Java <-> Gradle compatibility criteria.
1118//
1119// The [javaMax] is exclusive in terms of supporting the noted [gradleMin],
1120// whereas [javaMin] is inclusive.
1121@immutable
1122class JavaGradleCompat {
1123 const JavaGradleCompat({
1124 required this.javaMin,
1125 required this.javaMax,
1126 required this.gradleMin,
1127 required this.gradleMax,
1128 });
1129
1130 final String javaMin;
1131 final String javaMax;
1132 final String gradleMin;
1133 final String gradleMax;
1134
1135 @override
1136 bool operator ==(Object other) =>
1137 other is JavaGradleCompat &&
1138 other.javaMin == javaMin &&
1139 other.javaMax == javaMax &&
1140 other.gradleMin == gradleMin &&
1141 other.gradleMax == gradleMax;
1142
1143 @override
1144 int get hashCode => Object.hash(javaMin, javaMax, gradleMin, gradleMax);
1145}
1146
1147// Data class to hold defined Java <-> AGP compatibility criteria.
1148//
1149// The [agpMin] and [agpMax] are inclusive in terms of having the
1150// noted [javaMin] and [javaDefault] versions.
1151@immutable
1152class JavaAgpCompat {
1153 const JavaAgpCompat({
1154 required this.javaMin,
1155 required this.javaDefault,
1156 required this.agpMin,
1157 required this.agpMax,
1158 });
1159
1160 final String javaMin;
1161 final String javaDefault;
1162 final String agpMin;
1163 final String agpMax;
1164
1165 @override
1166 bool operator ==(Object other) =>
1167 other is JavaAgpCompat &&
1168 other.javaMin == javaMin &&
1169 other.javaDefault == javaDefault &&
1170 other.agpMin == agpMin &&
1171 other.agpMax == agpMax;
1172
1173 @override
1174 int get hashCode => Object.hash(javaMin, javaDefault, agpMin, agpMax);
1175}
1176
1177class GradleForAgp {
1178 GradleForAgp({required this.agpMin, required this.agpMax, required this.minRequiredGradle});
1179
1180 final String agpMin;
1181 final String agpMax;
1182 final String minRequiredGradle;
1183}
1184
1185// Returns gradlew file name based on the platform.
1186String getGradlewFileName(Platform platform) {
1187 if (platform.isWindows) {
1188 return 'gradlew.bat';
1189 } else {
1190 return 'gradlew';
1191 }
1192}
1193
1194/// List of compatible Java/Gradle versions.
1195///
1196/// Should be updated when a new version of Java is supported by a new version
1197/// of Gradle, as https://docs.gradle.org/current/userguide/compatibility.html
1198/// details.
1199var _javaGradleCompatList = const <JavaGradleCompat>[
1200 JavaGradleCompat(
1201 javaMin: '23',
1202 javaMax: '24',
1203 gradleMin: '8.10',
1204 gradleMax: maxKnownAndSupportedGradleVersion,
1205 ),
1206 JavaGradleCompat(
1207 javaMin: '22',
1208 javaMax: '23',
1209 gradleMin: '8.7',
1210 gradleMax: maxKnownAndSupportedGradleVersion,
1211 ),
1212 JavaGradleCompat(
1213 javaMin: '21',
1214 javaMax: '22',
1215 gradleMin: '8.4',
1216 gradleMax: maxKnownAndSupportedGradleVersion,
1217 ),
1218 JavaGradleCompat(
1219 javaMin: '20',
1220 javaMax: '21',
1221 gradleMin: '8.1',
1222 gradleMax: maxKnownAndSupportedGradleVersion,
1223 ),
1224 JavaGradleCompat(
1225 javaMin: '19',
1226 javaMax: '20',
1227 gradleMin: '7.6',
1228 gradleMax: maxKnownAndSupportedGradleVersion,
1229 ),
1230 JavaGradleCompat(
1231 javaMin: '18',
1232 javaMax: '19',
1233 gradleMin: '7.5',
1234 gradleMax: maxKnownAndSupportedGradleVersion,
1235 ),
1236 JavaGradleCompat(
1237 javaMin: '17',
1238 javaMax: '18',
1239 gradleMin: '7.3',
1240 gradleMax: maxKnownAndSupportedGradleVersion,
1241 ),
1242 JavaGradleCompat(
1243 javaMin: '16',
1244 javaMax: '17',
1245 gradleMin: '7.0',
1246 gradleMax: maxKnownAndSupportedGradleVersion,
1247 ),
1248 JavaGradleCompat(
1249 javaMin: '15',
1250 javaMax: '16',
1251 gradleMin: '6.7',
1252 gradleMax: maxKnownAndSupportedGradleVersion,
1253 ),
1254 JavaGradleCompat(
1255 javaMin: '14',
1256 javaMax: '15',
1257 gradleMin: '6.3',
1258 gradleMax: maxKnownAndSupportedGradleVersion,
1259 ),
1260 JavaGradleCompat(
1261 javaMin: '13',
1262 javaMax: '14',
1263 gradleMin: '6.0',
1264 gradleMax: maxKnownAndSupportedGradleVersion,
1265 ),
1266 JavaGradleCompat(
1267 javaMin: '12',
1268 javaMax: '13',
1269 gradleMin: '5.4',
1270 gradleMax: maxKnownAndSupportedGradleVersion,
1271 ),
1272 JavaGradleCompat(
1273 javaMin: '11',
1274 javaMax: '12',
1275 gradleMin: '5.0',
1276 gradleMax: maxKnownAndSupportedGradleVersion,
1277 ),
1278 // 1.11 is a made up java version to cover everything in 1.10.*
1279 JavaGradleCompat(
1280 javaMin: '1.10',
1281 javaMax: '1.11',
1282 gradleMin: '4.7',
1283 gradleMax: maxKnownAndSupportedGradleVersion,
1284 ),
1285 JavaGradleCompat(
1286 javaMin: '1.9',
1287 javaMax: '1.10',
1288 gradleMin: '4.3',
1289 gradleMax: maxKnownAndSupportedGradleVersion,
1290 ),
1291 JavaGradleCompat(
1292 javaMin: '1.8',
1293 javaMax: '1.9',
1294 gradleMin: '2.0',
1295 gradleMax: maxKnownAndSupportedGradleVersion,
1296 ),
1297];
1298
1299// List of compatible Java/AGP versions, where agpMax versions are inclusive.
1300//
1301// Should be updated whenever a new version of AGP is released as
1302// https://developer.android.com/build/releases/gradle-plugin details.
1303var _javaAgpCompatList = const <JavaAgpCompat>[
1304 JavaAgpCompat(
1305 javaMin: '17',
1306 javaDefault: '17',
1307 agpMin: '8.0',
1308 agpMax: maxKnownAndSupportedAgpVersion,
1309 ),
1310 JavaAgpCompat(javaMin: '11', javaDefault: '11', agpMin: '7.0', agpMax: '7.4'),
1311 JavaAgpCompat(
1312 // You may use JDK 1.7 with AGP 4.2, but we treat 1.8 as the default since
1313 // it is used by default for this AGP version and lower versions of Java
1314 // are deprecated for executing Gradle.
1315 javaMin: '1.8',
1316 javaDefault: '1.8',
1317 agpMin: '4.2',
1318 agpMax: '4.2',
1319 ),
1320];
1321