Convert dependency version checker to Kotlin source (#162771)
Converts the `dependency_version_checker.kts` script to a Kotlin source class. Also adds unit tests that depend heavily on mocking, because from what I can tell you can't create an instance of a Gradle `org.gradle.api.Project` for testing (so I can't pass a real `Project` to `DependencyVersionChecker.checkDependencyVersions()`). Perhaps functionality for unit testing will come in the future: https://docs.gradle.org/current/userguide/test_kit.html > At this time, it is focused on functional testing. That is, testing build logic by exercising it as part of a programmatically executed build. Over time, the TestKit will likely expand to facilitate other kinds of tests. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Gray Mackall <mackall@google.com> Co-authored-by: Reid Baker <hamilton.reid.baker@gmail.com>
This commit is contained in:
parent
fecbcbd367
commit
c3fc69d18d
@ -34,3 +34,6 @@ e.g `./gradlew test --tests "com.flutter.gradle.BaseApplicationNameHandlerTest.s
|
|||||||
|
|
||||||
Sometimes changing a test name and then running it will cause an IDE error. To get Android Studio back
|
Sometimes changing a test name and then running it will cause an IDE error. To get Android Studio back
|
||||||
to a good state on Mac, run `Help > "Repair IDE"`, and then in the popup window `"Rescan project indexes > Everything works now."`
|
to a good state on Mac, run `Help > "Repair IDE"`, and then in the popup window `"Rescan project indexes > Everything works now."`
|
||||||
|
|
||||||
|
To add a new test, add a class under `src/test/kotlin`, with methods annotated with `@Test`.
|
||||||
|
These tests will get automatically run on CI by `packages/flutter_tools/test/integration.shard/android_run_flutter_gradle_plugin_tests_test.dart`.
|
||||||
|
@ -59,6 +59,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compileOnly("androidx.annotation:annotation-jvm:1.9.1")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
|
||||||
// When bumping, also update:
|
// When bumping, also update:
|
||||||
// * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
|
// * 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/kotlin_scripts/dependency_version_checker.gradle.kts
|
// * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/kotlin_scripts/dependency_version_checker.gradle.kts
|
||||||
@ -68,4 +70,5 @@ dependencies {
|
|||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation("com.android.tools.build:gradle:8.7.3")
|
testImplementation("com.android.tools.build:gradle:8.7.3")
|
||||||
testImplementation("org.mockito:mockito-core:4.8.0")
|
testImplementation("org.mockito:mockito-core:4.8.0")
|
||||||
|
testImplementation("io.mockk:mockk:1.13.16")
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import com.android.build.OutputFile
|
import com.android.build.OutputFile
|
||||||
import com.flutter.gradle.BaseApplicationNameHandler
|
import com.flutter.gradle.BaseApplicationNameHandler
|
||||||
import com.flutter.gradle.Deeplink
|
import com.flutter.gradle.Deeplink
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker
|
||||||
import com.flutter.gradle.IntentFilterCheck
|
import com.flutter.gradle.IntentFilterCheck
|
||||||
import groovy.json.JsonGenerator
|
import groovy.json.JsonGenerator
|
||||||
import groovy.xml.QName
|
import groovy.xml.QName
|
||||||
@ -300,10 +301,7 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
final Boolean shouldSkipDependencyChecks = project.hasProperty("skipDependencyChecks") && project.getProperty("skipDependencyChecks")
|
final Boolean shouldSkipDependencyChecks = project.hasProperty("skipDependencyChecks") && project.getProperty("skipDependencyChecks")
|
||||||
if (!shouldSkipDependencyChecks) {
|
if (!shouldSkipDependencyChecks) {
|
||||||
try {
|
try {
|
||||||
final String dependencyCheckerPluginPath = Paths.get(flutterRoot.absolutePath,
|
DependencyVersionChecker.checkDependencyVersions(project)
|
||||||
"packages", "flutter_tools", "gradle", "src", "main", "kotlin_scripts",
|
|
||||||
"dependency_version_checker.gradle.kts")
|
|
||||||
project.apply from: dependencyCheckerPluginPath
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (!project.hasProperty("usesUnsupportedDependencyVersions") || !project.usesUnsupportedDependencyVersions) {
|
if (!project.hasProperty("usesUnsupportedDependencyVersions") || !project.usesUnsupportedDependencyVersions) {
|
||||||
// Possible bug in dependency checking code - warn and do not block build.
|
// Possible bug in dependency checking code - warn and do not block build.
|
||||||
|
@ -0,0 +1,331 @@
|
|||||||
|
package com.flutter.gradle
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.build.api.AndroidPluginVersion
|
||||||
|
import com.android.build.api.variant.AndroidComponentsExtension
|
||||||
|
import org.gradle.api.JavaVersion
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.kotlin.dsl.extra
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
|
||||||
|
|
||||||
|
object DependencyVersionChecker {
|
||||||
|
@VisibleForTesting internal const val GRADLE_NAME: String = "Gradle"
|
||||||
|
|
||||||
|
@VisibleForTesting internal const val JAVA_NAME: String = "Java"
|
||||||
|
|
||||||
|
@VisibleForTesting internal const val AGP_NAME: String = "Android Gradle Plugin"
|
||||||
|
|
||||||
|
@VisibleForTesting internal const val KGP_NAME: String = "Kotlin"
|
||||||
|
|
||||||
|
// String constant that defines the name of the Gradle extra property that we set when
|
||||||
|
// detecting that the project is using versions outside of Flutter's support range.
|
||||||
|
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api/-project/index.html#-2107180640%2FProperties%2F-1867656071.
|
||||||
|
@VisibleForTesting internal const val OUT_OF_SUPPORT_RANGE_PROPERTY = "usesUnsupportedDependencyVersions"
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
@VisibleForTesting internal 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.
|
||||||
|
@VisibleForTesting internal 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"
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun getPotentialAGPFix(projectDirectory: String): String {
|
||||||
|
return "Your project's AGP version is typically " +
|
||||||
|
"defined in 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun getPotentialKGPFix(projectDirectory: String): String {
|
||||||
|
return "Your project's KGP version is typically " +
|
||||||
|
"defined in 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.
|
||||||
|
// 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.
|
||||||
|
@VisibleForTesting internal val warnGradleVersion: Version = Version(7, 4, 2)
|
||||||
|
|
||||||
|
@VisibleForTesting internal val errorGradleVersion: Version = Version(7, 0, 2)
|
||||||
|
|
||||||
|
@VisibleForTesting internal val warnJavaVersion: JavaVersion = JavaVersion.VERSION_11
|
||||||
|
|
||||||
|
@VisibleForTesting internal val errorJavaVersion: JavaVersion = JavaVersion.VERSION_1_1
|
||||||
|
|
||||||
|
@VisibleForTesting internal val warnAGPVersion: AndroidPluginVersion = AndroidPluginVersion(7, 3, 1)
|
||||||
|
|
||||||
|
@VisibleForTesting internal val errorAGPVersion: AndroidPluginVersion = AndroidPluginVersion(7, 0, 0)
|
||||||
|
|
||||||
|
@VisibleForTesting internal val warnKGPVersion: Version = Version(1, 8, 10)
|
||||||
|
|
||||||
|
@VisibleForTesting internal val errorKGPVersion: Version = Version(1, 7, 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.
|
||||||
|
*/
|
||||||
|
@JvmStatic fun checkDependencyVersions(project: Project) {
|
||||||
|
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false)
|
||||||
|
|
||||||
|
checkGradleVersion(getGradleVersion(project), project)
|
||||||
|
checkJavaVersion(getJavaVersion(), project)
|
||||||
|
val agpVersion: AndroidPluginVersion? = 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."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val kgpVersion: Version? = getKGPVersion(project)
|
||||||
|
if (kgpVersion != null) {
|
||||||
|
checkKGPVersion(kgpVersion, project)
|
||||||
|
}
|
||||||
|
// KGP is not required, so don't log any warning if we can't find the version.
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.invocation/-gradle/index.html#-837060600%2FFunctions%2F-1793262594
|
||||||
|
@VisibleForTesting internal fun getGradleVersion(project: Project): Version {
|
||||||
|
val untrimmedGradleVersion: String = project.gradle.gradleVersion
|
||||||
|
// 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
|
||||||
|
@VisibleForTesting internal fun getJavaVersion(): JavaVersion {
|
||||||
|
return JavaVersion.current()
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun getAGPVersion(project: Project): AndroidPluginVersion? {
|
||||||
|
val androidPluginVersion: AndroidPluginVersion? =
|
||||||
|
project.extensions.findByType(
|
||||||
|
AndroidComponentsExtension::class.java
|
||||||
|
)?.pluginVersion
|
||||||
|
return androidPluginVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gmackall): AGP has a getKotlinAndroidPluginVersion(), and KGP has a
|
||||||
|
// getKotlinPluginVersion(). Consider replacing this implementation with one of
|
||||||
|
// those.
|
||||||
|
@VisibleForTesting internal fun getKGPVersion(project: Project): Version? {
|
||||||
|
val kotlinVersionProperty = "kotlin_version"
|
||||||
|
val firstKotlinVersionFieldName = "pluginVersion"
|
||||||
|
val secondKotlinVersionFieldName = "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[kotlinVersionProperty] as String)
|
||||||
|
}
|
||||||
|
val kotlinPlugin =
|
||||||
|
project.plugins
|
||||||
|
.findPlugin(KotlinAndroidPluginWrapper::class.java)
|
||||||
|
val versionField =
|
||||||
|
kotlinPlugin?.javaClass?.kotlin?.members?.first {
|
||||||
|
it.name == firstKotlinVersionFieldName || it.name == secondKotlinVersionFieldName
|
||||||
|
}
|
||||||
|
val versionString = versionField?.call(kotlinPlugin)
|
||||||
|
return if (versionString == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
Version.fromString(versionString as String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun checkGradleVersion(
|
||||||
|
version: Version,
|
||||||
|
project: Project
|
||||||
|
) {
|
||||||
|
if (version < errorGradleVersion) {
|
||||||
|
val errorMessage: String =
|
||||||
|
getErrorMessage(
|
||||||
|
GRADLE_NAME,
|
||||||
|
version.toString(),
|
||||||
|
errorGradleVersion.toString(),
|
||||||
|
getPotentialGradleFix(project.rootDir.path)
|
||||||
|
)
|
||||||
|
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
||||||
|
throw DependencyValidationException(errorMessage)
|
||||||
|
} else if (version < warnGradleVersion) {
|
||||||
|
val warnMessage: String =
|
||||||
|
getWarnMessage(
|
||||||
|
GRADLE_NAME,
|
||||||
|
version.toString(),
|
||||||
|
warnGradleVersion.toString(),
|
||||||
|
getPotentialGradleFix(project.rootDir.path)
|
||||||
|
)
|
||||||
|
project.logger.error(warnMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun checkJavaVersion(
|
||||||
|
version: JavaVersion,
|
||||||
|
project: Project
|
||||||
|
) {
|
||||||
|
if (version < errorJavaVersion) {
|
||||||
|
val errorMessage: String =
|
||||||
|
getErrorMessage(
|
||||||
|
JAVA_NAME,
|
||||||
|
version.toString(),
|
||||||
|
errorJavaVersion.toString(),
|
||||||
|
POTENTIAL_JAVA_FIX
|
||||||
|
)
|
||||||
|
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
||||||
|
throw DependencyValidationException(errorMessage)
|
||||||
|
} else if (version < warnJavaVersion) {
|
||||||
|
val warnMessage: String =
|
||||||
|
getWarnMessage(
|
||||||
|
JAVA_NAME,
|
||||||
|
version.toString(),
|
||||||
|
warnJavaVersion.toString(),
|
||||||
|
POTENTIAL_JAVA_FIX
|
||||||
|
)
|
||||||
|
project.logger.error(warnMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun checkAGPVersion(
|
||||||
|
androidPluginVersion: AndroidPluginVersion,
|
||||||
|
project: Project
|
||||||
|
) {
|
||||||
|
if (androidPluginVersion < errorAGPVersion) {
|
||||||
|
val errorMessage: String =
|
||||||
|
getErrorMessage(
|
||||||
|
AGP_NAME,
|
||||||
|
androidPluginVersion.toString(),
|
||||||
|
errorAGPVersion.toString(),
|
||||||
|
getPotentialAGPFix(project.rootDir.path)
|
||||||
|
)
|
||||||
|
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
||||||
|
throw DependencyValidationException(errorMessage)
|
||||||
|
} else if (androidPluginVersion < warnAGPVersion) {
|
||||||
|
val warnMessage: String =
|
||||||
|
getWarnMessage(
|
||||||
|
AGP_NAME,
|
||||||
|
androidPluginVersion.toString(),
|
||||||
|
warnAGPVersion.toString(),
|
||||||
|
getPotentialAGPFix(project.rootDir.path)
|
||||||
|
)
|
||||||
|
project.logger.error(warnMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting internal fun checkKGPVersion(
|
||||||
|
version: Version,
|
||||||
|
project: Project
|
||||||
|
) {
|
||||||
|
if (version < errorKGPVersion) {
|
||||||
|
val errorMessage: String =
|
||||||
|
getErrorMessage(
|
||||||
|
KGP_NAME,
|
||||||
|
version.toString(),
|
||||||
|
errorKGPVersion.toString(),
|
||||||
|
getPotentialKGPFix(project.rootDir.path)
|
||||||
|
)
|
||||||
|
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
||||||
|
throw DependencyValidationException(errorMessage)
|
||||||
|
} else if (version < warnKGPVersion) {
|
||||||
|
val warnMessage: String =
|
||||||
|
getWarnMessage(
|
||||||
|
KGP_NAME,
|
||||||
|
version.toString(),
|
||||||
|
warnKGPVersion.toString(),
|
||||||
|
getPotentialKGPFix(project.rootDir.path)
|
||||||
|
)
|
||||||
|
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.
|
||||||
|
internal 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(other: Version): Int {
|
||||||
|
if (major != other.major) {
|
||||||
|
return major - other.major
|
||||||
|
}
|
||||||
|
if (minor != other.minor) {
|
||||||
|
return minor - other.minor
|
||||||
|
}
|
||||||
|
if (patch != other.patch) {
|
||||||
|
return patch - other.patch
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$major.$minor.$patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom error for when the dependency_version_checker.kts script finds a dependency out of
|
||||||
|
// the defined support range.
|
||||||
|
@VisibleForTesting internal class DependencyValidationException(
|
||||||
|
message: String? = null,
|
||||||
|
cause: Throwable? = null
|
||||||
|
) : Exception(message, cause)
|
@ -1,370 +0,0 @@
|
|||||||
// 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"
|
|
||||||
|
|
||||||
// String constant that defines the name of the Gradle extra property that we set when
|
|
||||||
// detecting that the project is using versions outside of Flutter's support range.
|
|
||||||
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api/-project/index.html#-2107180640%2FProperties%2F-1867656071.
|
|
||||||
private const val OUT_OF_SUPPORT_RANGE_PROPERTY = "usesUnsupportedDependencyVersions"
|
|
||||||
|
|
||||||
// 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 in 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 in 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.
|
|
||||||
// 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.
|
|
||||||
val warnGradleVersion: Version = Version(7, 4, 2)
|
|
||||||
val errorGradleVersion: Version = Version(7, 0, 2)
|
|
||||||
|
|
||||||
val warnJavaVersion: JavaVersion = JavaVersion.VERSION_11
|
|
||||||
val errorJavaVersion: JavaVersion = JavaVersion.VERSION_1_1
|
|
||||||
|
|
||||||
val warnAGPVersion: Version = Version(7, 3, 1)
|
|
||||||
val errorAGPVersion: Version = Version(7, 0, 0)
|
|
||||||
|
|
||||||
val warnKGPVersion: Version = Version(1, 8, 10)
|
|
||||||
val errorKGPVersion: Version = Version(1, 7, 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) {
|
|
||||||
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false)
|
|
||||||
var agpVersion: Version?
|
|
||||||
var kgpVersion: Version?
|
|
||||||
|
|
||||||
checkGradleVersion(getGradleVersion(project), project)
|
|
||||||
checkJavaVersion(getJavaVersion(), 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)
|
|
||||||
}
|
|
||||||
// KGP is not required, so don't log any warning if we can't find the version.
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(): 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?
|
|
||||||
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.
|
|
||||||
@Suppress("deprecation")
|
|
||||||
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())
|
|
||||||
)
|
|
||||||
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
|
||||||
throw DependencyValidationException(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
|
|
||||||
)
|
|
||||||
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
|
||||||
throw DependencyValidationException(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())
|
|
||||||
)
|
|
||||||
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
|
||||||
throw DependencyValidationException(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())
|
|
||||||
)
|
|
||||||
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true)
|
|
||||||
throw DependencyValidationException(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(other: Version): Int {
|
|
||||||
if (major != other.major) {
|
|
||||||
return major - other.major
|
|
||||||
}
|
|
||||||
if (minor != other.minor) {
|
|
||||||
return minor - other.minor
|
|
||||||
}
|
|
||||||
if (patch != other.patch) {
|
|
||||||
return patch - other.patch
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return major.toString() + "." + minor.toString() + "." + patch.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom error for when the dependency_version_checker.kts script finds a dependency out of
|
|
||||||
// the defined support range.
|
|
||||||
class DependencyValidationException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
|
||||||
constructor(cause: Throwable) : this(null, cause)
|
|
||||||
}
|
|
@ -0,0 +1,259 @@
|
|||||||
|
package com.flutter.gradle
|
||||||
|
|
||||||
|
import com.android.build.api.AndroidPluginVersion
|
||||||
|
import com.android.build.api.variant.AndroidComponentsExtension
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.AGP_NAME
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.GRADLE_NAME
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.JAVA_NAME
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.KGP_NAME
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.OUT_OF_SUPPORT_RANGE_PROPERTY
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.POTENTIAL_JAVA_FIX
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.errorAGPVersion
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.errorGradleVersion
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.errorKGPVersion
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.getErrorMessage
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.getPotentialAGPFix
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.getPotentialGradleFix
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.getPotentialKGPFix
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.getWarnMessage
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.warnAGPVersion
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.warnGradleVersion
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.warnJavaVersion
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker.warnKGPVersion
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.gradle.api.JavaVersion
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.logging.Logger
|
||||||
|
import org.gradle.api.plugins.ExtraPropertiesExtension
|
||||||
|
import org.gradle.internal.extensions.core.extra
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
const val FAKE_PROJECT_ROOT_DIR = "/fake/root/dir"
|
||||||
|
|
||||||
|
// The following values will need to be modified when the corresponding "warn$DepName" versions
|
||||||
|
// are updated in DependencyVersionChecker.kt
|
||||||
|
const val SUPPORTED_GRADLE_VERSION: String = "7.4.2"
|
||||||
|
val SUPPORTED_JAVA_VERSION: JavaVersion = JavaVersion.VERSION_11
|
||||||
|
val SUPPORTED_AGP_VERSION: AndroidPluginVersion = AndroidPluginVersion(7, 3, 1)
|
||||||
|
const val SUPPORTED_KGP_VERSION: String = "1.8.10"
|
||||||
|
|
||||||
|
class DependencyVersionCheckerTest {
|
||||||
|
@Test
|
||||||
|
fun `AGP version in error range results in DependencyValidationException`() {
|
||||||
|
val exampleErrorAgpVersion = AndroidPluginVersion(4, 2, 0)
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(agpVersion = exampleErrorAgpVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(any(), any()) } returns Unit
|
||||||
|
|
||||||
|
val dependencyValidationException =
|
||||||
|
assertFailsWith<DependencyValidationException> { DependencyVersionChecker.checkDependencyVersions(mockProject) }
|
||||||
|
assert(
|
||||||
|
dependencyValidationException.message ==
|
||||||
|
getErrorMessage(
|
||||||
|
AGP_NAME,
|
||||||
|
exampleErrorAgpVersion.toString(),
|
||||||
|
errorAGPVersion.toString(),
|
||||||
|
getPotentialAGPFix(FAKE_PROJECT_ROOT_DIR)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
verify { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `AGP version in warn range results in warning logs`() {
|
||||||
|
val exampleWarnAgpVersion = AndroidPluginVersion(7, 1, 0)
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(agpVersion = exampleWarnAgpVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false) } returns Unit
|
||||||
|
val mockLogger = mockProject.logger
|
||||||
|
every { mockLogger.error(any()) } returns Unit
|
||||||
|
|
||||||
|
DependencyVersionChecker.checkDependencyVersions(mockProject)
|
||||||
|
verify {
|
||||||
|
mockLogger.error(
|
||||||
|
getWarnMessage(
|
||||||
|
AGP_NAME,
|
||||||
|
exampleWarnAgpVersion.toString(),
|
||||||
|
warnAGPVersion.toString(),
|
||||||
|
getPotentialAGPFix(FAKE_PROJECT_ROOT_DIR)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify(exactly = 0) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `KGP version in error range results in DependencyValidationException`() {
|
||||||
|
val exampleErrorKgpVersion = "1.6.0"
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(kgpVersion = exampleErrorKgpVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(any(), any()) } returns Unit
|
||||||
|
|
||||||
|
val dependencyValidationException =
|
||||||
|
assertFailsWith<DependencyValidationException> { DependencyVersionChecker.checkDependencyVersions(mockProject) }
|
||||||
|
|
||||||
|
println(dependencyValidationException.message)
|
||||||
|
assert(
|
||||||
|
dependencyValidationException.message ==
|
||||||
|
getErrorMessage(
|
||||||
|
KGP_NAME,
|
||||||
|
exampleErrorKgpVersion,
|
||||||
|
errorKGPVersion.toString(),
|
||||||
|
getPotentialKGPFix(FAKE_PROJECT_ROOT_DIR)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
verify { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `KGP version in warn range results in warning logs`() {
|
||||||
|
val exampleWarnKgpVersion = "1.8.0"
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(kgpVersion = exampleWarnKgpVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false) } returns Unit
|
||||||
|
val mockLogger = mockProject.logger
|
||||||
|
every { mockLogger.error(any()) } returns Unit
|
||||||
|
|
||||||
|
DependencyVersionChecker.checkDependencyVersions(mockProject)
|
||||||
|
verify {
|
||||||
|
mockLogger.error(
|
||||||
|
getWarnMessage(
|
||||||
|
KGP_NAME,
|
||||||
|
exampleWarnKgpVersion,
|
||||||
|
warnKGPVersion.toString(),
|
||||||
|
getPotentialKGPFix(FAKE_PROJECT_ROOT_DIR)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify(exactly = 0) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// No test for Java version in error range, as the lowest supported Java version is also the
|
||||||
|
// lowest possible.
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Java version in warn range results in warning logs`() {
|
||||||
|
val exampleWarnJavaVersion = JavaVersion.VERSION_1_8
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(javaVersion = exampleWarnJavaVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false) } returns Unit
|
||||||
|
val mockLogger = mockProject.logger
|
||||||
|
every { mockLogger.error(any()) } returns Unit
|
||||||
|
|
||||||
|
DependencyVersionChecker.checkDependencyVersions(mockProject)
|
||||||
|
verify {
|
||||||
|
mockLogger.error(
|
||||||
|
getWarnMessage(
|
||||||
|
JAVA_NAME,
|
||||||
|
exampleWarnJavaVersion.toString(),
|
||||||
|
warnJavaVersion.toString(),
|
||||||
|
POTENTIAL_JAVA_FIX
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify(exactly = 0) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Gradle version in error range results in DependencyValidationException`() {
|
||||||
|
val exampleErrorGradleVersion = "7.0.0"
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(gradleVersion = exampleErrorGradleVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(any(), any()) } returns Unit
|
||||||
|
|
||||||
|
val dependencyValidationException =
|
||||||
|
assertFailsWith<DependencyValidationException> { DependencyVersionChecker.checkDependencyVersions(mockProject) }
|
||||||
|
|
||||||
|
assert(
|
||||||
|
dependencyValidationException.message ==
|
||||||
|
getErrorMessage(
|
||||||
|
GRADLE_NAME,
|
||||||
|
exampleErrorGradleVersion,
|
||||||
|
errorGradleVersion.toString(),
|
||||||
|
getPotentialGradleFix(FAKE_PROJECT_ROOT_DIR)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
verify { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Gradle version in warn range results in warning logs`() {
|
||||||
|
val exampleWarnGradleVersion = "7.4.0"
|
||||||
|
val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(gradleVersion = exampleWarnGradleVersion)
|
||||||
|
|
||||||
|
val mockExtraPropertiesExtension = mockProject.extra
|
||||||
|
every { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false) } returns Unit
|
||||||
|
val mockLogger = mockProject.logger
|
||||||
|
every { mockLogger.error(any()) } returns Unit
|
||||||
|
|
||||||
|
DependencyVersionChecker.checkDependencyVersions(mockProject)
|
||||||
|
verify {
|
||||||
|
mockLogger.error(
|
||||||
|
getWarnMessage(
|
||||||
|
GRADLE_NAME,
|
||||||
|
exampleWarnGradleVersion,
|
||||||
|
warnGradleVersion.toString(),
|
||||||
|
getPotentialGradleFix(FAKE_PROJECT_ROOT_DIR)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify(exactly = 0) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There isn't a way to create a real org.gradle.api.Project object for testing unfortunately, so
|
||||||
|
// these tests rely heavily on mocking.
|
||||||
|
//
|
||||||
|
// TODO(gmackall): We should consider adding functional tests built on top of a testing framework
|
||||||
|
// perhaps like
|
||||||
|
// https://github.com/autonomousapps/dependency-analysis-gradle-plugin/tree/main/testkit
|
||||||
|
// as a way to fill this gap in testing (combined with moving functionality to individual tasks
|
||||||
|
// that can be tested independently).
|
||||||
|
object MockProjectFactory {
|
||||||
|
fun createMockProjectWithSpecifiedDependencyVersions(
|
||||||
|
javaVersion: JavaVersion = SUPPORTED_JAVA_VERSION,
|
||||||
|
gradleVersion: String = SUPPORTED_GRADLE_VERSION,
|
||||||
|
agpVersion: AndroidPluginVersion = SUPPORTED_AGP_VERSION,
|
||||||
|
kgpVersion: String = SUPPORTED_KGP_VERSION
|
||||||
|
): Project {
|
||||||
|
// Java
|
||||||
|
mockkStatic(JavaVersion::class)
|
||||||
|
every { JavaVersion.current() } returns javaVersion
|
||||||
|
|
||||||
|
// Gradle
|
||||||
|
val mockProject = mockk<Project>()
|
||||||
|
every { mockProject.gradle.gradleVersion } returns gradleVersion
|
||||||
|
|
||||||
|
// AGP
|
||||||
|
val mockAndroidComponentsExtension = mockk<AndroidComponentsExtension<*, *, *>>()
|
||||||
|
every { mockProject.extensions.findByType(AndroidComponentsExtension::class.java) } returns mockAndroidComponentsExtension
|
||||||
|
every { mockAndroidComponentsExtension.pluginVersion } returns agpVersion
|
||||||
|
|
||||||
|
// KGP
|
||||||
|
every { mockProject.hasProperty(eq("kotlin_version")) } returns true
|
||||||
|
every { mockProject.properties["kotlin_version"] } returns kgpVersion
|
||||||
|
|
||||||
|
// Logger
|
||||||
|
val mockLogger = mockk<Logger>()
|
||||||
|
every { mockProject.logger } returns mockLogger
|
||||||
|
|
||||||
|
// Extra properties extension
|
||||||
|
val mockExtraPropertiesExtension = mockk<ExtraPropertiesExtension>()
|
||||||
|
every { mockProject.extra } returns mockExtraPropertiesExtension
|
||||||
|
|
||||||
|
// Project path
|
||||||
|
every { mockProject.rootDir.path } returns FAKE_PROJECT_ROOT_DIR
|
||||||
|
|
||||||
|
return mockProject
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user