Enforce a policy on supported Gradle, Java, AGP, and KGP versions (#142000)
Policy per https://flutter.dev/go/android-dependency-versions.
https://github.com/flutter/flutter/issues/140913
~Still a WIP while I clean up some error handling, remove some prints, and figure out a Java test (more difficult than the others because I believe we can only install one java version per ci shard).~
~Also it looks like there are errors that I need to fix when this checking is applied to a project that uses the old way of applying AGP/KGP using the top-level `build.gradle` file (instead of the new template way of applying them in the `settings.gradle` file).~ Done, this is why [these lines exist](https://github.com/flutter/flutter/blob/9af6bae6b9a8d7a8a363467b834f52bfc64c9336/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy#L72-L88) in `flutter.groovy`. They just needed to be added
diff --git a/packages/flutter_tools/gradle/build.gradle.kts b/packages/flutter_tools/gradle/build.gradle.kts
index e14f1c3..c2ce5ff 100644
--- a/packages/flutter_tools/gradle/build.gradle.kts
+++ b/packages/flutter_tools/gradle/build.gradle.kts
@@ -28,8 +28,9 @@
dependencies {
// When bumping, also update:
- // * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/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/groovy/flutter.groovy
+ // * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
// * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart
- // * AGP version in buildscript block in packages/flutter_tools/gradle/src/main/flutter.groovy
compileOnly("com.android.tools.build:gradle:7.3.0")
}
diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
index 1546c1d..5d3ad4e 100644
--- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
@@ -113,7 +113,8 @@
}
dependencies {
// When bumping, also update:
- // * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/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/dependency_version_checker.gradle.kts
// * 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")
@@ -321,6 +322,23 @@
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile()
+ // Validate that the provided Gradle, Java, AGP, and KGP versions are all within our
+ // supported range.
+ final Boolean shouldSkipDependencyChecks = project.hasProperty("skipDependencyChecks")
+ && project.getProperty("skipDependencyChecks");
+ if (!shouldSkipDependencyChecks) {
+ try {
+ final String dependencyCheckerPluginPath = Paths.get(flutterRoot.absolutePath,
+ "packages", "flutter_tools", "gradle", "src", "main", "kotlin",
+ "dependency_version_checker.gradle.kts")
+ project.apply from: dependencyCheckerPluginPath
+ } catch (Exception ignored) {
+ project.logger.error("Warning: Flutter was unable to detect project Gradle, Java, " +
+ "AGP, and KGP versions. Skipping dependency version checking. Error was: "
+ + ignored)
+ }
+ }
+
// Use Kotlin DSL to handle baseApplicationName logic due to Groovy dynamic dispatch bug.
project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "kotlin", "flutter.gradle.kts")
@@ -1516,6 +1534,8 @@
Boolean validateDeferredComponents
@Optional @Input
+ Boolean skipDependencyChecks
+ @Optional @Input
String flavor
@OutputFiles
diff --git a/packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts b/packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
new file mode 100644
index 0000000..df14277
--- /dev/null
+++ b/packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
@@ -0,0 +1,330 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import org.gradle.api.JavaVersion
+import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
+
+// This buildscript block supplies dependencies for this file's own import
+// declarations above. It exists solely for compatibility with projects that
+// have not migrated to declaratively apply the Flutter Gradle Plugin;
+// for those that have, FGP's `build.gradle.kts` takes care of this.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ // When bumping, also update:
+ // * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+ // * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+ // * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart
+ // * AGP version in dependencies block in packages/flutter_tools/gradle/build.gradle.kts
+ classpath("com.android.tools.build:gradle:7.3.0")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
+ }
+}
+
+apply<FlutterDependencyCheckerPlugin>()
+
+class FlutterDependencyCheckerPlugin : Plugin<Project> {
+ override fun apply(project: Project) {
+ DependencyVersionChecker.checkDependencyVersions(project)
+ }
+}
+
+
+class DependencyVersionChecker {
+ companion object {
+ private const val GRADLE_NAME : String = "Gradle"
+ private const val JAVA_NAME : String = "Java"
+ private const val AGP_NAME : String = "Android Gradle Plugin"
+ private const val KGP_NAME : String = "Kotlin"
+
+ // The following messages represent best effort guesses at where a Flutter developer should
+ // look to upgrade a dependency that is below the corresponding threshold. Developers can
+ // change some of these locations, so they are not guaranteed to be accurate.
+ private fun getPotentialGradleFix(projectDirectory : String) : String {
+ return "Your project's gradle version is typically " +
+ "defined in the gradle wrapper file. By default, this can be found at " +
+ "$projectDirectory/gradle/wrapper/gradle-wrapper.properties. \n" +
+ "For more information, see https://docs.gradle.org/current/userguide/gradle_wrapper.html."
+ }
+
+ // 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`."
+
+ private fun getPotentialAGPFix(projectDirectory : String) : String {
+ return "Your project's AGP version is typically " +
+ "defined the plugins block of the `settings.gradle` file " +
+ "($projectDirectory/settings.gradle), by a plugin with the id of " +
+ "com.android.application. \nIf you don't see a plugins block, your project " +
+ "was likely created with an older template version. In this case it is most " +
+ "likely defined in the top-level build.gradle file " +
+ "($projectDirectory/build.gradle) by the following line in the dependencies" +
+ " block of the buildscript: \"classpath 'com.android.tools.build:gradle:<version>'\"."
+ }
+
+ private fun getPotentialKGPFix(projectDirectory : String) : String {
+ return "Your project's KGP version is typically " +
+ "defined the plugins block of the `settings.gradle` file " +
+ "($projectDirectory/settings.gradle), by a plugin with the id of " +
+ "org.jetbrains.kotlin.android. \nIf you don't see a plugins block, your project " +
+ "was likely created with an older template version, in which case it is most " +
+ "likely defined in the top-level build.gradle file " +
+ "($projectDirectory/build.gradle) by the ext.kotlin_version property."
+ }
+
+ // The following versions define our support policy for Gradle, Java, AGP, and KGP.
+ // All "error" versions are currently set to 0 as this policy is new. They will be increased
+ // to match the current values of the "warn" versions in the next release.
+ // Before updating any "error" version, ensure that you have updated the corresponding
+ // "warn" version for a full release to provide advanced warning. See
+ // flutter.dev/go/android-dependency-versions for more.
+ // TODO(gmackall): https://github.com/flutter/flutter/issues/142653.
+ val warnGradleVersion : Version = Version(7,0,2)
+ val errorGradleVersion : Version = Version(0,0,0)
+
+ val warnJavaVersion : JavaVersion = JavaVersion.VERSION_11
+ val errorJavaVersion : JavaVersion = JavaVersion.VERSION_1_1
+
+ val warnAGPVersion : Version = Version(7,0,0)
+ val errorAGPVersion : Version = Version(0,0,0)
+
+ val warnKGPVersion : Version = Version(1,5,0)
+ val errorKGPVersion : Version = Version(0,0,0)
+
+ /**
+ * Checks if the project's Android build time dependencies are each within the respective
+ * version range that we support. When we can't find a version for a given dependency
+ * we treat it as within the range for the purpose of this check.
+ */
+ fun checkDependencyVersions(project : Project) {
+ var agpVersion : Version? = null
+ var kgpVersion : Version? = null
+
+ checkGradleVersion(getGradleVersion(project), project)
+ checkJavaVersion(getJavaVersion(project), project)
+ agpVersion = getAGPVersion(project)
+ if (agpVersion != null) {
+ checkAGPVersion(agpVersion, project)
+ } else {
+ project.logger.error("Warning: unable to detect project AGP version. Skipping " +
+ "version checking. ")
+ }
+
+ kgpVersion = getKGPVersion(project)
+ if (kgpVersion != null) {
+ checkKGPVersion(kgpVersion, project)
+ } else {
+ project.logger.error("Warning: unable to detect project KGP version. Skipping " +
+ "version checking.")
+ }
+ }
+
+ // https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.invocation/-gradle/index.html#-837060600%2FFunctions%2F-1793262594
+ fun getGradleVersion(project : Project) : Version {
+ val untrimmedGradleVersion : String = project.gradle.getGradleVersion()
+ // Trim to handle candidate gradle versions (example 7.6-rc-4). This means we treat all
+ // candidate versions of gradle as the same as their base version
+ // (i.e., "7.6"="7.6-rc-4").
+ return Version.fromString(untrimmedGradleVersion.substringBefore('-'))
+ }
+
+ // https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api/-java-version/index.html#-1790786897%2FFunctions%2F-1793262594
+ fun getJavaVersion(project : Project) : JavaVersion {
+ return JavaVersion.current()
+ }
+
+ // This approach is taken from AGP's own version checking plugin:
+ // https://android.googlesource.com/platform/tools/base/+/1839aa23b8dc562005e2f0f0cc8e8b4c5caa37d0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/agpVersionChecker.kt#58.
+ fun getAGPVersion(project: Project): Version? {
+ val agpPluginName : String = "com.android.base";
+ val agpVersionFieldName : String = "ANDROID_GRADLE_PLUGIN_VERSION"
+ var agpVersion: Version? = null
+ try {
+ agpVersion = Version.fromString(
+ project.plugins.getPlugin(agpPluginName)::class.java.classLoader.loadClass(
+ com.android.Version::class.java.name
+ ).fields.find { it.name == agpVersionFieldName }!!
+ .get(null) as String
+ )
+ } catch (ignored: ClassNotFoundException) {
+ // Use deprecated Version class as it exists in older AGP (com.android.Version) does
+ // not exist in those versions.
+ agpVersion = Version.fromString(
+ project.plugins.getPlugin(agpPluginName)::class.java.classLoader.loadClass(
+ com.android.builder.model.Version::class.java.name
+ ).fields.find { it.name == agpVersionFieldName }!!
+ .get(null) as String
+ )
+ }
+ return agpVersion
+ }
+
+ fun getKGPVersion(project : Project) : Version? {
+ val kotlinVersionProperty : String = "kotlin_version"
+ val firstKotlinVersionFieldName : String = "pluginVersion"
+ val secondKotlinVersionFieldName : String = "kotlinPluginVersion"
+ // This property corresponds to application of the Kotlin Gradle plugin in the
+ // top-level build.gradle file.
+ if (project.hasProperty(kotlinVersionProperty)) {
+ return Version.fromString(project.properties.get(kotlinVersionProperty) as String)
+ }
+ val kotlinPlugin = project.getPlugins()
+ .findPlugin(KotlinAndroidPluginWrapper::class.java)
+ val versionfield =
+ kotlinPlugin?.javaClass?.kotlin?.members?.first { it.name == firstKotlinVersionFieldName || it.name == secondKotlinVersionFieldName }
+ val versionString = versionfield?.call(kotlinPlugin)
+ if (versionString == null) {
+ return null
+ } else {
+ return Version.fromString(versionString!! as String)
+ }
+ }
+
+ private fun getErrorMessage(dependencyName : String,
+ versionString : String,
+ errorVersion : String,
+ potentialFix : String) : String {
+ return "Error: Your project's $dependencyName version ($versionString) is lower " +
+ "than Flutter's minimum supported version of $errorVersion. Please upgrade " +
+ "your $dependencyName version. \nAlternatively, use the flag " +
+ "\"--android-skip-build-dependency-validation\" to bypass this check.\n\n" +
+ "Potential fix: $potentialFix"
+ }
+
+ private fun getWarnMessage(dependencyName : String,
+ versionString : String,
+ warnVersion : String,
+ potentialFix : String) : String {
+ return "Warning: Flutter support for your project's $dependencyName version " +
+ "($versionString) will soon be dropped. Please upgrade your $dependencyName " +
+ "version to a version of at least $warnVersion soon." +
+ "\nAlternatively, use the flag \"--android-skip-build-dependency-validation\"" +
+ " to bypass this check.\n\n Potential fix: $potentialFix"
+ }
+
+ fun checkGradleVersion(version : Version, project : Project) {
+ if (version < errorGradleVersion) {
+ val errorMessage : String = getErrorMessage(
+ GRADLE_NAME,
+ version.toString(),
+ errorGradleVersion.toString(),
+ getPotentialGradleFix(project.getRootDir().getPath())
+ )
+ throw GradleException(errorMessage)
+ }
+ else if (version < warnGradleVersion) {
+ val warnMessage : String = getWarnMessage(
+ GRADLE_NAME,
+ version.toString(),
+ warnGradleVersion.toString(),
+ getPotentialGradleFix(project.getRootDir().getPath())
+ )
+ project.logger.error(warnMessage)
+ }
+ }
+
+ fun checkJavaVersion(version : JavaVersion, project : Project) {
+ if (version < errorJavaVersion) {
+ val errorMessage : String = getErrorMessage(
+ JAVA_NAME,
+ version.toString(),
+ errorJavaVersion.toString(),
+ POTENTIAL_JAVA_FIX
+ )
+ throw GradleException(errorMessage)
+ }
+ else if (version < warnJavaVersion) {
+ val warnMessage : String = getWarnMessage(
+ JAVA_NAME,
+ version.toString(),
+ warnJavaVersion.toString(),
+ POTENTIAL_JAVA_FIX
+ )
+ project.logger.error(warnMessage)
+ }
+ }
+
+ fun checkAGPVersion(version : Version, project : Project) {
+ if (version < errorAGPVersion) {
+ val errorMessage : String = getErrorMessage(
+ AGP_NAME,
+ version.toString(),
+ errorAGPVersion.toString(),
+ getPotentialAGPFix(project.getRootDir().getPath())
+ )
+ throw GradleException(errorMessage)
+ }
+ else if (version < warnAGPVersion) {
+ val warnMessage : String = getWarnMessage(
+ AGP_NAME,
+ version.toString(),
+ warnAGPVersion.toString(),
+ getPotentialAGPFix(project.getRootDir().getPath())
+ )
+ project.logger.error(warnMessage)
+ }
+ }
+
+ fun checkKGPVersion(version : Version, project : Project) {
+ if (version < errorKGPVersion) {
+ val errorMessage : String = getErrorMessage(
+ KGP_NAME,
+ version.toString(),
+ errorKGPVersion.toString(),
+ getPotentialKGPFix(project.getRootDir().getPath())
+ )
+ throw GradleException(errorMessage)
+ }
+ else if (version < warnKGPVersion) {
+ val warnMessage : String = getWarnMessage(
+ KGP_NAME,
+ version.toString(),
+ warnKGPVersion.toString(),
+ getPotentialKGPFix(project.getRootDir().getPath())
+ )
+ project.logger.error(warnMessage)
+ }
+ }
+ }
+}
+
+
+// Helper class to parse the versions that are provided as plain strings (Gradle, Kotlin) and
+// perform easy comparisons. All versions will have a major, minor, and patch value. These values
+// default to 0 when they are not provided or are otherwise unparseable.
+// For example the version strings "8.2", "8.2.2hfd", and "8.2.0" would parse to the same version.
+class Version(val major : Int, val minor : Int, val patch : Int) : Comparable<Version> {
+ companion object {
+ fun fromString(version : String) : Version {
+ val asList : List<String> = version.split(".")
+ val convertedToNumbers : List<Int> = asList.map {it.toIntOrNull() ?: 0}
+ return Version(
+ major = convertedToNumbers.getOrElse(0, {0}),
+ minor = convertedToNumbers.getOrElse(1, {0}),
+ patch = convertedToNumbers.getOrElse(2, {0})
+ )
+ }
+ }
+ override fun compareTo(otherVersion : Version) : Int {
+ if (major != otherVersion.major) {
+ return major - otherVersion.major
+ }
+ if (minor != otherVersion.minor) {
+ return minor - otherVersion.minor
+ }
+ if (patch != otherVersion.patch) {
+ return patch - otherVersion.patch
+ }
+ return 0
+ }
+ override fun toString() : String {
+ return major.toString() + "." + minor.toString() + "." + patch.toString()
+ }
+}
diff --git a/packages/flutter_tools/lib/src/android/README.md b/packages/flutter_tools/lib/src/android/README.md
index a6b9580..a6845f4 100644
--- a/packages/flutter_tools/lib/src/android/README.md
+++ b/packages/flutter_tools/lib/src/android/README.md
@@ -29,6 +29,11 @@
SDK versions are updated (you should see these fail if you do not fix them
preemptively).
+Also, make sure to also update to the same version in the following places:
+- The version in the buildscript block in `packages/flutter_tools/gradle/src/main/groovy/flutter.groovy`.
+- The version in the buildscript block in `packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts`.
+- The version in the dependencies block in `packages/flutter_tools/gradle/build.gradle.kts`.
+
#### Gradle
When updating the Gradle version used in project templates
(`templateDefaultGradleVersion`), make sure that:
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 1b60f4e..12a92d4 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -370,6 +370,9 @@
if (!buildInfo.androidGradleDaemon) {
command.add('--no-daemon');
}
+ if (buildInfo.androidSkipBuildDependencyValidation) {
+ command.add('-PskipDependencyChecks=true');
+ }
final LocalEngineInfo? localEngineInfo = _artifacts.localEngineInfo;
if (localEngineInfo != null) {
final Directory localEngineRepo = _getLocalEngineRepo(
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 59e2459..feca4d7 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -41,6 +41,7 @@
this.nullSafetyMode = NullSafetyMode.sound,
this.codeSizeDirectory,
this.androidGradleDaemon = true,
+ this.androidSkipBuildDependencyValidation = false,
this.packageConfig = PackageConfig.empty,
this.initializeFromDill,
this.assumeInitializeFromDillUpToDate = false,
@@ -153,6 +154,10 @@
/// The Gradle daemon may also be disabled in the Android application's properties file.
final bool androidGradleDaemon;
+ /// Whether to skip checking of individual versions of our Android build time
+ /// dependencies.
+ final bool androidSkipBuildDependencyValidation;
+
/// Additional key value pairs that are passed directly to the gradle project via the `-P`
/// flag.
final List<String> androidProjectArgs;
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 7067376..87f2f0e 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -147,6 +147,7 @@
static const String kAndroidGradleDaemon = 'android-gradle-daemon';
static const String kDeferredComponents = 'deferred-components';
static const String kAndroidProjectArgs = 'android-project-arg';
+ static const String kAndroidSkipBuildDependencyValidation = 'android-skip-build-dependency-validation';
static const String kInitializeFromDill = 'initialize-from-dill';
static const String kAssumeInitializeFromDillUpToDate = 'assume-initialize-from-dill-up-to-date';
static const String kNativeAssetsYamlFile = 'native-assets-yaml-file';
@@ -974,6 +975,12 @@
defaultsTo: true,
hide: hide,
);
+ argParser.addFlag(
+ FlutterOptions.kAndroidSkipBuildDependencyValidation,
+ help: 'Whether to skip version checking for Java, Gradle, '
+ 'the Android Gradle Plugin (AGP), and the Kotlin Gradle Plugin (KGP)'
+ ' during Android builds.',
+ );
argParser.addMultiOption(
FlutterOptions.kAndroidProjectArgs,
help: 'Additional arguments specified as key=value that are passed directly to the gradle '
@@ -1228,6 +1235,9 @@
final bool androidGradleDaemon = !argParser.options.containsKey(FlutterOptions.kAndroidGradleDaemon)
|| boolArg(FlutterOptions.kAndroidGradleDaemon);
+ final bool androidSkipBuildDependencyValidation = !argParser.options.containsKey(FlutterOptions.kAndroidSkipBuildDependencyValidation)
+ || boolArg(FlutterOptions.kAndroidSkipBuildDependencyValidation);
+
final List<String> androidProjectArgs = argParser.options.containsKey(FlutterOptions.kAndroidProjectArgs)
? stringsArg(FlutterOptions.kAndroidProjectArgs)
: <String>[];
@@ -1316,6 +1326,7 @@
nullSafetyMode: nullSafetyMode,
codeSizeDirectory: codeSizeDirectory,
androidGradleDaemon: androidGradleDaemon,
+ androidSkipBuildDependencyValidation: androidSkipBuildDependencyValidation,
packageConfig: packageConfig,
androidProjectArgs: androidProjectArgs,
initializeFromDill: argParser.options.containsKey(FlutterOptions.kInitializeFromDill)
diff --git a/packages/flutter_tools/test/android_preview_integration.shard/android_dependency_version_checking_test.dart b/packages/flutter_tools/test/android_preview_integration.shard/android_dependency_version_checking_test.dart
new file mode 100644
index 0000000..1906753
--- /dev/null
+++ b/packages/flutter_tools/test/android_preview_integration.shard/android_dependency_version_checking_test.dart
@@ -0,0 +1,208 @@
+// 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 'dart:io';
+
+import 'package:file/src/interface/file_system_entity.dart';
+
+import '../integration.shard/test_utils.dart';
+import '../src/common.dart';
+import '../src/context.dart';
+
+const String gradleSettingsFileContent = r'''
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }()
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "AGP_REPLACE_ME" apply false
+ id "org.jetbrains.kotlin.android" version "KGP_REPLACE_ME" apply false
+}
+
+include ":app"
+
+''';
+
+const String agpReplacementString = 'AGP_REPLACE_ME';
+const String kgpReplacementString = 'KGP_REPLACE_ME';
+
+const String gradleWrapperPropertiesFileContent = r'''
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-GRADLE_REPLACE_ME-all.zip
+
+''';
+
+const String gradleReplacementString = 'GRADLE_REPLACE_ME';
+
+// This test is currently on the preview shard (but not using the preview
+// version of Android) because it is the only one using Java 11. This test
+// requires Java 11 due to the intentionally low version of Gradle.
+void main() {
+ late Directory tempDir;
+
+ setUpAll(() async {
+ tempDir = createResolvedTempDirectorySync('run_test.');
+ });
+
+ tearDownAll(() async {
+ tryToDelete(tempDir as FileSystemEntity);
+ });
+
+ testUsingContext(
+ 'AGP version out of "warn" support band prints warning but still builds', () async {
+ // Create a new flutter project.
+ final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
+ ProcessResult result = await processManager.run(<String>[
+ flutterBin,
+ 'create',
+ 'dependency_checker_app',
+ '--platforms=android',
+ ], workingDirectory: tempDir.path);
+ expect(result, const ProcessResultMatcher());
+ const String gradleVersion = '7.5';
+ const String agpVersion = '4.2.0';
+ const String kgpVersion = '1.7.10';
+
+ final Directory app = Directory(fileSystem.path.join(tempDir.path, 'dependency_checker_app'));
+
+ // Modify gradle version to passed in version.
+ final File gradleWrapperProperties = File(fileSystem.path.join(
+ app.path, 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties'));
+ final String propertyContent = gradleWrapperPropertiesFileContent.replaceFirst(
+ gradleReplacementString,
+ gradleVersion,
+ );
+ await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
+
+ final File gradleSettings = File(fileSystem.path.join(
+ app.path, 'android', 'settings.gradle'));
+ final String settingsContent = gradleSettingsFileContent
+ .replaceFirst(agpReplacementString, agpVersion)
+ .replaceFirst(kgpReplacementString, kgpVersion);
+ await gradleSettings.writeAsString(settingsContent, flush: true);
+
+
+ // Ensure that gradle files exists from templates.
+ result = await processManager.run(<String>[
+ flutterBin,
+ 'build',
+ 'apk',
+ '--debug',
+ ], workingDirectory: app.path);
+ expect(result, const ProcessResultMatcher());
+ expect(result.stderr, contains('Please upgrade your Android Gradle '
+ 'Plugin version'));
+ });
+
+ testUsingContext(
+ 'Gradle version out of "warn" support band prints warning but still builds', () async {
+ // Create a new flutter project.
+ final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
+ ProcessResult result = await processManager.run(<String>[
+ flutterBin,
+ 'create',
+ 'dependency_checker_app',
+ '--platforms=android',
+ ], workingDirectory: tempDir.path);
+ expect(result, const ProcessResultMatcher());
+ const String gradleVersion = '7.0';
+ const String agpVersion = '4.2.0';
+ const String kgpVersion = '1.7.10';
+
+ final Directory app = Directory(fileSystem.path.join(tempDir.path, 'dependency_checker_app'));
+
+ // Modify gradle version to passed in version.
+ final File gradleWrapperProperties = File(fileSystem.path.join(
+ app.path, 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties'));
+ final String propertyContent = gradleWrapperPropertiesFileContent.replaceFirst(
+ gradleReplacementString,
+ gradleVersion,
+ );
+ await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
+
+ final File gradleSettings = File(fileSystem.path.join(
+ app.path, 'android', 'settings.gradle'));
+ final String settingsContent = gradleSettingsFileContent
+ .replaceFirst(agpReplacementString, agpVersion)
+ .replaceFirst(kgpReplacementString, kgpVersion);
+ await gradleSettings.writeAsString(settingsContent, flush: true);
+
+
+ // Ensure that gradle files exists from templates.
+ result = await processManager.run(<String>[
+ flutterBin,
+ 'build',
+ 'apk',
+ '--debug',
+ ], workingDirectory: app.path);
+ expect(result, const ProcessResultMatcher());
+ expect(result.stderr, contains('Please upgrade your Gradle version'));
+ });
+
+ testUsingContext(
+ 'Kotlin version out of "warn" support band prints warning but still builds', () async {
+ // Create a new flutter project.
+ final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
+ ProcessResult result = await processManager.run(<String>[
+ flutterBin,
+ 'create',
+ 'dependency_checker_app',
+ '--platforms=android',
+ ], workingDirectory: tempDir.path);
+ expect(result, const ProcessResultMatcher());
+ const String gradleVersion = '7.5';
+ const String agpVersion = '7.4.0';
+ const String kgpVersion = '1.4.10';
+
+ final Directory app = Directory(fileSystem.path.join(tempDir.path, 'dependency_checker_app'));
+
+ // Modify gradle version to passed in version.
+ final File gradleWrapperProperties = File(fileSystem.path.join(
+ app.path, 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties'));
+ final String propertyContent = gradleWrapperPropertiesFileContent.replaceFirst(
+ gradleReplacementString,
+ gradleVersion,
+ );
+ await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
+
+ final File gradleSettings = File(fileSystem.path.join(
+ app.path, 'android', 'settings.gradle'));
+ final String settingsContent = gradleSettingsFileContent
+ .replaceFirst(agpReplacementString, agpVersion)
+ .replaceFirst(kgpReplacementString, kgpVersion);
+ await gradleSettings.writeAsString(settingsContent, flush: true);
+
+
+ // Ensure that gradle files exists from templates.
+ result = await processManager.run(<String>[
+ flutterBin,
+ 'build',
+ 'apk',
+ '--debug',
+ ], workingDirectory: app.path);
+ expect(result, const ProcessResultMatcher());
+ expect(result.stderr, contains('Please upgrade your Kotlin version'));
+ });
+
+ // TODO(gmackall): Add tests for build blocking when the
+ // corresponding error versions are enabled.
+}