Add kotlin compatability to build file validation (#167143)

Fixes https://github.com/flutter/flutter/issues/161443 

* adds a new gradle task like `javaVersion` named `kgpVersion` that
prints the version of kgp.
* adds gradle_utils.dart method for getting kgp version
* * kgp method is moved to utilities and we attempt to use a plugin
method before reflection.
* adds methods or  evaluating KGP + gradle and KGP + AGP compatibility. 
* * It turns out that we have been using incompatible versions that
happen to work with the subset of kotlin we are using.
* Uses new kgp methods in flutter analyze --suggestions as part of the
existing agp/java/gradle compatibility matrix.
* adds new tests for all new functionality
* Adds comments to sections of the code I found could use them. 
* Modifies flutter gallery to use a compatible version of kotlin and
update its lockfiles.


## 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.
This commit is contained in:
Reid Baker 2025-04-18 16:32:08 +00:00 committed by GitHub
parent d203008741
commit f20bc39cc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1015 additions and 172 deletions

View File

@ -113,35 +113,30 @@ org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath
org.glassfish.jaxb:txw2:2.3.2=classpath
org.jdom:jdom2:2.0.6=classpath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath
org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin:1.8.10=classpath
org.jetbrains.kotlin:kotlin-android-extensions:1.8.10=classpath
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.8.10=classpath
org.jetbrains.kotlin:kotlin-build-common:1.8.10=classpath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.10=classpath
org.jetbrains.kotlin:kotlin-compiler-runner:1.8.10=classpath
org.jetbrains.kotlin:kotlin-daemon-client:1.8.10=classpath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.8.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.8.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:1.8.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.8.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.8.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10=classpath
org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.8.10=classpath
org.jetbrains.kotlin:kotlin-native-utils:1.8.10=classpath
org.jetbrains.kotlin:kotlin-project-model:1.8.10=classpath
org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin:2.1.10=classpath
org.jetbrains.kotlin:kotlin-build-statistics:2.1.10=classpath
org.jetbrains.kotlin:kotlin-build-tools-api:2.1.10=classpath
org.jetbrains.kotlin:kotlin-compiler-runner:2.1.10=classpath
org.jetbrains.kotlin:kotlin-daemon-client:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-annotations:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-api:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-idea:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-model:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugins-bom:2.1.10=classpath
org.jetbrains.kotlin:kotlin-klib-commonizer-api:2.1.10=classpath
org.jetbrains.kotlin:kotlin-native-utils:2.1.10=classpath
org.jetbrains.kotlin:kotlin-reflect:1.9.20=classpath
org.jetbrains.kotlin:kotlin-scripting-common:1.8.10=classpath
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.8.10=classpath
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.8.10=classpath
org.jetbrains.kotlin:kotlin-scripting-jvm:1.8.10=classpath
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=classpath
org.jetbrains.kotlin:kotlin-stdlib:1.9.20=classpath
org.jetbrains.kotlin:kotlin-tooling-core:1.8.10=classpath
org.jetbrains.kotlin:kotlin-util-io:1.8.10=classpath
org.jetbrains.kotlin:kotlin-util-klib:1.8.10=classpath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath
org.jetbrains.kotlin:kotlin-tooling-core:2.1.10=classpath
org.jetbrains.kotlin:kotlin-util-io:2.1.10=classpath
org.jetbrains.kotlin:kotlin-util-klib-metadata:2.1.10=classpath
org.jetbrains.kotlin:kotlin-util-klib:2.1.10=classpath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=classpath
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.4.0=classpath
org.jetbrains.kotlinx:kotlinx-serialization-core:1.4.0=classpath
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.4.0=classpath

View File

@ -111,8 +111,8 @@ javax.annotation:javax.annotation-api:1.3.2=_internal-unified-test-platform-andr
javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
net.bytebuddy:byte-buddy:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.14.10=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
net.bytebuddy:byte-buddy:1.14.10=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
net.java.dev.jna:jna-platform:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle
net.java.dev.jna:jna:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle
net.sf.kxml:kxml2:2.3.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
@ -144,8 +144,7 @@ org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4=_internal-unified-test-platf
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
org.jetbrains:annotations:13.0=_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle
org.jetbrains:annotations:23.0.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
org.mockito:mockito-core:5.1.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
org.mockito:mockito-inline:5.1.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
org.mockito:mockito-core:5.8.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
org.objenesis:objenesis:3.3=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath
org.ow2.asm:asm-analysis:9.1=androidJacocoAnt
org.ow2.asm:asm-commons:9.1=androidJacocoAnt

View File

@ -30,7 +30,7 @@ androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugAndro
androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.test.espresso:espresso-idling-resource:3.5.1=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath
androidx.test:annotation:1.0.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.test:core:1.0.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.test:core:1.4.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.test:monitor:1.6.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.tracing:tracing:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath

View File

@ -35,7 +35,7 @@ buildscript {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
}
include ":app"

View File

@ -12,8 +12,13 @@ import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.gradle.kotlin.dsl.extra
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
/**
* Warns or errors on version ranges of dependencies required to build a Flutter Android app.
*
* For code that evaluates if dependencies are compatible with each other see
* packages/flutter_tools/lib/src/android/gradle_utils.dart.
*/
object DependencyVersionChecker {
// Logging constants.
@VisibleForTesting internal const val GRADLE_NAME: String = "Gradle"
@ -114,12 +119,12 @@ object DependencyVersionChecker {
@JvmStatic fun checkDependencyVersions(project: Project) {
project.extra.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false)
checkGradleVersion(getGradleVersion(project), project)
checkJavaVersion(getJavaVersion(), project)
checkGradleVersion(VersionFetcher.getGradleVersion(project), project)
checkJavaVersion(VersionFetcher.getJavaVersion(), project)
configureMinSdkCheck(project)
val agpVersion: AndroidPluginVersion? = getAGPVersion(project)
val agpVersion: AndroidPluginVersion? = VersionFetcher.getAGPVersion(project)
if (agpVersion != null) {
checkAGPVersion(agpVersion, project)
} else {
@ -129,7 +134,7 @@ object DependencyVersionChecker {
)
}
val kgpVersion: Version? = getKGPVersion(project)
val kgpVersion: Version? = VersionFetcher.getKGPVersion(project)
if (kgpVersion != null) {
checkKGPVersion(kgpVersion, project)
}
@ -176,7 +181,7 @@ object DependencyVersionChecker {
project: Project,
it: Variant
): MinSdkVersion {
val agpVersion: AndroidPluginVersion? = getAGPVersion(project)
val agpVersion: AndroidPluginVersion? = VersionFetcher.getAGPVersion(project)
return if (agpVersion != null && agpVersion.major >= 8 && agpVersion.minor >= 1) {
MinSdkVersion(it.name, it.minSdk.apiLevel)
} else {
@ -184,54 +189,6 @@ object DependencyVersionChecker {
}
}
// 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 = 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,
@ -394,43 +351,6 @@ object DependencyVersionChecker {
}
}
// 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 = "$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(

View File

@ -314,6 +314,7 @@ class FlutterPlugin : Plugin<Project> {
}
FlutterPluginUtils.addTaskForJavaVersion(projectToAddTasksTo)
FlutterPluginUtils.addTaskForKGPVersion(projectToAddTasksTo)
if (FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo)) {
FlutterPluginUtils.addTaskForPrintBuildVariants(projectToAddTasksTo)
FlutterPluginUtils.addTasksForOutputsAppLinkSettings(projectToAddTasksTo)

View File

@ -12,7 +12,6 @@ import com.flutter.gradle.plugins.PluginHandler
import groovy.lang.Closure
import groovy.util.Node
import org.gradle.api.GradleException
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.UnknownTaskException
@ -625,7 +624,7 @@ object FlutterPluginUtils {
// build artifact, so we move it from that directory to within Flutter's build directory
// to avoid polluting source directories with build artifacts.
//
// AGP explicitely recommends not setting the buildStagingDirectory to be within a build
// AGP explicitly recommends not setting the buildStagingDirectory to be within a build
// directory in
// https://developer.android.com/reference/tools/gradle-api/8.3/null/com/android/build/api/dsl/Cmake#buildStagingDirectory(kotlin.Any),
// but as we are not actually building anything (and are instead only tricking AGP into
@ -675,7 +674,7 @@ object FlutterPluginUtils {
if (!supportsBuildMode(project, flutterBuildMode)) {
project.logger.quiet(
"Project does not support Flutter build mode: $flutterBuildMode, " +
"skipping adding flutter dependencies"
"skipping adding Flutter dependencies"
)
return
}
@ -714,7 +713,7 @@ object FlutterPluginUtils {
// ------------------ Task adders (a subset of the above category)
// Add a task that can be called on flutter projects that prints the Java version used in Gradle.
// Add a task that can be called on Flutter projects that prints the Java version used in Gradle.
//
// Format of the output of this task can be used in debugging what version of Java Gradle is using.
// Not recommended for use in time sensitive commands like `flutter run` or `flutter build` as
@ -726,7 +725,25 @@ object FlutterPluginUtils {
description = "Print the current java version used by gradle. see: " +
"https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html"
doLast {
println(JavaVersion.current())
println(VersionFetcher.getJavaVersion())
}
}
}
// Add a task that can be called on Flutter projects that prints the KGP version used in
// the project.
//
// Format of the output of this task can be used in debugging what version of KGP a
// project is using.
// Not recommended for use in time sensitive commands like `flutter run` or `flutter build` as
// Gradle tasks are slower than we want. Particularly in light of https://github.com/flutter/flutter/issues/119196.
@JvmStatic
@JvmName("addTaskForKGPVersion")
internal fun addTaskForKGPVersion(project: Project) {
project.tasks.register("kgpVersion") {
description = "Print the current kgp version used by the project."
doLast {
println("KGP Version: " + VersionFetcher.getKGPVersion(project).toString())
}
}
}

View File

@ -0,0 +1,121 @@
// 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.
package com.flutter.gradle
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.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
internal object VersionFetcher {
/**
* Returns the version of the JVM.
*/
internal fun getJavaVersion(): JavaVersion = JavaVersion.current()
/**
* Returns the version of Gradle.
*/
internal fun getGradleVersion(project: Project): Version {
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.invocation/-gradle/index.html#-837060600%2FFunctions%2F-1793262594
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('-'))
}
/**
* Returns the version of the Android Gradle plugin.
*/
internal fun getAGPVersion(project: Project): AndroidPluginVersion? {
val androidPluginVersion: AndroidPluginVersion? =
project.extensions
.findByType(
AndroidComponentsExtension::class.java
)?.pluginVersion
return androidPluginVersion
}
/**
* Returns the version of the Kotlin Gradle plugin.
*/
internal fun getKGPVersion(project: Project): Version? {
// TODO(gmackall): AGP has a getKotlinAndroidPluginVersion(), and KGP has a
// getKotlinPluginVersion(). Consider replacing this implementation with one of
// those.
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)
// Partial implementation of getKotlinPluginVersion from the comment above.
var versionString: String? = kotlinPlugin?.pluginVersion
if (!versionString.isNullOrEmpty()) {
return Version.fromString(versionString)
}
// Fall back to reflection.
val versionField =
kotlinPlugin?.javaClass?.kotlin?.members?.firstOrNull {
it.name == firstKotlinVersionFieldName || it.name == secondKotlinVersionFieldName
}
versionString = versionField?.call(kotlinPlugin) as String?
return if (versionString == null) {
null
} else {
Version.fromString(versionString)
}
}
}
/**
* 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 equals(other: Any?): Boolean = other is Version && compareTo(other) == 0
override fun hashCode(): Int = major.hashCode() or minor.hashCode() or patch.hashCode()
override fun toString(): String = "$major.$minor.$patch"
}

View File

@ -886,7 +886,7 @@ class FlutterPluginUtilsTest {
verify(exactly = 1) {
project.logger.quiet(
"Project does not support Flutter build mode: debug, " +
"skipping adding flutter dependencies"
"skipping adding Flutter dependencies"
)
}
}
@ -1049,6 +1049,24 @@ class FlutterPluginUtilsTest {
}
}
// addTaskForKGPVersion
@Test
fun `addTaskForKGPVersion adds task for KGP version`() {
val project = mockk<Project>()
every { project.tasks.register(any(), any<Action<Task>>()) } returns mockk()
val captureSlot = slot<Action<Task>>()
FlutterPluginUtils.addTaskForKGPVersion(project)
verify { project.tasks.register("kgpVersion", capture(captureSlot)) }
val mockTask = mockk<Task>()
every { mockTask.description = any() } returns Unit
every { mockTask.doLast(any<Action<Task>>()) } returns mockk()
captureSlot.captured.execute(mockTask)
verify {
mockTask.description = "Print the current kgp version used by the project."
}
}
// addTaskForPrintBuildVariants
@Test
fun `addTaskForPrintBuildVariants adds task for printing build variants`() {

View File

@ -0,0 +1,67 @@
// 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.
package com.flutter.gradle
import com.android.build.api.AndroidPluginVersion
import com.android.build.api.variant.AndroidComponentsExtension
import io.mockk.every
import io.mockk.mockk
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
import kotlin.test.Test
import kotlin.test.assertEquals
class VersionFetcherTest {
// getGradleVersion
@Test
fun `getGradleVersion returns version when gradleVersion is set`() {
val gradleVersion = Version(1, 9, 20)
val project = mockk<Project>()
every { project.gradle.gradleVersion } returns gradleVersion.toString()
assertEquals(VersionFetcher.getGradleVersion(project), gradleVersion)
}
@Test
fun `getGradleVersion returns version when gradleVersion has hyphen`() {
val project = mockk<Project>()
every { project.gradle.gradleVersion } returns "2.1.20-2"
assertEquals(VersionFetcher.getGradleVersion(project), Version(2, 1, 20))
}
// getAGPVersion
@Test
fun `getAGPVersion returns version when agpVersion is set`() {
val agpVersion = AndroidPluginVersion(8, 3, 0)
val project = mockk<Project>()
val mockAndroidComponentsExtension = mockk<AndroidComponentsExtension<*, *, *>>()
every { project.extensions.findByType(AndroidComponentsExtension::class.java) } returns mockAndroidComponentsExtension
every { mockAndroidComponentsExtension.pluginVersion } returns agpVersion
assertEquals(VersionFetcher.getAGPVersion(project).toString(), agpVersion.toString())
}
// getKGPVersion
@Test
fun `getKGPVersion returns version when kotlin_version is set`() {
val kgpVersion = Version(1, 9, 20)
val project = mockk<Project>()
every { project.hasProperty(eq("kotlin_version")) } returns true
every { project.properties["kotlin_version"] } returns kgpVersion.toString()
val result = VersionFetcher.getKGPVersion(project)
assertEquals(kgpVersion, result!!)
}
@Test
fun `getKGPVersion returns version from KotlinAndroidPluginWrapper`() {
val kgpVersion = Version(1, 9, 20)
val project = mockk<Project>()
every { project.hasProperty(eq("kotlin_version")) } returns false
every { project.plugins.findPlugin(KotlinAndroidPluginWrapper::class.java) } returns
mockk<KotlinAndroidPluginWrapper> {
every { pluginVersion } returns kgpVersion.toString()
}
val result = VersionFetcher.getKGPVersion(project)
assertEquals(kgpVersion, result!!)
}
}

View File

@ -8,6 +8,7 @@ import 'package:unified_analytics/unified_analytics.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/platform.dart';
@ -62,6 +63,12 @@ const String oneMajorVersionHigherJavaVersion = '24';
// flutter analyze --suggestions and does not imply broader flutter support.
const String maxKnownAndSupportedGradleVersion = '8.12';
// Update this with new KGP versions come out including minor versions.
//
// Supported here means supported by the tooling for
// flutter analyze --suggestions and does not imply broader flutter support.
const String maxKnownAndSupportedKgpVersion = '2.1.20';
// Update this when new versions of AGP come out.
//
// Supported here means tooling is aware of this version's Java <-> AGP
@ -72,12 +79,30 @@ const String maxKnownAndSupportedAgpVersion = '8.7.3';
// Update this when new versions of AGP come out.
const String maxKnownAgpVersion = '8.7.3';
// Supported here means tooling is aware of this versions
// Java <-> AGP compatibility and does not imply broader flutter support.
// For use in flutter see the code in:
// flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt
@visibleForTesting
const String oldestConsideredAgpVersion = '3.3.0';
// Supported here means tooling is aware of this versions
// gradle compatibility and does not imply broader flutter support.
@visibleForTesting
const String oldestConsideredGradleVersion = '4.10.1';
// Supported here means tooling is aware of this versions
// gradle/AGP compatibility and does not imply broader flutter support.
@visibleForTesting
const String oldestDocumentedKgpCompatabilityVersion = '1.6.20';
// 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.
// [_settingsAndroidGradlePluginRegExp] and [_kotlinGradlePluginRegExpFromId]
// to identify the version section.
const String _versionGroupName = 'version';
// AGP can be defined in the dependencies block of [build.gradle] or [build.gradle.kts].
@ -103,6 +128,17 @@ final RegExp _androidGradlePluginRegExpFromId = RegExp(
multiLine: true,
);
// KGP is defined in several places this code only checks in plugins block
// of [settings.gradle] and [settings.gradle.kts].
// Expected content:
// Groovy DSL - id "org.jetbrains.kotlin.android" version "{{kgpVersion}}"
// Kotlin DSL - id("org.jetbrains.kotlin.android") version "{{kgpVersion}}"
// ?<version> is used to name the version group which helps with extraction.
final RegExp _kotlinGradlePluginRegExpFromId = RegExp(
r"""[^\/]*s*id\s*\(?['"]org\.jetbrains\.kotlin\.android['"]\)?\s+version\s+['"](?<version>\d+(\.\d+){1,2})\)?""",
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'
@ -125,7 +161,8 @@ final RegExp tooOldMinSdkVersionMatch = RegExp(
);
// From https://docs.gradle.org/current/userguide/command_line_interface.html#command_line_interface
const String gradleVersionFlag = r'--version';
// Flag to print the versions for gradle, kotlin dsl, groovy, etc.
const String gradleVersionsFlag = r'--version';
// Directory under android/ that gradle uses to store gradle information.
// Regularly used with [gradleWrapperDirectory] and
@ -274,7 +311,7 @@ Future<String?> getGradleVersion(
return gradleVersion;
} else {
// Did not find gradle zip url. Likely this is a bug in our parsing.
logger.printWarning(_formatParseWarning(wrapperFileContent));
logger.printWarning(_formatParseWarning(wrapperFileContent, type: 'gradle'));
}
} else {
// If no distributionUrl log then treat as if there was no propertiesFile.
@ -287,9 +324,10 @@ Future<String?> getGradleVersion(
logger.printTrace('$propertiesFile does not exist falling back to system gradle');
}
// System installed Gradle version.
// TODO(reidbaker): Modify this gradle execution to use gradlew.
if (processManager.canRun('gradle')) {
final String gradleVersionVerbose =
(await processManager.run(<String>['gradle', gradleVersionFlag])).stdout as String;
final String gradleVersionsVerbose =
(await processManager.run(<String>['gradle', gradleVersionsFlag])).stdout as String;
// Expected format:
/*
@ -311,10 +349,10 @@ OS: Mac OS X 13.2.1 aarch64
// 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);
final RegExpMatch? version = gradleVersionRegex.firstMatch(gradleVersionsVerbose);
if (version == null) {
// Most likely a bug in our parse implementation/regex.
logger.printWarning(_formatParseWarning(gradleVersionVerbose));
logger.printWarning(_formatParseWarning(gradleVersionsVerbose, type: 'gradle'));
return null;
}
return version.group(1);
@ -324,6 +362,80 @@ OS: Mac OS X 13.2.1 aarch64
}
}
/// Returns the Kotlin Gradle Plugin (KGP) version that the current project
/// depends on if found, null otherwise.
/// [directory] should be an android directory with a build.gradle file.
Future<String?> getKgpVersion(
Directory androidDirectory,
Logger logger,
ProcessManager processManager,
) async {
// Maintainers of the kotlin dsl and the kotlin gradle plugin are different.
//
// Android Docs refer to the kotlin gradle plugin with either the full name or KGP.
// Kotlin docs refer to the kotlin gradle plugin as kotlin android plugin.
//
// gradle --version or ./gradlew --version will print the kotlin dsl version.
// This version normally changes with the version of gradle.
// https://github.com/gradle/gradle/blob/cefbee263181a924ac4efcaace6bda97a55bc0f7/platforms/core-runtime/gradle-cli/src/main/java/org/gradle/launcher/cli/DefaultCommandLineActionFactory.java#L260
// This vesion is NOT the version of KGP that the project uses.
//
// Instead the kgpVersion task is a custom flutter task dynamiclly added that can
// print the kgp version if gradle can run successfuly.
if (processManager.canRun('./gradlew', workingDirectory: androidDirectory.path)) {
final ProcessResult command = await processManager.run(<String>[
'./gradlew',
'kgpVersion',
'-q',
], workingDirectory: androidDirectory.path);
if (command.exitCode == 0) {
final String kgpVersionOutput = command.stdout as String;
// See expected output defined in
// flutter/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt addTaskForKGPVersion
final RegExp kotlinVersionRegex = RegExp(r'KGP Version:\s+(\d+\.\d+(?:\.\d+)?)');
final RegExpMatch? version = kotlinVersionRegex.firstMatch(kgpVersionOutput);
if (version != null) {
return version.group(1);
}
// Most likely a bug in our parse implementation/regex.
logger.printWarning(_formatParseWarning(kgpVersionOutput, type: 'kotlin'));
} else {
logger.printTrace('Non zero exit code from gradle task kgpVersion.');
}
} else {
logger.printTrace('Could not run gradle task kgpVersion.');
}
// Project valiation code is regularly run on projects that can not build.
// Because of that this code also attempts to search through known template
// locations for kotlin versions.
logger.printTrace('Checking settings for kgp version.');
File settingsFile = androidDirectory.childFile('settings.gradle');
if (!settingsFile.existsSync()) {
settingsFile = androidDirectory.childFile('settings.gradle.kts');
}
if (settingsFile.existsSync()) {
final String settingsFileContent = settingsFile.readAsStringSync();
final RegExpMatch? settingsMatch = _kotlinGradlePluginRegExpFromId.firstMatch(
settingsFileContent,
);
if (settingsMatch != null) {
final String? kgpVersion = settingsMatch.namedGroup(_versionGroupName);
logger.printTrace('$settingsFile provides KGP version: $kgpVersion');
return kgpVersion;
}
} else {
logger.printTrace('No settings.gradle.kts');
}
return null;
}
/// Returns the Android Gradle Plugin (AGP) version that the current project
/// depends on when found, null otherwise.
///
@ -378,14 +490,198 @@ String? getAgpVersion(Directory androidDirectory, Logger logger) {
return null;
}
String _formatParseWarning(String content) {
return 'Could not parse gradle version from: \n'
String _formatParseWarning(String content, {required String type}) {
return 'Could not parse $type 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 KGP and Gradle are compatible with each other.
//
// Returns true if versions are compatible.
// Null or empty Gradle or KGP version returns false.
// If compatibility cannot 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://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
bool validateGradleAndKGP(Logger logger, {required String? kgpV, required String? gradleV}) {
if (gradleV == null || kgpV == null || gradleV.isEmpty || kgpV.isEmpty) {
logger.printTrace('Gradle or KGP version unknown ($gradleV, $kgpV).');
return false;
}
if (isWithinVersionRange(gradleV, min: '0.0', max: oldestConsideredGradleVersion)) {
logger.printTrace(
'Gradle version $gradleV older than oldest considered $oldestConsideredGradleVersion',
);
return false;
}
if (isWithinVersionRange(
kgpV,
min: maxKnownAndSupportedKgpVersion,
max: '100.100',
inclusiveMin: false,
)) {
logger.printTrace(
'Newer than known KGP version ($kgpV), gradle ($gradleV).'
'\n Treating as valid configuration.',
);
return true;
}
// https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
// Documenation is non continuous, past versions are known to the
// publishers of KGP. When covering version ranges beyond what is documented
// add a comment with the documented value.
// Continuous KGP version handling is prefered in case an emergency patch to a
// past release is shipped this code will assume the version range that is closest.
if (isWithinVersionRange(kgpV, min: '2.1.20', max: '2.1.20')) {
// Documented max is 8.11, using 8.12 non inclusive covers patch versions.
return isWithinVersionRange(gradleV, min: '7.6.3', max: '8.12', inclusiveMax: false);
}
if (isWithinVersionRange(kgpV, min: '2.1.0', max: '2.1.10')) {
// Documented max is 8.10, using 8.11 non inclusive covers patch versions.
return isWithinVersionRange(gradleV, min: '7.6.3', max: '8.11', inclusiveMax: false);
}
// Documented max is 2.0.21.
if (isWithinVersionRange(kgpV, min: '2.0.20', max: '2.1', inclusiveMax: false)) {
// Documented max is 8.5, using 8.9 non inclusive covers patch versions.
// Kotlin Multiplatform can throw warnings on 8.8.
return isWithinVersionRange(gradleV, min: '6.8.3', max: '8.9', inclusiveMax: false);
}
if (isWithinVersionRange(kgpV, min: '2.0', max: '2.0.20', inclusiveMax: false)) {
// Documented max is 8.5, using 8.6 non inclusive covers patch versions.
return isWithinVersionRange(gradleV, min: '6.8.3', max: '8.6', inclusiveMax: false);
}
// Documented max is 1.9.25.
if (isWithinVersionRange(kgpV, min: '1.9.20', max: '2.0', inclusiveMax: false)) {
return isWithinVersionRange(gradleV, min: '6.8.3', max: '8.1.1');
}
// Documented max is 1.9.10.
if (isWithinVersionRange(kgpV, min: '1.8.20', max: '1.9.20', inclusiveMax: false)) {
return isWithinVersionRange(gradleV, min: '6.8.3', max: '7.6.0');
}
// Documented max is 1.8.11.
if (isWithinVersionRange(kgpV, min: '1.8.0', max: '1.8.20', inclusiveMax: false)) {
return isWithinVersionRange(gradleV, min: '6.8.3', max: '7.3.3');
}
// Documented max is 1.7.22.
if (isWithinVersionRange(kgpV, min: '1.7.20', max: '1.8.0', inclusiveMax: false)) {
return isWithinVersionRange(gradleV, min: '6.7.1', max: '7.1.1');
}
// Documented max is 1.7.10.
if (isWithinVersionRange(kgpV, min: '1.7.0', max: '1.7.20', inclusiveMax: false)) {
return isWithinVersionRange(gradleV, min: '6.7.1', max: '7.0.2');
}
// Documented max is 1.6.21.
if (isWithinVersionRange(
kgpV,
min: oldestDocumentedKgpCompatabilityVersion,
max: '1.7.0',
inclusiveMax: false,
)) {
return isWithinVersionRange(gradleV, min: '6.1.1', max: '7.0.2');
}
logger.printTrace('Unknown KGP-Gradle compatibility, KGP: $kgpV, Gradle: $gradleV');
return false;
}
// Validate that KGP and AGP are compatible with each other.
//
// Returns true if versions are compatible.
// Null or empty KGP or AGP version returns false.
// If compatibility cannot 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://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
bool validateAgpAndKgp(Logger logger, {required String? kgpV, required String? agpV}) {
if (agpV == null || kgpV == null || agpV.isEmpty || kgpV.isEmpty) {
logger.printTrace('KGP or AGP version unknown ($kgpV, $agpV).');
return false;
}
if (isWithinVersionRange(agpV, min: '0.0', max: oldestConsideredAgpVersion)) {
logger.printTrace(
'AGP version ($agpV) older than oldest supported $oldestConsideredAgpVersion.',
);
}
const String maxKnownAgpVersionWithFullKotinSupport = '8.7.2';
if (isWithinVersionRange(
kgpV,
min: maxKnownAndSupportedKgpVersion,
max: '100.100',
inclusiveMin: false,
) ||
isWithinVersionRange(
agpV,
min: maxKnownAgpVersionWithFullKotinSupport,
max: '100.100',
inclusiveMin: false,
)) {
logger.printTrace(
'Newer than known KGP version ($kgpV), AGP ($agpV).'
'\n Treating as valid configuration.',
);
return true;
}
// https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
// Documenation is non continuous, past versions are known to the
// publishers of KGP. When covering version ranges beyond what is documented
// add a comment with the documented value.
// Continuous KGP version handling is prefered in case an emergency patch to a
// past release is shipped this code will assume the version range that is closest.
if (isWithinVersionRange(kgpV, min: '2.1.0', max: '2.1.20')) {
return isWithinVersionRange(agpV, min: '7.3.1', max: '8.7.2');
}
// Documented max is 2.0.21
if (isWithinVersionRange(kgpV, min: '2.0.20', max: '2.1.0', inclusiveMax: false)) {
// Documented max is 8.5.
return isWithinVersionRange(agpV, min: '7.1.3', max: '8.6', inclusiveMax: false);
}
// Documented max is 2.0.0.
if (isWithinVersionRange(kgpV, min: '2.0.0', max: '2.0.20', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '7.1.3', max: '8.3.1');
}
// Documented max is 1.9.25
if (isWithinVersionRange(kgpV, min: '1.9.20', max: '2.0.0', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '4.2.2', max: '8.1.0');
}
// Documented max is 1.9.10
if (isWithinVersionRange(kgpV, min: '1.9.0', max: '1.9.20', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '4.2.2', max: '7.4.0');
}
// Documented max is 1.8.22
if (isWithinVersionRange(kgpV, min: '1.8.20', max: '1.9', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '4.1.3', max: '7.4.0');
}
// Documented max is 1.8.11
if (isWithinVersionRange(kgpV, min: '1.8.0', max: '1.8.20', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '4.1.3', max: '7.2.1');
}
// Documented max is 1.7.22
if (isWithinVersionRange(kgpV, min: '1.7.20', max: '1.8.0', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '3.6.4', max: '7.0.4');
}
// Documented max is 1.7.10
// Documented gap between 1.6.21 and 1.7.0.
if (isWithinVersionRange(kgpV, min: '1.6.20', max: '1.7.20', inclusiveMax: false)) {
return isWithinVersionRange(agpV, min: '3.4.3', max: '7.0.2');
}
logger.printTrace('Unknown KGP-Gradle compatibility, KGP: $kgpV, AGP: $agpV');
return false;
}
// Validate that Gradle version and AGP are compatible with each other.
//
// Returns true if versions are compatible.
@ -399,23 +695,25 @@ String _formatParseWarning(String content) {
// 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)) {
if (isWithinVersionRange(
agpV,
min: '0.0',
max: oldestConsideredAgpVersion,
inclusiveMax: false,
)) {
logger.printTrace('AGP Version: $agpV is too old.');
return false;
}
if (isWithinVersionRange(
gradleV,
min: '0.0',
max: oldestSupportedGradleVersion,
max: oldestConsideredGradleVersion,
inclusiveMax: false,
)) {
logger.printTrace('Gradle Version: $gradleV is too old.');
@ -499,7 +797,7 @@ bool validateGradleAndAgp(Logger logger, {required String? gradleV, required Str
/// 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 oldestConsideredJavaVersion = '1.8';
const String oldestDocumentedJavaGradleCompatibility = '2.0';
// Begin Java <-> Gradle validation.
@ -513,7 +811,7 @@ bool validateJavaAndGradle(Logger logger, {required String? javaV, required Stri
if (isWithinVersionRange(
javaV,
min: '1.1',
max: oldestSupportedJavaVersion,
max: oldestConsideredJavaVersion,
inclusiveMax: false,
)) {
logger.printTrace('Java Version: $javaV is too old.');

View File

@ -442,9 +442,9 @@ abstract class FlutterProjectPlatform {
class AndroidProject extends FlutterProjectPlatform {
AndroidProject._(this.parent);
// User facing string when java/gradle/agp versions are compatible.
// User facing string when java/gradle/agp/kgp versions are compatible.
@visibleForTesting
static const String validJavaGradleAgpString = 'compatible java/gradle/agp';
static const String validJavaGradleAgpKgpString = 'compatible java/gradle/agp/kgp';
// User facing link that describes compatibility between gradle and
// android gradle plugin.
@ -456,6 +456,11 @@ class AndroidProject extends FlutterProjectPlatform {
static const String javaGradleCompatUrl =
'https://docs.gradle.org/current/userguide/compatibility.html#java';
// User facing link that describes compatibility between KGP and Gradle
// and AGP.
static const String kgpCompatUrl =
'https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin';
// User facing link that describes instructions for downloading
// the latest version of Android Studio.
static const String installAndroidStudioUrl = 'https://developer.android.com/studio/install';
@ -681,7 +686,7 @@ class AndroidProject extends FlutterProjectPlatform {
// Constructing ProjectValidatorResult happens here and not in
// flutter_tools/lib/src/project_validator.dart because of the additional
// Complexity of variable status values and error string formatting.
const String visibleName = 'Java/Gradle/Android Gradle Plugin';
const String visibleName = 'Java/Gradle/KGP/Android Gradle Plugin';
final CompatibilityResult validJavaGradleAgpVersions = await hasValidJavaGradleAgpVersions();
return ProjectValidatorResult(
@ -695,8 +700,8 @@ class AndroidProject extends FlutterProjectPlatform {
}
/// Ensures Java SDK is compatible with the project's Gradle version and
/// the project's Gradle version is compatible with the AGP version used
/// in build.gradle.
/// the project's Gradle version is compatible with the AGP version and
/// kotlin version used in build.gradle.
Future<CompatibilityResult> hasValidJavaGradleAgpVersions() async {
final String? gradleVersion = await gradle.getGradleVersion(
hostAppGradleRoot,
@ -705,9 +710,14 @@ class AndroidProject extends FlutterProjectPlatform {
);
final String? agpVersion = gradle.getAgpVersion(hostAppGradleRoot, globals.logger);
final String? javaVersion = versionToParsableString(globals.java?.version);
final String? kgpVersion = await gradle.getKgpVersion(
hostAppGradleRoot,
globals.logger,
globals.processManager,
);
// Assume valid configuration.
String description = validJavaGradleAgpString;
String description = validJavaGradleAgpKgpString;
final bool compatibleGradleAgp = gradle.validateGradleAndAgp(
globals.logger,
@ -721,6 +731,18 @@ class AndroidProject extends FlutterProjectPlatform {
gradleV: gradleVersion,
);
final bool compatibleKgpGradle = gradle.validateGradleAndKGP(
globals.logger,
gradleV: gradleVersion,
kgpV: kgpVersion,
);
final bool compatibleAgpKgp = gradle.validateAgpAndKgp(
globals.logger,
agpV: agpVersion,
kgpV: kgpVersion,
);
// Begin description formatting.
if (!compatibleGradleAgp) {
final String gradleDescription =
@ -745,7 +767,28 @@ See the link below for more information:
$javaGradleCompatUrl
''';
}
return CompatibilityResult(compatibleJavaGradle && compatibleGradleAgp, description);
if (!compatibleKgpGradle) {
description = '''
${compatibleGradleAgp ? '' : description}
Incompatible KGP/Gradle versions.
Gradle Version: $gradleVersion, Kotlin Version: $kgpVersion\n
See the link below for more information:
$kgpCompatUrl
''';
}
if (!compatibleAgpKgp) {
description = '''
${compatibleGradleAgp ? '' : description}
Incompatible AGP/KGP versions.
AGP Version: $agpVersion, KGP Version: $kgpVersion\n
See the link below for more information:
$kgpCompatUrl
''';
}
return CompatibilityResult(
compatibleJavaGradle && compatibleGradleAgp && compatibleKgpGradle && compatibleAgpKgp,
description,
);
}
bool get isUsingGradle {

View File

@ -332,7 +332,10 @@ OS: Mac OS X 13.2.1 aarch64
final Directory androidDirectory = fileSystem.directory('/android')..createSync();
final ProcessManager processManager =
FakeProcessManager.empty()..addCommand(
const FakeCommand(command: <String>['gradle', gradleVersionFlag], stdout: gradleOutput),
const FakeCommand(
command: <String>['gradle', gradleVersionsFlag],
stdout: gradleOutput,
),
);
expect(
@ -347,7 +350,10 @@ OS: Mac OS X 13.2.1 aarch64
final Directory androidDirectory = fileSystem.directory('/android')..createSync();
final ProcessManager processManager =
FakeProcessManager.empty()..addCommand(
const FakeCommand(command: <String>['gradle', gradleVersionFlag], stdout: gradleOutput),
const FakeCommand(
command: <String>['gradle', gradleVersionsFlag],
stdout: gradleOutput,
),
);
expect(
@ -708,6 +714,247 @@ dependencies {
}
});
FakeCommand createKgpVersionCommand(String kgpV) {
return FakeCommand(
command: const <String>['./gradlew', 'kgpVersion', '-q'],
stdout: '''
KGP Version: $kgpV
''',
);
}
testWithoutContext('returns the KGP fetched from kgpVersion gradle task', () async {
final Directory androidDirectory = fileSystem.directory('/android')..createSync();
// Three numbered versions.
const String kgpV2 = '1.8.22';
final FakeProcessManager processManager2 = FakeProcessManager.list(<FakeCommand>[
createKgpVersionCommand(kgpV2),
]);
expect(await getKgpVersion(androidDirectory, BufferLogger.test(), processManager2), kgpV2);
// 2 numbered versions
const String kgpV3 = '1.9';
final FakeProcessManager processManager3 = FakeProcessManager.list(<FakeCommand>[
createKgpVersionCommand(kgpV3),
]);
expect(await getKgpVersion(androidDirectory, BufferLogger.test(), processManager3), kgpV3);
final FakeProcessManager processManagerNoGradle = FakeProcessManager.empty();
processManagerNoGradle.excludedExecutables = <String>{'./gradlew'};
expect(
await getKgpVersion(androidDirectory, BufferLogger.test(), processManagerNoGradle),
null,
);
});
testWithoutContext('returns the KGP version when in Kotlin settings as plugin', () async {
final Directory androidDirectory = fileSystem.directory('/android')..createSync();
// File must exist and cannot have kgp defined.
androidDirectory.childFile('build.gradle.kts').writeAsStringSync(r'');
androidDirectory.childFile('settings.gradle.kts').writeAsStringSync(r'''
pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
// Decoy value to ensure we ignore commented out lines.
// id("org.jetbrains.kotlin.android") version "6.1.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
}
''');
final FakeProcessManager processManager = FakeProcessManager.empty();
processManager.excludedExecutables = <String>{'./gradlew'};
expect(await getKgpVersion(androidDirectory, BufferLogger.test(), processManager), '1.8.22');
});
group('validates kgp/gradle versions', () {
final List<GradleKgpTestData> testData = <GradleKgpTestData>[
// Values too new.
GradleKgpTestData(true, kgpVersion: '3.0', gradleVersion: '99.99'),
// Template versions of Gradle/AGP.
GradleKgpTestData(
true,
kgpVersion: templateKotlinGradlePluginVersion,
// TODO(reidbaker): replace with templateDefaultGradleVersion.
gradleVersion: '8.10',
),
// Kotlin version at the edge of support window.
GradleKgpTestData(true, kgpVersion: '2.1.20', gradleVersion: '8.1'),
GradleKgpTestData(true, kgpVersion: '2.1.10', gradleVersion: '8.3'),
GradleKgpTestData(true, kgpVersion: '2.0.21', gradleVersion: '7.6.3'),
GradleKgpTestData(true, kgpVersion: '2.0.20', gradleVersion: '7.6.3'),
GradleKgpTestData(true, kgpVersion: '2.0', gradleVersion: '8.5'),
GradleKgpTestData(true, kgpVersion: '1.9.25', gradleVersion: '8.1.1'),
GradleKgpTestData(true, kgpVersion: '1.9.20', gradleVersion: '6.8.3'),
GradleKgpTestData(true, kgpVersion: '1.9.10', gradleVersion: '6.8.3'),
GradleKgpTestData(true, kgpVersion: '1.9.0', gradleVersion: '7.6.0'),
GradleKgpTestData(true, kgpVersion: '1.8.22', gradleVersion: '7.6.0'),
GradleKgpTestData(true, kgpVersion: '1.8.20', gradleVersion: '6.8.3'),
GradleKgpTestData(true, kgpVersion: '1.8.11', gradleVersion: '7.3.3'),
GradleKgpTestData(true, kgpVersion: '1.8.0', gradleVersion: '7.3.3'),
GradleKgpTestData(true, kgpVersion: '1.7.22', gradleVersion: '7.1.1'),
GradleKgpTestData(true, kgpVersion: '1.7.20', gradleVersion: '6.7.1'),
GradleKgpTestData(true, kgpVersion: '1.7.10', gradleVersion: '7.0.2'),
GradleKgpTestData(true, kgpVersion: '1.7.0', gradleVersion: '6.7.1'),
GradleKgpTestData(true, kgpVersion: '1.6.21', gradleVersion: '6.1.1'),
GradleKgpTestData(true, kgpVersion: '1.6.20', gradleVersion: '7.0.2'),
// Gradle versions inspired by
// https://developer.android.com/build/releases/gradle-plugin#expandable-1
GradleKgpTestData(true, kgpVersion: '2.1.20', gradleVersion: '8.11.1'),
GradleKgpTestData(true, kgpVersion: '2.1.10', gradleVersion: '8.10.2'),
GradleKgpTestData(true, kgpVersion: '2.1.10', gradleVersion: '8.9'),
GradleKgpTestData(true, kgpVersion: '2.1.5', gradleVersion: '8.7'),
GradleKgpTestData(true, kgpVersion: '2.1.0', gradleVersion: '8.7'),
GradleKgpTestData(true, kgpVersion: '2.0.20', gradleVersion: '8.6'),
GradleKgpTestData(true, kgpVersion: '2.0.1', gradleVersion: '8.4'),
GradleKgpTestData(true, kgpVersion: '2.0.0', gradleVersion: '8.2'),
GradleKgpTestData(true, kgpVersion: '1.9.25', gradleVersion: '8.0'),
GradleKgpTestData(true, kgpVersion: '1.9.10', gradleVersion: '7.6.0'),
GradleKgpTestData(true, kgpVersion: '1.9.7', gradleVersion: '7.5'),
GradleKgpTestData(true, kgpVersion: '1.8.21', gradleVersion: '7.4'),
GradleKgpTestData(true, kgpVersion: '1.9.0', gradleVersion: '7.3.3'),
GradleKgpTestData(true, kgpVersion: '1.8.0', gradleVersion: '7.2'),
GradleKgpTestData(true, kgpVersion: '1.7.0', gradleVersion: '7.0'),
GradleKgpTestData(true, kgpVersion: '2.0.21', gradleVersion: '7.0'),
GradleKgpTestData(true, kgpVersion: '1.7.22', gradleVersion: '6.7.1'),
GradleKgpTestData(true, kgpVersion: '1.6.21', gradleVersion: '6.7.1'),
GradleKgpTestData(true, kgpVersion: '1.6.21', gradleVersion: '6.5'),
// Kotlin newer than max known.
GradleKgpTestData(true, kgpVersion: '2.1.21', gradleVersion: '8.12.1'),
// Kotlin too new for gradle version.
GradleKgpTestData(false, kgpVersion: '2.1.20', gradleVersion: '7.6.2'),
GradleKgpTestData(false, kgpVersion: '2.1.0', gradleVersion: '7.6.2'),
GradleKgpTestData(false, kgpVersion: '2.0.20', gradleVersion: '6.8.2'),
GradleKgpTestData(false, kgpVersion: '1.9.0', gradleVersion: '6.8.2'),
GradleKgpTestData(false, kgpVersion: '1.8.0', gradleVersion: '6.8.2'),
GradleKgpTestData(false, kgpVersion: '1.7.22', gradleVersion: '6.7.0'),
GradleKgpTestData(false, kgpVersion: '1.7.0', gradleVersion: '6.1.1'),
// Kotlin too old for gradle version.
GradleKgpTestData(false, kgpVersion: '2.1.10', gradleVersion: '8.11.1'),
GradleKgpTestData(false, kgpVersion: '2.1.0', gradleVersion: '8.11'),
GradleKgpTestData(false, kgpVersion: '2.0.0', gradleVersion: '8.6'),
GradleKgpTestData(false, kgpVersion: '1.9.20', gradleVersion: '8.2'),
GradleKgpTestData(false, kgpVersion: '1.9.0', gradleVersion: '7.7'),
GradleKgpTestData(false, kgpVersion: '1.8.20', gradleVersion: '7.7'),
GradleKgpTestData(false, kgpVersion: '1.8.0', gradleVersion: '7.4'),
GradleKgpTestData(false, kgpVersion: '1.7.20', gradleVersion: '7.2'),
GradleKgpTestData(false, kgpVersion: '1.7.0', gradleVersion: '7.0.3'),
GradleKgpTestData(false, kgpVersion: '1.6.20', gradleVersion: '7.0.3'),
// Kotlin older than oldest supported.
GradleKgpTestData(false, kgpVersion: '1.6.19', gradleVersion: '7.0.3'),
// Gradle older than oldest supported.
GradleKgpTestData(false, kgpVersion: '1.6.20', gradleVersion: '4.10'),
// Null values:
// ignore: avoid_redundant_argument_values
GradleKgpTestData(false, kgpVersion: null, gradleVersion: '7.2'),
// ignore: avoid_redundant_argument_values
GradleKgpTestData(false, kgpVersion: '2.1', gradleVersion: null),
// ignore: avoid_redundant_argument_values
GradleKgpTestData(false, kgpVersion: '', gradleVersion: ''),
// ignore: avoid_redundant_argument_values
GradleKgpTestData(false, kgpVersion: null, gradleVersion: null),
];
for (final GradleKgpTestData data in testData) {
test('(KGP, Gradle): (${data.kgpVersion}, ${data.gradleVersion})', () {
expect(
validateGradleAndKGP(
BufferLogger.test(),
gradleV: data.gradleVersion,
kgpV: data.kgpVersion,
),
data.validPair ? isTrue : isFalse,
reason: 'KGP: ${data.kgpVersion}, G: ${data.gradleVersion}',
);
});
}
});
group('validates KGP/AGP versions', () {
final List<KgpAgpTestData> testData = <KgpAgpTestData>[
// Values too new.
KgpAgpTestData(true, kgpVersion: '3.0', agpVersion: '99.99'),
// Template versions of Gradle/AGP.
KgpAgpTestData(
true,
kgpVersion: templateKotlinGradlePluginVersion,
// TODO(reidbaker): Replace with templateAndroidGradlePluginVersion
agpVersion: '8.7.2',
),
// Kotlin version at the edge of support window.
KgpAgpTestData(true, kgpVersion: '2.1.20', agpVersion: '8.7.2'),
KgpAgpTestData(true, kgpVersion: '2.1.20', agpVersion: '7.3.1'),
// AGP Versions not "fully supported" by kotlin
KgpAgpTestData(true, kgpVersion: '2.1.20', agpVersion: '8.9'),
KgpAgpTestData(true, kgpVersion: '2.1.20', agpVersion: '8.8'),
// Gradle versions inspired by
// https://developer.android.com/build/releases/gradle-plugin#expandable-1
KgpAgpTestData(true, kgpVersion: '2.1.5', agpVersion: '8.7'),
KgpAgpTestData(true, kgpVersion: '2.1.10', agpVersion: '8.6'),
KgpAgpTestData(true, kgpVersion: '2.0.21', agpVersion: '8.5'),
KgpAgpTestData(true, kgpVersion: '2.0.20', agpVersion: '8.4'),
KgpAgpTestData(true, kgpVersion: '2.0', agpVersion: '8.3.1'),
KgpAgpTestData(true, kgpVersion: '2.1.5', agpVersion: '8.2'),
KgpAgpTestData(true, kgpVersion: '1.9.25', agpVersion: '8.1'),
KgpAgpTestData(true, kgpVersion: '1.9.20', agpVersion: '8.0'),
KgpAgpTestData(true, kgpVersion: '1.9.10', agpVersion: '7.4'),
KgpAgpTestData(true, kgpVersion: '1.8.20', agpVersion: '7.4'),
KgpAgpTestData(true, kgpVersion: '1.8.21', agpVersion: '7.3'),
KgpAgpTestData(true, kgpVersion: '1.8.11', agpVersion: '7.2.1'),
KgpAgpTestData(true, kgpVersion: '1.8.0', agpVersion: '7.2.1'),
KgpAgpTestData(true, kgpVersion: '1.8.0', agpVersion: '7.1'),
KgpAgpTestData(true, kgpVersion: '1.7.20', agpVersion: '7.0.4'),
KgpAgpTestData(true, kgpVersion: '1.7.22', agpVersion: '7.0'),
KgpAgpTestData(true, kgpVersion: '1.8.22', agpVersion: '4.2.0'),
KgpAgpTestData(true, kgpVersion: '1.6.20', agpVersion: '4.1.0'),
// Kotlin newer than max known.
KgpAgpTestData(true, kgpVersion: '2.1.21', agpVersion: '8.7.2'),
// Kotlin too new for AGP version.
KgpAgpTestData(false, kgpVersion: '2.1.20', agpVersion: '7.3.0'),
KgpAgpTestData(false, kgpVersion: '2.1.10', agpVersion: '7.3.0'),
KgpAgpTestData(false, kgpVersion: '2.0.21', agpVersion: '7.1.2'),
KgpAgpTestData(false, kgpVersion: '2.0.0', agpVersion: '7.1.2'),
KgpAgpTestData(false, kgpVersion: '1.9.25', agpVersion: '4.2.1'),
KgpAgpTestData(false, kgpVersion: '1.8.20', agpVersion: '4.1.2'),
// Kotlin too old for gradle version.
KgpAgpTestData(false, kgpVersion: '2.0.20', agpVersion: '8.7.2'),
KgpAgpTestData(false, kgpVersion: '2.0.20', agpVersion: '8.6'),
KgpAgpTestData(false, kgpVersion: '2.0.0', agpVersion: '8.4'),
KgpAgpTestData(false, kgpVersion: '1.9.20', agpVersion: '8.2'),
KgpAgpTestData(false, kgpVersion: '1.9.0', agpVersion: '7.5'),
KgpAgpTestData(false, kgpVersion: '1.8.20', agpVersion: '7.5'),
KgpAgpTestData(false, kgpVersion: '1.8.1', agpVersion: '7.3'),
KgpAgpTestData(false, kgpVersion: '1.7.20', agpVersion: '7.1'),
KgpAgpTestData(false, kgpVersion: '1.7.0', agpVersion: '7.0.3'),
KgpAgpTestData(false, kgpVersion: '1.6.19', agpVersion: '7.0.3'),
// Unknown values.
KgpAgpTestData(
false,
kgpVersion: oldestDocumentedKgpCompatabilityVersion,
agpVersion: oldestConsideredAgpVersion,
),
// Null values:
// ignore: avoid_redundant_argument_values
KgpAgpTestData(false, kgpVersion: null, agpVersion: '7.2'),
// ignore: avoid_redundant_argument_values
KgpAgpTestData(false, kgpVersion: '2.1', agpVersion: null),
// ignore: avoid_redundant_argument_values
KgpAgpTestData(false, kgpVersion: '', agpVersion: ''),
// ignore: avoid_redundant_argument_values
KgpAgpTestData(false, kgpVersion: null, agpVersion: null),
];
for (final KgpAgpTestData data in testData) {
test('(KGP, AGP): (${data.kgpVersion}, ${data.agpVersion})', () {
expect(
validateAgpAndKgp(BufferLogger.test(), agpV: data.agpVersion, kgpV: data.kgpVersion),
data.validPair ? isTrue : isFalse,
reason: 'KGP: ${data.kgpVersion}, AGP: ${data.agpVersion}',
);
});
}
});
group('Parse gradle version from distribution url', () {
testWithoutContext('null distribution url returns null version', () {
expect(parseGradleVersionFromDistributionUrl(null), null);
@ -1373,6 +1620,20 @@ class GradleAgpTestData {
final bool validPair;
}
class GradleKgpTestData {
GradleKgpTestData(this.validPair, {this.gradleVersion, this.kgpVersion});
final String? gradleVersion;
final String? kgpVersion;
final bool validPair;
}
class KgpAgpTestData {
KgpAgpTestData(this.validPair, {this.agpVersion, this.kgpVersion});
final String? agpVersion;
final String? kgpVersion;
final bool validPair;
}
class JavaGradleTestData {
JavaGradleTestData(this.validPair, {this.javaVersion, this.gradleVersion});
final String? gradleVersion;

View File

@ -576,7 +576,7 @@ dependencies {
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
java = FakeJava(version: Version(17, 0, 2));
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand('1.9.20')]);
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync();
@ -604,7 +604,7 @@ dependencies {
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
java = FakeJava(version: const Version.withText(1, 8, 0, '1.8.0_242'));
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand('1.7.20')]);
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync();
@ -632,7 +632,7 @@ dependencies {
final AndroidStudio androidStudio;
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand('1.9.1')]);
java = FakeJava(version: Version(11, 0, 14));
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
@ -658,13 +658,14 @@ dependencies {
const String javaV = '17.0.2';
const String gradleV = '6.7.3';
const String agpV = '7.2.0';
const String kgpV = '2.1.0';
final FakeProcessManager processManager;
final Java java;
final AndroidStudio androidStudio;
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]);
java = FakeJava(version: Version.parse(javaV));
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
@ -682,7 +683,7 @@ dependencies {
// Should not have the valid string
expect(
value.description,
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpString))),
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))),
);
// On gradle/agp error print help url and gradle and agp versions.
expect(value.description, contains(RegExp(AndroidProject.gradleAgpCompatUrl)));
@ -692,6 +693,15 @@ dependencies {
expect(value.description, contains(RegExp(AndroidProject.javaGradleCompatUrl)));
expect(value.description, contains(RegExp(javaV)));
expect(value.description, contains(RegExp(gradleV)));
// On kgp/gradle eror print help url and kgp versions
expect(value.description, contains(RegExp(kgpV)));
expect(value.description, contains(RegExp('KGP/Gradle')));
expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl)));
// On agp/kgp error print help url and agp and kgp versions
expect(value.description, contains(RegExp(agpV)));
expect(value.description, contains(RegExp(kgpV)));
expect(value.description, contains(RegExp('AGP/KGP')));
expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl)));
},
java: java,
androidStudio: androidStudio,
@ -703,13 +713,14 @@ dependencies {
const String javaV = '17.0.2';
const String gradleV = '6.7.3';
const String agpV = '4.2.0';
const String kgpV = '1.7.22';
final FakeProcessManager processManager;
final Java java;
final AndroidStudio androidStudio;
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]);
java = FakeJava(version: Version(17, 0, 2));
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
@ -727,7 +738,7 @@ dependencies {
// Should not have the valid string.
expect(
value.description,
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpString))),
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))),
);
// On gradle/agp error print help url and java and gradle versions.
expect(value.description, contains(RegExp(AndroidProject.javaGradleCompatUrl)));
@ -747,7 +758,7 @@ dependencies {
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
java = FakeJava(version: Version(11, 0, 2));
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.any();
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync();
@ -766,7 +777,7 @@ dependencies {
// Should not have the valid string.
expect(
value.description,
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpString))),
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))),
);
// On gradle/agp error print help url and gradle and agp versions.
expect(value.description, contains(RegExp(AndroidProject.gradleAgpCompatUrl)));
@ -779,6 +790,89 @@ dependencies {
androidSdk: androidSdk,
);
});
group('_', () {
const String gradleV = '8.11';
const String agpV = '8.7.2';
const String kgpV = '2.1.10';
final FakeProcessManager processManager;
final Java java;
final AndroidStudio androidStudio;
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]);
java = FakeJava(version: Version(17, 0, 2));
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync();
_testInMemory(
'incompatible kgp/gradle only',
() async {
final FlutterProject? project = await configureGradleAgpForTest(
gradleV: gradleV,
agpV: agpV,
);
final CompatibilityResult value =
await project!.android.hasValidJavaGradleAgpVersions();
expect(value.success, isFalse);
// Should not have the valid string.
expect(
value.description,
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))),
);
// On gradle/agp error print help url and java and gradle versions.
expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl)));
expect(value.description, contains(RegExp(kgpV)));
expect(value.description, contains(RegExp(gradleV)));
},
java: java,
androidStudio: androidStudio,
processManager: processManager,
androidSdk: androidSdk,
);
});
group('_', () {
const String gradleV = '8.9';
const String agpV = '8.7.2';
const String kgpV = '2.0.20';
final FakeProcessManager processManager;
final Java java;
final AndroidStudio androidStudio;
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
processManager = FakeProcessManager.list(<FakeCommand>[createKgpVersionCommand(kgpV)]);
java = FakeJava(version: Version(17, 0, 2));
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync();
_testInMemory(
'incompatible agp/kgp only',
() async {
final FlutterProject? project = await configureGradleAgpForTest(
gradleV: gradleV,
agpV: agpV,
);
final CompatibilityResult value =
await project!.android.hasValidJavaGradleAgpVersions();
expect(value.success, isFalse);
// Should not have the valid string.
expect(
value.description,
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))),
);
// On gradle/agp error print help url and java and gradle versions.
expect(value.description, contains(RegExp(kgpV)));
expect(value.description, contains(RegExp(agpV)));
expect(value.description, contains(RegExp('AGP/KGP')));
expect(value.description, contains(RegExp(AndroidProject.kgpCompatUrl)));
},
java: java,
androidStudio: androidStudio,
processManager: processManager,
androidSdk: androidSdk,
);
});
group('_', () {
final FakeProcessManager processManager;
final Java java;
@ -786,7 +880,7 @@ dependencies {
final FakeAndroidSdkWithDir androidSdk;
final FileSystem fileSystem = getFileSystemForPlatform();
java = FakeJava(version: Version(11, 0, 2));
processManager = FakeProcessManager.empty();
processManager = FakeProcessManager.any();
androidStudio = FakeAndroidStudio();
androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory);
fileSystem.currentDirectory.childDirectory(androidStudio.javaPath!).createSync();
@ -804,7 +898,7 @@ dependencies {
// Should not have the valid string.
expect(
value.description,
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpString))),
isNot(contains(RegExp(AndroidProject.validJavaGradleAgpKgpString))),
);
// On gradle/agp error print help url null value for agp.
expect(value.description, contains(RegExp(AndroidProject.gradleAgpCompatUrl)));
@ -1979,6 +2073,15 @@ flutter:
return FlutterProject.fromDirectory(directory);
}
FakeCommand createKgpVersionCommand(String kgpV) {
return FakeCommand(
command: const <String>['./gradlew', 'kgpVersion', '-q'],
stdout: '''
KGP Version: $kgpV
''',
);
}
/// Executes the [testMethod] in a context where the file system
/// is in memory.
@isTest

View File

@ -51,15 +51,15 @@ void main() {
const String expected =
'\n'
'┌───────────────────────────────────────────────────────────────────\n'
'│ General Info \n'
'│ [✓] App Name: flutter_gallery \n'
'│ [✓] Supported Platforms: android, ios, web, macos, linux, windows \n'
'│ [✓] Is Flutter Package: yes \n'
'│ [✓] Uses Material Design: yes \n'
'│ [✓] Is Plugin: no \n'
'│ [✓] Java/Gradle/Android Gradle Plugin: ${AndroidProject.validJavaGradleAgpString}\n'
'└───────────────────────────────────────────────────────────────────\n';
'┌───────────────────────────────────────────────────────────────────────────\n'
'│ General Info \n'
'│ [✓] App Name: flutter_gallery \n'
'│ [✓] Supported Platforms: android, ios, web, macos, linux, windows \n'
'│ [✓] Is Flutter Package: yes \n'
'│ [✓] Uses Material Design: yes \n'
'│ [✓] Is Plugin: no \n'
'│ [✓] Java/Gradle/KGP/Android Gradle Plugin: ${AndroidProject.validJavaGradleAgpKgpString}\n'
'└───────────────────────────────────────────────────────────────────────────\n';
expect(loggerTest.statusText, contains(expected));
});