From 4d21a026e59d672d5b4d01ac4c377dbee5d0968e Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Thu, 2 Nov 2023 17:14:58 -0400 Subject: [PATCH] Protect flutter analyze --suggestions from erroring on missing AGP value. (#137719) Fixes #137600 Protect flutter analyze --suggestions from null error when AGP value is missing Update template with a reference to new agp definition location Look for AGP version being set in settings.gradle (change to templates happened in https://github.com/flutter/flutter/commit/dbe0ccd8853dabdcb0f94ce2cdef892a1b10e48a#diff-20537fb84ee37894a3f3d9723a06bcf2674290ee25aa83332c2524a1f7546a6d --- .../lib/src/android/gradle_utils.dart | 62 ++++++++++++++----- packages/flutter_tools/lib/src/project.dart | 11 +++- .../android-java.tmpl/build.gradle.tmpl | 1 + .../android-kotlin.tmpl/build.gradle.tmpl | 1 + .../android/gradle_utils_test.dart | 45 ++++++++++++++ .../test/general.shard/project_test.dart | 42 +++++++++++++ 6 files changed, 144 insertions(+), 18 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 78c9e16ba1cb..a0f1dd8fc979 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -71,19 +71,31 @@ const String maxKnownAgpVersion = '8.3'; // compatible Java version. const String oldestDocumentedJavaAgpCompatibilityVersion = '4.2'; +// Constant used in [_buildAndroidGradlePluginRegExp] and +// [_settingsAndroidGradlePluginRegExp] to identify the version section. +const String _versionGroupName = 'version'; + +// AGP can be defined in build.gradle // Expected content: // "classpath 'com.android.tools.build:gradle:7.3.0'" -// Parentheticals are use to group which helps with version extraction. -// "...build:gradle:(...)" where group(1) should be the version string. -final RegExp _androidGradlePluginRegExp = - RegExp(r'com\.android\.tools\.build:gradle:(\d+\.\d+\.\d+)'); +// ? is used to name the version group which helps with extraction. +final RegExp _buildAndroidGradlePluginRegExp = + RegExp(r'com\.android\.tools\.build:gradle:(?\d+\.\d+\.\d+)'); + +// AGP can be defined in settings.gradle. +// Expected content: +// "id "com.android.application" version "{{agpVersion}}"" +// ? is used to name the version group which helps with extraction. +final RegExp _settingsAndroidGradlePluginRegExp = RegExp( + r'^\s+id\s+"com.android.application"\s+version\s+"(?\d+\.\d+\.\d+)"', + 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' // '^\s*' protects against commented out lines. final RegExp distributionUrlRegex = - RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true); + RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true); // Modified version of the gradle distribution url match designed to only match // gradle.org urls so that we can guarantee any modifications to the url @@ -199,7 +211,7 @@ String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) { return templateDefaultGradleVersion; } final String buildFileContent = buildFile.readAsStringSync(); - final Iterable pluginMatches = _androidGradlePluginRegExp.allMatches(buildFileContent); + final Iterable pluginMatches = _buildAndroidGradlePluginRegExp.allMatches(buildFileContent); if (pluginMatches.isEmpty) { logger.printTrace("$buildFile doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion"); return templateDefaultGradleVersion; @@ -309,8 +321,9 @@ OS: Mac OS X 13.2.1 aarch64 /// Returns the Android Gradle Plugin (AGP) version that the current project /// depends on when found, null otherwise. /// -/// The Android plugin version is specified in the [build.gradle] file within -/// the project's Android directory ([androidDirectory]). +/// The Android plugin version is specified in the [build.gradle] or +/// [settings.gradle] file within the project's +/// Android directory ([androidDirectory]). String? getAgpVersion(Directory androidDirectory, Logger logger) { final File buildFile = androidDirectory.childFile('build.gradle'); if (!buildFile.existsSync()) { @@ -318,15 +331,34 @@ String? getAgpVersion(Directory androidDirectory, Logger logger) { return null; } final String buildFileContent = buildFile.readAsStringSync(); - final Iterable pluginMatches = - _androidGradlePluginRegExp.allMatches(buildFileContent); - if (pluginMatches.isEmpty) { - logger.printTrace("$buildFile doesn't provide an AGP version"); + final RegExpMatch? buildMatch = + _buildAndroidGradlePluginRegExp.firstMatch(buildFileContent); + if (buildMatch != null) { + final String? androidPluginVersion = + buildMatch.namedGroup(_versionGroupName); + logger.printTrace('$buildFile provides AGP version: $androidPluginVersion'); + return androidPluginVersion; + } + logger.printTrace( + "$buildFile doesn't provide an AGP version. Checking settings."); + final File settingsFile = androidDirectory.childFile('settings.gradle'); + if (!settingsFile.existsSync()) { + logger.printTrace('$settingsFile does not exist.'); return null; } - final String? androidPluginVersion = pluginMatches.first.group(1); - logger.printTrace('$buildFile provides AGP version: $androidPluginVersion'); - return androidPluginVersion; + final String settingsFileContent = settingsFile.readAsStringSync(); + final RegExpMatch? settingsMatch = + _settingsAndroidGradlePluginRegExp.firstMatch(settingsFileContent); + + if (settingsMatch != null) { + final String? androidPluginVersion = + settingsMatch.namedGroup(_versionGroupName); + logger.printTrace( + '$settingsFile provides AGP version: $androidPluginVersion'); + return androidPluginVersion; + } + logger.printTrace("$settingsFile doesn't provide an AGP version."); + return null; } String _formatParseWarning(String content) { diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 7cffc7583b6e..c8d9bbf0a0c1 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -608,15 +608,20 @@ class AndroidProject extends FlutterProjectPlatform { final bool compatibleGradleAgp = gradle.validateGradleAndAgp(globals.logger, gradleV: gradleVersion, agpV: agpVersion); - final bool compatibleJavaGradle = gradle.validateJavaAndGradle(globals.logger, - javaV: javaVersion, gradleV: gradleVersion); + final bool compatibleJavaGradle = gradle.validateJavaAndGradle( + globals.logger, + javaV: javaVersion, + gradleV: gradleVersion); // Begin description formatting. if (!compatibleGradleAgp) { + final String gradleDescription = agpVersion != null + ? 'Update Gradle to at least "${gradle.getGradleVersionFor(agpVersion)}".' + : ''; description = ''' Incompatible Gradle/AGP versions. \n Gradle Version: $gradleVersion, AGP Version: $agpVersion -Update Gradle to at least "${gradle.getGradleVersionFor(agpVersion!)}".\n +$gradleDescription\n See the link below for more information: $gradleAgpCompatUrl '''; diff --git a/packages/flutter_tools/templates/app_shared/android-java.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/app_shared/android-java.tmpl/build.gradle.tmpl index 40c56e3454e0..3a967a317945 100644 --- a/packages/flutter_tools/templates/app_shared/android-java.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/app_shared/android-java.tmpl/build.gradle.tmpl @@ -6,6 +6,7 @@ buildscript { } dependencies { + // AGP version is set in settings.gradle. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/flutter_tools/templates/app_shared/android-kotlin.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/app_shared/android-kotlin.tmpl/build.gradle.tmpl index 40c56e3454e0..3a967a317945 100644 --- a/packages/flutter_tools/templates/app_shared/android-kotlin.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/app_shared/android-kotlin.tmpl/build.gradle.tmpl @@ -6,6 +6,7 @@ buildscript { } dependencies { + // AGP version is set in settings.gradle. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart index e789afaa8d02..c570416e5c7e 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart @@ -469,6 +469,51 @@ allprojects { ); }); + testWithoutContext('returns the AGP version when in settings', () async { + final Directory androidDirectory = fileSystem.directory('/android') + ..createSync(); + // File must exist and can not have agp defined. + androidDirectory.childFile('build.gradle').writeAsStringSync(r''); + androidDirectory.childFile('settings.gradle').writeAsStringSync(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 + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + // Decoy value to ensure we ignore commented out lines. + // id "com.android.application" version "6.1.0" apply false + id "com.android.application" version "7.3.0" apply false +} + +include ":app" +'''); + + expect( + getAgpVersion(androidDirectory, BufferLogger.test()), + '7.3.0', + ); + }); + group('validates gradle/agp versions', () { final List testData = [ // Values too new *these need to be updated* when diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index 2cd3ca26f8bf..8de846927bc8 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -659,6 +659,48 @@ dependencies { androidSdk: androidSdk, ); }); + group('_', () { + final FakeProcessManager processManager; + final Java java; + final AndroidStudio androidStudio; + final FakeAndroidSdkWithDir androidSdk; + final FileSystem fileSystem = getFileSystemForPlatform(); + java = FakeJava(version: Version(11, 0, 2)); + processManager = FakeProcessManager.empty(); + androidStudio = FakeAndroidStudio(); + androidSdk = + FakeAndroidSdkWithDir(fileSystem.currentDirectory); + fileSystem.currentDirectory + .childDirectory(androidStudio.javaPath!) + .createSync(); + _testInMemory( + 'null agp only', + () async { + const String gradleV = '7.0.3'; + final FlutterProject? project = await configureGradleAgpForTest( + gradleV: gradleV, + 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.validJavaGradleAgpString)))); + // On gradle/agp error print help url null value for agp. + expect(value.description, + contains(RegExp(AndroidProject.gradleAgpCompatUrl))); + expect(value.description, contains(RegExp(gradleV))); + expect(value.description, contains(RegExp('null'))); + }, + java: java, + androidStudio: androidStudio, + processManager: processManager, + androidSdk: androidSdk, + ); + }); }); group('language', () {