Camille Simon 1fa54ea90a
[Android] Bump template & integration test Gradle version to 7.6.4 (#139276)
Updates Gradle version for Flutter project templates and integration tests to at least 7.6.3 (changed all of those with versions below it) to fix security vulnerability.

Part of fix for https://github.com/flutter/flutter/issues/138336.
2023-12-07 18:31:20 +00:00

975 lines
34 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../base/utils.dart';
import '../base/version.dart';
import '../base/version_range.dart';
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../reporting/reporting.dart';
import 'android_sdk.dart';
// These are the versions used in the project templates.
//
// In general, Flutter aims to default to the latest version.
// However, this currently requires to migrate existing integration tests to the latest supported values.
//
// Please see the README before changing any of these values.
const String templateDefaultGradleVersion = '7.6.3';
const String templateAndroidGradlePluginVersion = '7.3.0';
const String templateAndroidGradlePluginVersionForModule = '7.3.0';
const String templateKotlinGradlePluginVersion = '1.7.10';
// The Flutter Gradle Plugin is only applied to app projects, and modules that
// are built from source using (`include_flutter.groovy`). The remaining
// projects are: plugins, and modules compiled as AARs. In modules, the
// ephemeral directory `.android` is always regenerated after `flutter pub get`,
// so new versions are picked up after a Flutter upgrade.
//
// Please see the README before changing any of these values.
const String compileSdkVersion = '34';
const String minSdkVersion = '19';
const String targetSdkVersion = '33';
const String ndkVersion = '23.1.7779620';
// Update these when new major versions of Java are supported by new Gradle
// versions that we support.
// Source of truth: https://docs.gradle.org/current/userguide/compatibility.html
const String oneMajorVersionHigherJavaVersion = '20';
// Update this when new versions of Gradle come out including minor versions
// and should correspond to the maximum Gradle version we test in CI.
//
// Supported here means supported by the tooling for
// flutter analyze --suggestions and does not imply broader flutter support.
const String maxKnownAndSupportedGradleVersion = '8.0.2';
// Update this when new versions of AGP come out.
//
// Supported here means tooling is aware of this version's Java <-> AGP
// compatibility.
@visibleForTesting
const String maxKnownAndSupportedAgpVersion = '8.1';
// Update this when new versions of AGP come out.
const String maxKnownAgpVersion = '8.3';
// Oldest documented version of AGP that has a listed minimum
// compatible Java version.
const String oldestDocumentedJavaAgpCompatibilityVersion = '4.2';
// Constant used in [_buildAndroidGradlePluginRegExp] and
// [_settingsAndroidGradlePluginRegExp] to identify the version section.
const String _versionGroupName = 'version';
// AGP can be defined in build.gradle
// Expected content:
// "classpath 'com.android.tools.build:gradle:7.3.0'"
// ?<version> is used to name the version group which helps with extraction.
final RegExp _buildAndroidGradlePluginRegExp =
RegExp(r'com\.android\.tools\.build:gradle:(?<version>\d+\.\d+\.\d+)');
// AGP can be defined in settings.gradle.
// Expected content:
// "id "com.android.application" version "{{agpVersion}}""
// ?<version> is used to name the version group which helps with extraction.
final RegExp _settingsAndroidGradlePluginRegExp = RegExp(
r'^\s+id\s+"com.android.application"\s+version\s+"(?<version>\d+\.\d+\.\d+)"',
multiLine: true);
// Expected content format (with lines above and below).
// Version can have 2 or 3 numbers.
// 'distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip'
// '^\s*' protects against commented out lines.
final RegExp distributionUrlRegex =
RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true);
// Modified version of the gradle distribution url match designed to only match
// gradle.org urls so that we can guarantee any modifications to the url
// still points to a hosted zip.
final RegExp gradleOrgVersionMatch =
RegExp(
r'^\s*distributionUrl\s*=\s*https\\://services\.gradle\.org/distributions/gradle-((?:\d|\.)+)-(.*)\.zip',
multiLine: true
);
// This matches uncommented minSdkVersion lines in the module-level build.gradle
// file which have minSdkVersion 16,17, or 18 (the Jelly Bean api levels).
final RegExp jellyBeanMinSdkVersionMatch =
RegExp(r'(?<=^\s*)minSdkVersion 1[678](?=\s*(?://|$))', multiLine: true);
// From https://docs.gradle.org/current/userguide/command_line_interface.html#command_line_interface
const String gradleVersionFlag = r'--version';
// Directory under android/ that gradle uses to store gradle information.
// Regularly used with [gradleWrapperDirectory] and
// [gradleWrapperPropertiesFilename].
// Different from the directory of gradle files stored in
// `_cache.getArtifactDirectory('gradle_wrapper')`
const String gradleDirectoryName = 'gradle';
const String gradleWrapperDirectoryName = 'wrapper';
const String gradleWrapperPropertiesFilename = 'gradle-wrapper.properties';
/// Provides utilities to run a Gradle task, such as finding the Gradle executable
/// or constructing a Gradle project.
class GradleUtils {
GradleUtils({
required Platform platform,
required Logger logger,
required Cache cache,
required OperatingSystemUtils operatingSystemUtils,
}) : _platform = platform,
_logger = logger,
_cache = cache,
_operatingSystemUtils = operatingSystemUtils;
final Cache _cache;
final Platform _platform;
final Logger _logger;
final OperatingSystemUtils _operatingSystemUtils;
/// Gets the Gradle executable path and prepares the Gradle project.
/// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
String getExecutable(FlutterProject project) {
final Directory androidDir = project.android.hostAppGradleRoot;
injectGradleWrapperIfNeeded(androidDir);
final File gradle = androidDir.childFile(getGradlewFileName(_platform));
if (gradle.existsSync()) {
_logger.printTrace('Using gradle from ${gradle.absolute.path}.');
// If the Gradle executable doesn't have execute permission,
// then attempt to set it.
_operatingSystemUtils.makeExecutable(gradle);
return gradle.absolute.path;
}
throwToolExit(
'Unable to locate gradlew script. Please check that ${gradle.path} '
'exists or that ${gradle.dirname} can be read.');
}
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
void injectGradleWrapperIfNeeded(Directory directory) {
copyDirectory(
_cache.getArtifactDirectory('gradle_wrapper'),
directory,
shouldCopyFile: (File sourceFile, File destinationFile) {
// Don't override the existing files in the project.
return !destinationFile.existsSync();
},
onFileCopied: (File source, File dest) {
_operatingSystemUtils.makeExecutable(dest);
}
);
// Add the `gradle-wrapper.properties` file if it doesn't exist.
final Directory propertiesDirectory = directory
.childDirectory(gradleDirectoryName)
.childDirectory(gradleWrapperDirectoryName);
final File propertiesFile =
propertiesDirectory.childFile(gradleWrapperPropertiesFilename);
if (propertiesFile.existsSync()) {
return;
}
propertiesDirectory.createSync(recursive: true);
final String gradleVersion =
getGradleVersionForAndroidPlugin(directory, _logger);
final String propertyContents = '''
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersion-all.zip
''';
propertiesFile.writeAsStringSync(propertyContents);
}
}
/// Returns the Gradle version that the current Android plugin depends on when found,
/// otherwise it returns a default version.
///
/// The Android plugin version is specified in the [build.gradle] file within
/// the project's Android directory.
String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
final File buildFile = directory.childFile('build.gradle');
if (!buildFile.existsSync()) {
logger.printTrace(
"$buildFile doesn't exist, assuming Gradle version: $templateDefaultGradleVersion");
return templateDefaultGradleVersion;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> pluginMatches = _buildAndroidGradlePluginRegExp.allMatches(buildFileContent);
if (pluginMatches.isEmpty) {
logger.printTrace("$buildFile doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion");
return templateDefaultGradleVersion;
}
final String? androidPluginVersion = pluginMatches.first.group(1);
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
return getGradleVersionFor(androidPluginVersion ?? 'unknown');
}
/// Returns the gradle file from the top level directory.
/// The returned file is not guaranteed to be present.
File getGradleWrapperFile(Directory directory) {
return directory.childDirectory(gradleDirectoryName)
.childDirectory(gradleWrapperDirectoryName)
.childFile(gradleWrapperPropertiesFilename);
}
/// Parses the gradle wrapper distribution url to return a string containing
/// the version number.
///
/// Expected input is of the form '...gradle-7.4.2-all.zip', and the output
/// would be of the form '7.4.2'.
String? parseGradleVersionFromDistributionUrl(String? distributionUrl) {
if (distributionUrl == null) {
return null;
}
final List<String> zipParts = distributionUrl.split('-');
if (zipParts.length < 2) {
return null;
}
return zipParts[1];
}
/// Returns either the gradle-wrapper.properties value from the passed in
/// [directory] or if not present the version available in local path.
///
/// If gradle version is not found null is returned.
/// [directory] should be an android directory with a build.gradle file.
Future<String?> getGradleVersion(
Directory directory, Logger logger, ProcessManager processManager) async {
final File propertiesFile = getGradleWrapperFile(directory);
if (propertiesFile.existsSync()) {
final String wrapperFileContent = propertiesFile.readAsStringSync();
final RegExpMatch? distributionUrl =
distributionUrlRegex.firstMatch(wrapperFileContent);
if (distributionUrl != null) {
final String? gradleVersion =
parseGradleVersionFromDistributionUrl(distributionUrl.group(0));
if (gradleVersion != null) {
return gradleVersion;
} else {
// Did not find gradle zip url. Likely this is a bug in our parsing.
logger.printWarning(_formatParseWarning(wrapperFileContent));
}
} else {
// If no distributionUrl log then treat as if there was no propertiesFile.
logger.printTrace(
'$propertiesFile does not provide a Gradle version falling back to system gradle.');
}
} else {
// Could not find properties file.
logger.printTrace(
'$propertiesFile does not exist falling back to system gradle');
}
// System installed Gradle version.
if (processManager.canRun('gradle')) {
final String gradleVersionVerbose =
(await processManager.run(<String>['gradle', gradleVersionFlag])).stdout
as String;
// Expected format:
/*
------------------------------------------------------------
Gradle 7.6
------------------------------------------------------------
Build time: 2022-11-25 13:35:10 UTC
Revision: daece9dbc5b79370cc8e4fd6fe4b2cd400e150a8
Kotlin: 1.7.10
Groovy: 3.0.13
Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM: 17.0.6 (Homebrew 17.0.6+0)
OS: Mac OS X 13.2.1 aarch64
*/
// Observation shows that the version can have 2 or 3 numbers.
// Inner parentheticals `(\.\d+)?` denote the optional third value.
// Outer parentheticals `Gradle (...)` denote a grouping used to extract
// the version number.
final RegExp gradleVersionRegex = RegExp(r'Gradle\s+(\d+\.\d+(?:\.\d+)?)');
final RegExpMatch? version =
gradleVersionRegex.firstMatch(gradleVersionVerbose);
if (version == null) {
// Most likely a bug in our parse implementation/regex.
logger.printWarning(_formatParseWarning(gradleVersionVerbose));
return null;
}
return version.group(1);
} else {
logger.printTrace('Could not run system gradle');
return null;
}
}
/// Returns the Android Gradle Plugin (AGP) version that the current project
/// depends on when found, null otherwise.
///
/// The Android plugin version is specified in the [build.gradle] or
/// [settings.gradle] file within the project's
/// Android directory ([androidDirectory]).
String? getAgpVersion(Directory androidDirectory, Logger logger) {
final File buildFile = androidDirectory.childFile('build.gradle');
if (!buildFile.existsSync()) {
logger.printTrace('Can not find build.gradle in $androidDirectory');
return null;
}
final String buildFileContent = buildFile.readAsStringSync();
final RegExpMatch? buildMatch =
_buildAndroidGradlePluginRegExp.firstMatch(buildFileContent);
if (buildMatch != null) {
final String? androidPluginVersion =
buildMatch.namedGroup(_versionGroupName);
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
return androidPluginVersion;
}
logger.printTrace(
"$buildFile doesn't provide an AGP version. Checking settings.");
final File settingsFile = androidDirectory.childFile('settings.gradle');
if (!settingsFile.existsSync()) {
logger.printTrace('$settingsFile does not exist.');
return null;
}
final String settingsFileContent = settingsFile.readAsStringSync();
final RegExpMatch? settingsMatch =
_settingsAndroidGradlePluginRegExp.firstMatch(settingsFileContent);
if (settingsMatch != null) {
final String? androidPluginVersion =
settingsMatch.namedGroup(_versionGroupName);
logger.printTrace(
'$settingsFile provides AGP version: $androidPluginVersion');
return androidPluginVersion;
}
logger.printTrace("$settingsFile doesn't provide an AGP version.");
return null;
}
String _formatParseWarning(String content) {
return 'Could not parse gradle version from: \n'
'$content \n'
'If there is a version please look for an existing bug '
'https://github.com/flutter/flutter/issues/'
' and if one does not exist file a new issue.';
}
// Validate that Gradle version and AGP are compatible with each other.
//
// Returns true if versions are compatible.
// Null Gradle version or AGP version returns false.
// If compatibility can not be evaluated returns false.
// If versions are newer than the max known version a warning is logged and true
// returned.
//
// Source of truth found here:
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
// AGP has a minimum version of gradle required but no max starting at
// AGP version 2.3.0+.
bool validateGradleAndAgp(Logger logger,
{required String? gradleV, required String? agpV}) {
const String oldestSupportedAgpVersion = '3.3.0';
const String oldestSupportedGradleVersion = '4.10.1';
if (gradleV == null || agpV == null) {
logger
.printTrace('Gradle version or AGP version unknown ($gradleV, $agpV).');
return false;
}
// First check if versions are too old.
if (isWithinVersionRange(agpV,
min: '0.0', max: oldestSupportedAgpVersion, inclusiveMax: false)) {
logger.printTrace('AGP Version: $agpV is too old.');
return false;
}
if (isWithinVersionRange(gradleV,
min: '0.0', max: oldestSupportedGradleVersion, inclusiveMax: false)) {
logger.printTrace('Gradle Version: $gradleV is too old.');
return false;
}
// Check highest supported version before checking unknown versions.
if (isWithinVersionRange(agpV, min: '8.0', max: maxKnownAndSupportedAgpVersion)) {
return isWithinVersionRange(gradleV,
min: '8.0', max: maxKnownAndSupportedGradleVersion);
}
// Check if versions are newer than the max known versions.
if (isWithinVersionRange(agpV,
min: maxKnownAndSupportedAgpVersion, max: '100.100')) {
// Assume versions we do not know about are valid but log.
final bool validGradle =
isWithinVersionRange(gradleV, min: '8.0', max: '100.00');
logger.printTrace('Newer than known AGP version ($agpV), gradle ($gradleV).'
'\n Treating as valid configuration.');
return validGradle;
}
// Begin Known Gradle <-> AGP validation.
// Max agp here is a made up version to contain all 7.4 changes.
if (isWithinVersionRange(agpV, min: '7.4', max: '7.5')) {
return isWithinVersionRange(gradleV,
min: '7.5', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '7.3', max: '7.4', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '7.4', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '7.2', max: '7.3', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '7.3.3', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '7.1', max: '7.2', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '7.2', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '7.0', max: '7.1', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '7.0', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '4.2.0', max: '7.0', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '6.7.1', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '4.1.0', max: '4.2.0', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '6.5', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(agpV,
min: '4.0.0', max: '4.1.0', inclusiveMax: false)) {
return isWithinVersionRange(gradleV,
min: '6.1.1', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(
agpV,
min: '3.6.0',
max: '3.6.4',
)) {
return isWithinVersionRange(gradleV,
min: '5.6.4', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(
agpV,
min: '3.5.0',
max: '3.5.4',
)) {
return isWithinVersionRange(gradleV,
min: '5.4.1', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(
agpV,
min: '3.4.0',
max: '3.4.3',
)) {
return isWithinVersionRange(gradleV,
min: '5.1.1', max: maxKnownAndSupportedGradleVersion);
}
if (isWithinVersionRange(
agpV,
min: '3.3.0',
max: '3.3.3',
)) {
return isWithinVersionRange(gradleV,
min: '4.10.1', max: maxKnownAndSupportedGradleVersion);
}
logger.printTrace('Unknown Gradle-Agp compatibility, $gradleV, $agpV');
return false;
}
/// Validate that the [javaVersion] and Gradle version are compatible with
/// each other.
///
/// Source of truth:
/// https://docs.gradle.org/current/userguide/compatibility.html#java
bool validateJavaAndGradle(Logger logger,
{required String? javaV, required String? gradleV}) {
// https://docs.gradle.org/current/userguide/compatibility.html#java
const String oldestSupportedJavaVersion = '1.8';
const String oldestDocumentedJavaGradleCompatibility = '2.0';
// Begin Java <-> Gradle validation.
if (javaV == null || gradleV == null) {
logger.printTrace(
'Java version or Gradle version unknown ($javaV, $gradleV).');
return false;
}
// First check if versions are too old.
if (isWithinVersionRange(javaV,
min: '1.1', max: oldestSupportedJavaVersion, inclusiveMax: false)) {
logger.printTrace('Java Version: $javaV is too old.');
return false;
}
if (isWithinVersionRange(gradleV,
min: '0.0', max: oldestDocumentedJavaGradleCompatibility, inclusiveMax: false)) {
logger.printTrace('Gradle Version: $gradleV is too old.');
return false;
}
// Check if versions are newer than the max supported versions.
if (isWithinVersionRange(
javaV,
min: oneMajorVersionHigherJavaVersion,
max: '100.100',
)) {
// Assume versions Java versions newer than [maxSupportedJavaVersion]
// required a higher gradle version.
final bool validGradle = isWithinVersionRange(gradleV,
min: maxKnownAndSupportedGradleVersion, max: '100.00');
logger.printWarning(
'Newer than known valid Java version ($javaV), gradle ($gradleV).'
'\n Treating as valid configuration.');
return validGradle;
}
// Begin known Java <-> Gradle evaluation.
for (final JavaGradleCompat data in _javaGradleCompatList) {
if (isWithinVersionRange(javaV, min: data.javaMin, max: data.javaMax, inclusiveMax: false)) {
return isWithinVersionRange(gradleV, min: data.gradleMin, max: data.gradleMax);
}
}
logger.printTrace('Unknown Java-Gradle compatibility $javaV, $gradleV');
return false;
}
/// Returns compatibility information for the valid range of Gradle versions for
/// the specified Java version.
///
/// Returns null when the tooling has not documented the compatibile Gradle
/// versions for the Java version (either the version is too old or too new). If
/// this seems like a mistake, the caller may need to update the
/// [_javaGradleCompatList] detailing Java/Gradle compatibility.
JavaGradleCompat? getValidGradleVersionRangeForJavaVersion(
Logger logger, {
required String javaV,
}) {
for (final JavaGradleCompat data in _javaGradleCompatList) {
if (isWithinVersionRange(javaV, min: data.javaMin, max: data.javaMax, inclusiveMax: false)) {
return data;
}
}
logger.printTrace('Unable to determine valid Gradle version range for Java version $javaV.');
return null;
}
/// Validate that the specified Java and Android Gradle Plugin (AGP) versions are
/// compatible with each other.
///
/// Returns true when the specified Java and AGP versions are
/// definitely compatible; otherwise, false is assumed by default. In addition,
/// this will return false when either a null Java or AGP version is provided.
///
/// Source of truth are the AGP release notes:
/// https://developer.android.com/build/releases/gradle-plugin
bool validateJavaAndAgp(Logger logger,
{required String? javaV, required String? agpV}) {
if (javaV == null || agpV == null) {
logger.printTrace(
'Java version or AGP version unknown ($javaV, $agpV).');
return false;
}
// Check if AGP version is too old to perform validation.
if (isWithinVersionRange(agpV,
min: '1.0', max: oldestDocumentedJavaAgpCompatibilityVersion, inclusiveMax: false)) {
logger.printTrace('AGP Version: $agpV is too old to determine Java compatibility.');
return false;
}
if (isWithinVersionRange(agpV,
min: maxKnownAndSupportedAgpVersion, max: '100.100', inclusiveMin: false)) {
logger.printTrace('AGP Version: $agpV is too new to determine Java compatibility.');
return false;
}
// Begin known Java <-> AGP evaluation.
for (final JavaAgpCompat data in _javaAgpCompatList) {
if (isWithinVersionRange(agpV, min: data.agpMin, max: data.agpMax)) {
return isWithinVersionRange(javaV, min: data.javaMin, max: '100.100');
}
}
logger.printTrace('Unknown Java-AGP compatibility $javaV, $agpV');
return false;
}
/// Returns compatibility information concerning the minimum AGP
/// version for the specified Java version.
JavaAgpCompat? getMinimumAgpVersionForJavaVersion(Logger logger,
{required String javaV}) {
for (final JavaAgpCompat data in _javaAgpCompatList) {
if (isWithinVersionRange(javaV, min: data.javaMin, max: '100.100')) {
return data;
}
}
logger.printTrace('Unable to determine minimum AGP version for specified Java version.');
return null;
}
/// Returns valid Java range for specified Gradle and AGP verisons.
///
/// Assumes that gradleV and agpV are compatible versions.
VersionRange getJavaVersionFor({required String gradleV, required String agpV}) {
// Find minimum Java version based on AGP compatibility.
String? minJavaVersion;
for (final JavaAgpCompat data in _javaAgpCompatList) {
if (isWithinVersionRange(agpV, min: data.agpMin, max: data.agpMax)) {
minJavaVersion = data.javaMin;
}
}
// Find maximum Java version based on Gradle compatibility.
String? maxJavaVersion;
for (final JavaGradleCompat data in _javaGradleCompatList.reversed) {
if (isWithinVersionRange(gradleV, min: data.gradleMin, max: maxKnownAndSupportedGradleVersion)) {
maxJavaVersion = data.javaMax;
}
}
return VersionRange(minJavaVersion, maxJavaVersion);
}
/// Returns the Gradle version that is required by the given Android Gradle plugin version
/// by picking the largest compatible version from
/// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
String getGradleVersionFor(String androidPluginVersion) {
final List<GradleForAgp> compatList = <GradleForAgp> [
GradleForAgp(agpMin: '1.0.0', agpMax: '1.1.3', minRequiredGradle: '2.3'),
GradleForAgp(agpMin: '1.2.0', agpMax: '1.3.1', minRequiredGradle: '2.9'),
GradleForAgp(agpMin: '1.5.0', agpMax: '1.5.0', minRequiredGradle: '2.2.1'),
GradleForAgp(agpMin: '2.0.0', agpMax: '2.1.2', minRequiredGradle: '2.13'),
GradleForAgp(agpMin: '2.1.3', agpMax: '2.2.3', minRequiredGradle: '2.14.1'),
GradleForAgp(agpMin: '2.3.0', agpMax: '2.9.9', minRequiredGradle: '3.3'),
GradleForAgp(agpMin: '3.0.0', agpMax: '3.0.9', minRequiredGradle: '4.1'),
GradleForAgp(agpMin: '3.1.0', agpMax: '3.1.9', minRequiredGradle: '4.4'),
GradleForAgp(agpMin: '3.2.0', agpMax: '3.2.1', minRequiredGradle: '4.6'),
GradleForAgp(agpMin: '3.3.0', agpMax: '3.3.2', minRequiredGradle: '4.10.2'),
GradleForAgp(agpMin: '3.4.0', agpMax: '3.5.0', minRequiredGradle: '5.6.2'),
GradleForAgp(agpMin: '4.0.0', agpMax: '4.1.0', minRequiredGradle: '6.7'),
// 7.5 is a made up value to include everything through 7.4.*
GradleForAgp(agpMin: '7.0.0', agpMax: '7.5', minRequiredGradle: '7.5'),
GradleForAgp(agpMin: '7.5.0', agpMax: '100.100', minRequiredGradle: '8.0'),
// Assume if AGP is newer than this code know about return the highest gradle
// version we know about.
GradleForAgp(agpMin: maxKnownAgpVersion, agpMax: maxKnownAgpVersion, minRequiredGradle: maxKnownAndSupportedGradleVersion),
];
for (final GradleForAgp data in compatList) {
if (isWithinVersionRange(androidPluginVersion, min: data.agpMin, max: data.agpMax)) {
return data.minRequiredGradle;
}
}
if (isWithinVersionRange(androidPluginVersion, min: maxKnownAgpVersion, max: '100.100')) {
return maxKnownAndSupportedGradleVersion;
}
throwToolExit('Unsupported Android Plugin version: $androidPluginVersion.');
}
/// Overwrite local.properties in the specified Flutter project's Android
/// sub-project, if needed.
///
/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
/// this will fail with a [ToolExit].
void updateLocalProperties({
required FlutterProject project,
BuildInfo? buildInfo,
bool requireAndroidSdk = true,
}) {
if (requireAndroidSdk && globals.androidSdk == null) {
exitWithNoSdkMessage();
}
final File localProperties = project.android.localPropertiesFile;
bool changed = false;
SettingsFile settings;
if (localProperties.existsSync()) {
settings = SettingsFile.parseFromFile(localProperties);
} else {
settings = SettingsFile();
changed = true;
}
void changeIfNecessary(String key, String? value) {
if (settings.values[key] == value) {
return;
}
if (value == null) {
settings.values.remove(key);
} else {
settings.values[key] = value;
}
changed = true;
}
final AndroidSdk? androidSdk = globals.androidSdk;
if (androidSdk != null) {
changeIfNecessary('sdk.dir', globals.fsUtils.escapePath(androidSdk.directory.path));
}
changeIfNecessary('flutter.sdk', globals.fsUtils.escapePath(Cache.flutterRoot!));
if (buildInfo != null) {
changeIfNecessary('flutter.buildMode', buildInfo.modeName);
final String? buildName = validatedBuildNameForPlatform(
TargetPlatform.android_arm,
buildInfo.buildName ?? project.manifest.buildName,
globals.logger,
);
changeIfNecessary('flutter.versionName', buildName);
final String? buildNumber = validatedBuildNumberForPlatform(
TargetPlatform.android_arm,
buildInfo.buildNumber ?? project.manifest.buildNumber,
globals.logger,
);
changeIfNecessary('flutter.versionCode', buildNumber);
}
if (changed) {
settings.writeContents(localProperties);
}
}
/// Writes standard Android local properties to the specified [properties] file.
///
/// Writes the path to the Android SDK, if known.
void writeLocalProperties(File properties) {
final SettingsFile settings = SettingsFile();
final AndroidSdk? androidSdk = globals.androidSdk;
if (androidSdk != null) {
settings.values['sdk.dir'] = globals.fsUtils.escapePath(androidSdk.directory.path);
}
settings.writeContents(properties);
}
void exitWithNoSdkMessage() {
BuildEvent('unsupported-project',
type: 'gradle',
eventError: 'android-sdk-not-found',
flutterUsage: globals.flutterUsage)
.send();
globals.analytics.send(Event.flutterBuildInfo(
label: 'unsupported-project',
buildType: 'gradle',
error: 'android-sdk-not-found',
));
throwToolExit('${globals.logger.terminal.warningMark} No Android SDK found. '
'Try setting the ANDROID_HOME environment variable.');
}
// Data class to hold normal/defined Java <-> Gradle compatability criteria.
//
// The [javaMax] is exclusive in terms of supporting the noted [gradleMin],
// whereas [javaMin] is inclusive.
@immutable
class JavaGradleCompat {
const JavaGradleCompat({
required this.javaMin,
required this.javaMax,
required this.gradleMin,
required this.gradleMax,
});
final String javaMin;
final String javaMax;
final String gradleMin;
final String gradleMax;
@override
bool operator ==(Object other) =>
other is JavaGradleCompat &&
other.javaMin == javaMin &&
other.javaMax == javaMax &&
other.gradleMin == gradleMin &&
other.gradleMax == gradleMax;
@override
int get hashCode => Object.hash(javaMin, javaMax, gradleMin, gradleMax);
}
// Data class to hold defined Java <-> AGP compatibility criteria.
//
// The [agpMin] and [agpMax] are inclusive in terms of having the
// noted [javaMin] and [javaDefault] versions.
@immutable
class JavaAgpCompat {
const JavaAgpCompat({
required this.javaMin,
required this.javaDefault,
required this.agpMin,
required this.agpMax,
});
final String javaMin;
final String javaDefault;
final String agpMin;
final String agpMax;
@override
bool operator ==(Object other) =>
other is JavaAgpCompat &&
other.javaMin == javaMin &&
other.javaDefault == javaDefault &&
other.agpMin == agpMin &&
other.agpMax == agpMax;
@override
int get hashCode => Object.hash(javaMin, javaDefault, agpMin, agpMax);
}
class GradleForAgp {
GradleForAgp({
required this.agpMin,
required this.agpMax,
required this.minRequiredGradle,
});
final String agpMin;
final String agpMax;
final String minRequiredGradle;
}
// Returns gradlew file name based on the platform.
String getGradlewFileName(Platform platform) {
if (platform.isWindows) {
return 'gradlew.bat';
} else {
return 'gradlew';
}
}
/// List of compatible Java/Gradle versions.
///
/// Should be updated when a new version of Java is supported by a new version
/// of Gradle, as https://docs.gradle.org/current/userguide/compatibility.html
/// details.
List<JavaGradleCompat> _javaGradleCompatList = const <JavaGradleCompat>[
JavaGradleCompat(
javaMin: '19',
javaMax: '20',
gradleMin: '7.6',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '18',
javaMax: '19',
gradleMin: '7.5',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '17',
javaMax: '18',
gradleMin: '7.3',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '16',
javaMax: '17',
gradleMin: '7.0',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '15',
javaMax: '16',
gradleMin: '6.7',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '14',
javaMax: '15',
gradleMin: '6.3',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '13',
javaMax: '14',
gradleMin: '6.0',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '12',
javaMax: '13',
gradleMin: '5.4',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '11',
javaMax: '12',
gradleMin: '5.0',
gradleMax: maxKnownAndSupportedGradleVersion,
),
// 1.11 is a made up java version to cover everything in 1.10.*
JavaGradleCompat(
javaMin: '1.10',
javaMax: '1.11',
gradleMin: '4.7',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '1.9',
javaMax: '1.10',
gradleMin: '4.3',
gradleMax: maxKnownAndSupportedGradleVersion,
),
JavaGradleCompat(
javaMin: '1.8',
javaMax: '1.9',
gradleMin: '2.0',
gradleMax: maxKnownAndSupportedGradleVersion,
),
];
// List of compatible Java/AGP versions, where agpMax versions are inclusive.
//
// Should be updated whenever a new version of AGP is released as
// https://developer.android.com/build/releases/gradle-plugin details.
List<JavaAgpCompat> _javaAgpCompatList = const <JavaAgpCompat>[
JavaAgpCompat(
javaMin: '17',
javaDefault: '17',
agpMin: '8.0',
agpMax: maxKnownAndSupportedAgpVersion,
),
JavaAgpCompat(
javaMin: '11',
javaDefault: '11',
agpMin: '7.0',
agpMax: '7.4',
),
JavaAgpCompat(
// You may use JDK 1.7 with AGP 4.2, but we treat 1.8 as the default since
// it is used by default for this AGP version and lower versions of Java
// are deprecated for executing Gradle.
javaMin: '1.8',
javaDefault: '1.8',
agpMin: '4.2',
agpMax: '4.2',
),
];