| // 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.5'; |
| 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', |
| ), |
| ]; |