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