| // 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 org.gradle.api.JavaVersion |
| import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper |
| |
| // This buildscript block supplies dependencies for this file's own import |
| // declarations above. It exists solely for compatibility with projects that |
| // have not migrated to declaratively apply the Flutter Gradle Plugin; |
| // for those that have, FGP's `build.gradle.kts` takes care of this. |
| buildscript { |
| repositories { |
| google() |
| mavenCentral() |
| } |
| dependencies { |
| // When bumping, also update: |
| // * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy |
| // * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy |
| // * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart |
| // * AGP version in dependencies block in packages/flutter_tools/gradle/build.gradle.kts |
| classpath("com.android.tools.build:gradle:7.3.0") |
| classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") |
| } |
| } |
| |
| apply<FlutterDependencyCheckerPlugin>() |
| |
| class FlutterDependencyCheckerPlugin : Plugin<Project> { |
| override fun apply(project: Project) { |
| DependencyVersionChecker.checkDependencyVersions(project) |
| } |
| } |
| |
| class DependencyVersionChecker { |
| companion object { |
| private const val GRADLE_NAME: String = "Gradle" |
| private const val JAVA_NAME: String = "Java" |
| private const val AGP_NAME: String = "Android Gradle Plugin" |
| private const val KGP_NAME: String = "Kotlin" |
| |
| // The following messages represent best effort guesses at where a Flutter developer should |
| // look to upgrade a dependency that is below the corresponding threshold. Developers can |
| // change some of these locations, so they are not guaranteed to be accurate. |
| private fun getPotentialGradleFix(projectDirectory: String): String { |
| return "Your project's gradle version is typically " + |
| "defined in the gradle wrapper file. By default, this can be found at " + |
| "$projectDirectory/gradle/wrapper/gradle-wrapper.properties. \n" + |
| "For more information, see https://docs.gradle.org/current/userguide/gradle_wrapper.html.\n" |
| } |
| |
| // The potential java fix does not make use of the project directory, |
| // so it left as a constant. |
| private const val POTENTIAL_JAVA_FIX: String = |
| "The Java version used by Flutter can be " + |
| "set with `flutter config --jdk-dir=<path>`. \nFor more information about how Flutter " + |
| "chooses which version of Java to use, see the --jdk-dir section of the " + |
| "output of `flutter config -h`.\n" |
| |
| private fun getPotentialAGPFix(projectDirectory: String): String { |
| return "Your project's AGP version is typically " + |
| "defined the plugins block of the `settings.gradle` file " + |
| "($projectDirectory/settings.gradle), by a plugin with the id of " + |
| "com.android.application. \nIf you don't see a plugins block, your project " + |
| "was likely created with an older template version. In this case it is most " + |
| "likely defined in the top-level build.gradle file " + |
| "($projectDirectory/build.gradle) by the following line in the dependencies" + |
| " block of the buildscript: \"classpath 'com.android.tools.build:gradle:<version>'\".\n" |
| } |
| |
| private fun getPotentialKGPFix(projectDirectory: String): String { |
| return "Your project's KGP version is typically " + |
| "defined the plugins block of the `settings.gradle` file " + |
| "($projectDirectory/settings.gradle), by a plugin with the id of " + |
| "org.jetbrains.kotlin.android. \nIf you don't see a plugins block, your project " + |
| "was likely created with an older template version, in which case it is most " + |
| "likely defined in the top-level build.gradle file " + |
| "($projectDirectory/build.gradle) by the ext.kotlin_version property.\n" |
| } |
| |
| // The following versions define our support policy for Gradle, Java, AGP, and KGP. |
| // All "error" versions are currently set to 0 as this policy is new. They will be increased |
| // to match the current values of the "warn" versions in the next release. |
| // Before updating any "error" version, ensure that you have updated the corresponding |
| // "warn" version for a full release to provide advanced warning. See |
| // flutter.dev/go/android-dependency-versions for more. |
| // TODO(gmackall): https://github.com/flutter/flutter/issues/142653. |
| val warnGradleVersion: Version = Version(7, 0, 2) |
| val errorGradleVersion: Version = Version(0, 0, 0) |
| |
| val warnJavaVersion: JavaVersion = JavaVersion.VERSION_11 |
| val errorJavaVersion: JavaVersion = JavaVersion.VERSION_1_1 |
| |
| val warnAGPVersion: Version = Version(7, 0, 0) |
| val errorAGPVersion: Version = Version(0, 0, 0) |
| |
| val warnKGPVersion: Version = Version(1, 5, 0) |
| val errorKGPVersion: Version = Version(0, 0, 0) |
| |
| /** |
| * Checks if the project's Android build time dependencies are each within the respective |
| * version range that we support. When we can't find a version for a given dependency |
| * we treat it as within the range for the purpose of this check. |
| */ |
| fun checkDependencyVersions(project: Project) { |
| var agpVersion: Version? = null |
| var kgpVersion: Version? = null |
| |
| checkGradleVersion(getGradleVersion(project), project) |
| checkJavaVersion(getJavaVersion(project), project) |
| agpVersion = getAGPVersion(project) |
| if (agpVersion != null) { |
| checkAGPVersion(agpVersion, project) |
| } else { |
| project.logger.error( |
| "Warning: unable to detect project AGP version. Skipping " + |
| "version checking. \nThis may be because you have applied AGP after the Flutter Gradle Plugin." |
| ) |
| } |
| |
| kgpVersion = getKGPVersion(project) |
| if (kgpVersion != null) { |
| checkKGPVersion(kgpVersion, project) |
| } else { |
| project.logger.error( |
| "Warning: unable to detect project KGP version. Skipping " + |
| "version checking. \nThis may be because you have applied KGP after the Flutter Gradle Plugin." |
| ) |
| } |
| } |
| |
| // https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.invocation/-gradle/index.html#-837060600%2FFunctions%2F-1793262594 |
| fun getGradleVersion(project: Project): Version { |
| val untrimmedGradleVersion: String = project.gradle.getGradleVersion() |
| // Trim to handle candidate gradle versions (example 7.6-rc-4). This means we treat all |
| // candidate versions of gradle as the same as their base version |
| // (i.e., "7.6"="7.6-rc-4"). |
| return Version.fromString(untrimmedGradleVersion.substringBefore('-')) |
| } |
| |
| // https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api/-java-version/index.html#-1790786897%2FFunctions%2F-1793262594 |
| fun getJavaVersion(project: Project): JavaVersion { |
| return JavaVersion.current() |
| } |
| |
| // This approach is taken from AGP's own version checking plugin: |
| // https://android.googlesource.com/platform/tools/base/+/1839aa23b8dc562005e2f0f0cc8e8b4c5caa37d0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/agpVersionChecker.kt#58. |
| fun getAGPVersion(project: Project): Version? { |
| val agpPluginName: String = "com.android.base" |
| val agpVersionFieldName: String = "ANDROID_GRADLE_PLUGIN_VERSION" |
| var agpVersion: Version? = null |
| try { |
| agpVersion = |
| Version.fromString( |
| project.plugins.getPlugin(agpPluginName)::class.java.classLoader.loadClass( |
| com.android.Version::class.java.name |
| ).fields.find { it.name == agpVersionFieldName }!! |
| .get(null) as String |
| ) |
| } catch (ignored: ClassNotFoundException) { |
| // Use deprecated Version class as it exists in older AGP (com.android.Version) does |
| // not exist in those versions. |
| agpVersion = |
| Version.fromString( |
| project.plugins.getPlugin(agpPluginName)::class.java.classLoader.loadClass( |
| com.android.builder.model.Version::class.java.name |
| ).fields.find { it.name == agpVersionFieldName }!! |
| .get(null) as String |
| ) |
| } |
| return agpVersion |
| } |
| |
| fun getKGPVersion(project: Project): Version? { |
| val kotlinVersionProperty: String = "kotlin_version" |
| val firstKotlinVersionFieldName: String = "pluginVersion" |
| val secondKotlinVersionFieldName: String = "kotlinPluginVersion" |
| // This property corresponds to application of the Kotlin Gradle plugin in the |
| // top-level build.gradle file. |
| if (project.hasProperty(kotlinVersionProperty)) { |
| return Version.fromString(project.properties.get(kotlinVersionProperty) as String) |
| } |
| val kotlinPlugin = |
| project.getPlugins() |
| .findPlugin(KotlinAndroidPluginWrapper::class.java) |
| val versionfield = |
| kotlinPlugin?.javaClass?.kotlin?.members?.first { |
| it.name == firstKotlinVersionFieldName || it.name == secondKotlinVersionFieldName |
| } |
| val versionString = versionfield?.call(kotlinPlugin) |
| if (versionString == null) { |
| return null |
| } else { |
| return Version.fromString(versionString!! as String) |
| } |
| } |
| |
| private fun getErrorMessage( |
| dependencyName: String, |
| versionString: String, |
| errorVersion: String, |
| potentialFix: String |
| ): String { |
| return "Error: Your project's $dependencyName version ($versionString) is lower " + |
| "than Flutter's minimum supported version of $errorVersion. Please upgrade " + |
| "your $dependencyName version. \nAlternatively, use the flag " + |
| "\"--android-skip-build-dependency-validation\" to bypass this check.\n\n" + |
| "Potential fix: $potentialFix" |
| } |
| |
| private fun getWarnMessage( |
| dependencyName: String, |
| versionString: String, |
| warnVersion: String, |
| potentialFix: String |
| ): String { |
| return "Warning: Flutter support for your project's $dependencyName version " + |
| "($versionString) will soon be dropped. Please upgrade your $dependencyName " + |
| "version to a version of at least $warnVersion soon." + |
| "\nAlternatively, use the flag \"--android-skip-build-dependency-validation\"" + |
| " to bypass this check.\n\nPotential fix: $potentialFix" |
| } |
| |
| fun checkGradleVersion( |
| version: Version, |
| project: Project |
| ) { |
| if (version < errorGradleVersion) { |
| val errorMessage: String = |
| getErrorMessage( |
| GRADLE_NAME, |
| version.toString(), |
| errorGradleVersion.toString(), |
| getPotentialGradleFix(project.getRootDir().getPath()) |
| ) |
| throw GradleException(errorMessage) |
| } else if (version < warnGradleVersion) { |
| val warnMessage: String = |
| getWarnMessage( |
| GRADLE_NAME, |
| version.toString(), |
| warnGradleVersion.toString(), |
| getPotentialGradleFix(project.getRootDir().getPath()) |
| ) |
| project.logger.error(warnMessage) |
| } |
| } |
| |
| fun checkJavaVersion( |
| version: JavaVersion, |
| project: Project |
| ) { |
| if (version < errorJavaVersion) { |
| val errorMessage: String = |
| getErrorMessage( |
| JAVA_NAME, |
| version.toString(), |
| errorJavaVersion.toString(), |
| POTENTIAL_JAVA_FIX |
| ) |
| throw GradleException(errorMessage) |
| } else if (version < warnJavaVersion) { |
| val warnMessage: String = |
| getWarnMessage( |
| JAVA_NAME, |
| version.toString(), |
| warnJavaVersion.toString(), |
| POTENTIAL_JAVA_FIX |
| ) |
| project.logger.error(warnMessage) |
| } |
| } |
| |
| fun checkAGPVersion( |
| version: Version, |
| project: Project |
| ) { |
| if (version < errorAGPVersion) { |
| val errorMessage: String = |
| getErrorMessage( |
| AGP_NAME, |
| version.toString(), |
| errorAGPVersion.toString(), |
| getPotentialAGPFix(project.getRootDir().getPath()) |
| ) |
| throw GradleException(errorMessage) |
| } else if (version < warnAGPVersion) { |
| val warnMessage: String = |
| getWarnMessage( |
| AGP_NAME, |
| version.toString(), |
| warnAGPVersion.toString(), |
| getPotentialAGPFix(project.getRootDir().getPath()) |
| ) |
| project.logger.error(warnMessage) |
| } |
| } |
| |
| fun checkKGPVersion( |
| version: Version, |
| project: Project |
| ) { |
| if (version < errorKGPVersion) { |
| val errorMessage: String = |
| getErrorMessage( |
| KGP_NAME, |
| version.toString(), |
| errorKGPVersion.toString(), |
| getPotentialKGPFix(project.getRootDir().getPath()) |
| ) |
| throw GradleException(errorMessage) |
| } else if (version < warnKGPVersion) { |
| val warnMessage: String = |
| getWarnMessage( |
| KGP_NAME, |
| version.toString(), |
| warnKGPVersion.toString(), |
| getPotentialKGPFix(project.getRootDir().getPath()) |
| ) |
| project.logger.error(warnMessage) |
| } |
| } |
| } |
| } |
| |
| // Helper class to parse the versions that are provided as plain strings (Gradle, Kotlin) and |
| // perform easy comparisons. All versions will have a major, minor, and patch value. These values |
| // default to 0 when they are not provided or are otherwise unparseable. |
| // For example the version strings "8.2", "8.2.2hfd", and "8.2.0" would parse to the same version. |
| class Version(val major: Int, val minor: Int, val patch: Int) : Comparable<Version> { |
| companion object { |
| fun fromString(version: String): Version { |
| val asList: List<String> = version.split(".") |
| val convertedToNumbers: List<Int> = asList.map { it.toIntOrNull() ?: 0 } |
| return Version( |
| major = convertedToNumbers.getOrElse(0, { 0 }), |
| minor = convertedToNumbers.getOrElse(1, { 0 }), |
| patch = convertedToNumbers.getOrElse(2, { 0 }) |
| ) |
| } |
| } |
| |
| override fun compareTo(otherVersion: Version): Int { |
| if (major != otherVersion.major) { |
| return major - otherVersion.major |
| } |
| if (minor != otherVersion.minor) { |
| return minor - otherVersion.minor |
| } |
| if (patch != otherVersion.patch) { |
| return patch - otherVersion.patch |
| } |
| return 0 |
| } |
| |
| override fun toString(): String { |
| return major.toString() + "." + minor.toString() + "." + patch.toString() |
| } |
| } |