diff --git a/packages/flutter_tools/gradle/build.gradle.kts b/packages/flutter_tools/gradle/build.gradle.kts index e14f1c37e1..c2ce5fff21 100644 --- a/packages/flutter_tools/gradle/build.gradle.kts +++ b/packages/flutter_tools/gradle/build.gradle.kts @@ -28,8 +28,9 @@ gradlePlugin { 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 1546c1da0e..5d3ad4ef56 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 @@ buildscript { } 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 @@ class FlutterPlugin implements Plugin { 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") @@ -1515,6 +1533,8 @@ abstract class BaseFlutterTask extends DefaultTask { @Optional @Input Boolean validateDeferredComponents + @Optional @Input + Boolean skipDependencyChecks @Optional @Input String flavor 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 0000000000..df14277258 --- /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() + +class FlutterDependencyCheckerPlugin : Plugin { + 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=`. \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:'\"." + } + + 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 { + companion object { + fun fromString(version : String) : Version { + val asList : List = version.split(".") + val convertedToNumbers : List = 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 a6b9580b11..a6845f4261 100644 --- a/packages/flutter_tools/lib/src/android/README.md +++ b/packages/flutter_tools/lib/src/android/README.md @@ -29,6 +29,11 @@ version. 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 1b60f4eb19..12a92d4bd8 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -370,6 +370,9 @@ class AndroidGradleBuilder implements AndroidBuilder { 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 59e2459262..feca4d7931 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -41,6 +41,7 @@ class BuildInfo { 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 @@ class BuildInfo { /// 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 androidProjectArgs; diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 706737695e..87f2f0e40d 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 @@ abstract final class FlutterOptions { 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 @@ abstract class FlutterCommand extends Command { 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 @@ abstract class FlutterCommand extends Command { final bool androidGradleDaemon = !argParser.options.containsKey(FlutterOptions.kAndroidGradleDaemon) || boolArg(FlutterOptions.kAndroidGradleDaemon); + final bool androidSkipBuildDependencyValidation = !argParser.options.containsKey(FlutterOptions.kAndroidSkipBuildDependencyValidation) + || boolArg(FlutterOptions.kAndroidSkipBuildDependencyValidation); + final List androidProjectArgs = argParser.options.containsKey(FlutterOptions.kAndroidProjectArgs) ? stringsArg(FlutterOptions.kAndroidProjectArgs) : []; @@ -1316,6 +1326,7 @@ abstract class FlutterCommand extends Command { 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 0000000000..1906753b04 --- /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([ + 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([ + 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([ + 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([ + 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([ + 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([ + 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. +}