Unverified Commit e5a922fe authored by Ann Marie Mossman's avatar Ann Marie Mossman Committed by GitHub

Update AGP version validation code to support KGP and kotlin build files. (#142357)

Addresses: https://github.com/flutter/flutter/issues/141410
parent f3ee3712
...@@ -75,19 +75,25 @@ const String oldestDocumentedJavaAgpCompatibilityVersion = '4.2'; ...@@ -75,19 +75,25 @@ const String oldestDocumentedJavaAgpCompatibilityVersion = '4.2';
// [_settingsAndroidGradlePluginRegExp] to identify the version section. // [_settingsAndroidGradlePluginRegExp] to identify the version section.
const String _versionGroupName = 'version'; const String _versionGroupName = 'version';
// AGP can be defined in build.gradle // AGP can be defined in the dependencies block of [build.gradle] or [build.gradle.kts].
// Expected content: // Expected content (covers both classpath and compileOnly cases):
// "classpath 'com.android.tools.build:gradle:7.3.0'" // Groovy DSL with single quotes - 'com.android.tools.build:gradle:{{agpVersion}}'
// Groovy DSL with double quotes - "com.android.tools.build:gradle:{{agpVersion}}"
// Kotlin DSL - ("com.android.tools.build.gradle:{{agpVersion}}")
// ?<version> is used to name the version group which helps with extraction. // ?<version> is used to name the version group which helps with extraction.
final RegExp _buildAndroidGradlePluginRegExp = final RegExp _androidGradlePluginRegExpFromDependencies = RegExp(
RegExp(r'com\.android\.tools\.build:gradle:(?<version>\d+\.\d+\.\d+)'); r"""[^\/]*\s*((\bclasspath\b)|(\bcompileOnly\b))\s*\(?['"]com\.android\.tools\.build:gradle:(?<version>\d+(\.\d+){1,2})\)?""",
multiLine: true);
// AGP can be defined in settings.gradle. // AGP can be defined in the plugins block of [build.gradle],
// [build.gradle.kts], [settings.gradle], or [settings.gradle.kts].
// Expected content: // Expected content:
// "id "com.android.application" version "{{agpVersion}}"" // Groovy DSL with single quotes - id 'com.android.application' version '{{agpVersion}}'
// Groovy DSL with double quotes - id "com.android.application" version "{{agpVersion}}"
// Kotlin DSL - id("com.android.application") version "{{agpVersion}}"
// ?<version> is used to name the version group which helps with extraction. // ?<version> is used to name the version group which helps with extraction.
final RegExp _settingsAndroidGradlePluginRegExp = RegExp( final RegExp _androidGradlePluginRegExpFromId = RegExp(
r'^\s+id\s+"com.android.application"\s+version\s+"(?<version>\d+\.\d+\.\d+)"', r"""[^\/]*s*id\s*\(?['"]com\.android\.application['"]\)?\s+version\s+['"](?<version>\d+(\.\d+){1,2})\)?""",
multiLine: true); multiLine: true);
// Expected content format (with lines above and below). // Expected content format (with lines above and below).
...@@ -203,30 +209,17 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio ...@@ -203,30 +209,17 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio
/// Returns the Gradle version that the current Android plugin depends on when found, /// Returns the Gradle version that the current Android plugin depends on when found,
/// otherwise it returns a default version. /// otherwise it returns a default version.
/// ///
/// The Android plugin version is specified in the [build.gradle] file within /// The Android plugin version is specified in the [build.gradle],
/// [build.gradle.kts], [settings.gradle], or [settings.gradle.kts] file within
/// the project's Android directory. /// the project's Android directory.
String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) { String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
const String buildFileName = 'build.gradle/build.gradle.kts'; final String? androidPluginVersion = getAgpVersion(directory, logger);
if (androidPluginVersion == null) {
File buildFile = directory.childFile('build.gradle');
if (!buildFile.existsSync()) {
buildFile = directory.childFile('build.gradle.kts');
}
if (!buildFile.existsSync()) {
logger.printTrace( logger.printTrace(
"$buildFileName doesn't exist, assuming Gradle version: $templateDefaultGradleVersion"); 'AGP version cannot be determined, assuming Gradle version: $templateDefaultGradleVersion');
return templateDefaultGradleVersion;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> pluginMatches = _buildAndroidGradlePluginRegExp.allMatches(buildFileContent);
if (pluginMatches.isEmpty) {
logger.printTrace("$buildFileName doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion");
return templateDefaultGradleVersion; return templateDefaultGradleVersion;
} }
final String? androidPluginVersion = pluginMatches.first.group(1); return getGradleVersionFor(androidPluginVersion);
logger.printTrace('$buildFileName provides AGP version: $androidPluginVersion');
return getGradleVersionFor(androidPluginVersion ?? 'unknown');
} }
/// Returns the gradle file from the top level directory. /// Returns the gradle file from the top level directory.
...@@ -329,38 +322,51 @@ OS: Mac OS X 13.2.1 aarch64 ...@@ -329,38 +322,51 @@ OS: Mac OS X 13.2.1 aarch64
/// Returns the Android Gradle Plugin (AGP) version that the current project /// Returns the Android Gradle Plugin (AGP) version that the current project
/// depends on when found, null otherwise. /// depends on when found, null otherwise.
/// ///
/// The Android plugin version is specified in the [build.gradle] or /// The Android plugin version is specified in the [build.gradle],
/// [settings.gradle] file within the project's /// [build.gradle.kts], [settings.gradle] or [settings.gradle.kts]
/// Android directory ([androidDirectory]). /// file within the project's Android directory ([androidDirectory]).
String? getAgpVersion(Directory androidDirectory, Logger logger) { String? getAgpVersion(Directory androidDirectory, Logger logger) {
File buildFile = androidDirectory.childFile('build.gradle'); File buildFile = androidDirectory.childFile('build.gradle');
if (!buildFile.existsSync()) { if (!buildFile.existsSync()) {
buildFile = androidDirectory.childFile('build.gradle.kts'); buildFile = androidDirectory.childFile('build.gradle.kts');
} }
if (!buildFile.existsSync()) { if (!buildFile.existsSync()) {
logger.printTrace('Can not find build.gradle/build.gradle.kts in $androidDirectory'); logger.printTrace(
'Cannot find build.gradle/build.gradle.kts in $androidDirectory');
return null; return null;
} }
final String buildFileContent = buildFile.readAsStringSync(); final String buildFileContent = buildFile.readAsStringSync();
final RegExpMatch? buildMatch = final RegExpMatch? buildMatchClasspath =
_buildAndroidGradlePluginRegExp.firstMatch(buildFileContent); _androidGradlePluginRegExpFromDependencies.firstMatch(buildFileContent);
if (buildMatch != null) { if (buildMatchClasspath != null) {
final String? androidPluginVersion =
buildMatchClasspath.namedGroup(_versionGroupName);
logger.printTrace('$buildFile provides AGP version from classpath: $androidPluginVersion');
return androidPluginVersion;
}
final RegExpMatch? buildMatchId =
_androidGradlePluginRegExpFromId.firstMatch(buildFileContent);
if (buildMatchId != null) {
final String? androidPluginVersion = final String? androidPluginVersion =
buildMatch.namedGroup(_versionGroupName); buildMatchId.namedGroup(_versionGroupName);
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion'); logger.printTrace('$buildFile provides AGP version from plugin id: $androidPluginVersion');
return androidPluginVersion; return androidPluginVersion;
} }
logger.printTrace( logger.printTrace(
"$buildFile doesn't provide an AGP version. Checking settings."); "$buildFile doesn't provide an AGP version. Checking settings.");
final File settingsFile = androidDirectory.childFile('settings.gradle'); File settingsFile = androidDirectory.childFile('settings.gradle');
if (!settingsFile.existsSync()) {
settingsFile = androidDirectory.childFile('settings.gradle.kts');
}
if (!settingsFile.existsSync()) { if (!settingsFile.existsSync()) {
logger.printTrace('$settingsFile does not exist.'); logger.printTrace(
'Cannot find settings.gradle/settings.gradle.kts in $androidDirectory');
return null; return null;
} }
final String settingsFileContent = settingsFile.readAsStringSync(); final String settingsFileContent = settingsFile.readAsStringSync();
final RegExpMatch? settingsMatch = final RegExpMatch? settingsMatch =
_settingsAndroidGradlePluginRegExp.firstMatch(settingsFileContent); _androidGradlePluginRegExpFromId.firstMatch(settingsFileContent);
if (settingsMatch != null) { if (settingsMatch != null) {
final String? androidPluginVersion = final String? androidPluginVersion =
...@@ -385,7 +391,7 @@ String _formatParseWarning(String content) { ...@@ -385,7 +391,7 @@ String _formatParseWarning(String content) {
// //
// Returns true if versions are compatible. // Returns true if versions are compatible.
// Null Gradle version or AGP version returns false. // Null Gradle version or AGP version returns false.
// If compatibility can not be evaluated returns false. // If compatibility cannot be evaluated returns false.
// If versions are newer than the max known version a warning is logged and true // If versions are newer than the max known version a warning is logged and true
// returned. // returned.
// //
......
...@@ -387,7 +387,9 @@ OS: Mac OS X 13.2.1 aarch64 ...@@ -387,7 +387,9 @@ OS: Mac OS X 13.2.1 aarch64
); );
}); });
testWithoutContext('returns the AGP version when set in Groovy', () async { testWithoutContext(
'returns the AGP version when set in Groovy build file as classpath with single quotes and commented line',
() async {
const String expectedVersion = '7.3.0'; const String expectedVersion = '7.3.0';
final Directory androidDirectory = fileSystem.directory('/android') final Directory androidDirectory = fileSystem.directory('/android')
..createSync(); ..createSync();
...@@ -399,6 +401,8 @@ buildscript { ...@@ -399,6 +401,8 @@ buildscript {
} }
dependencies { dependencies {
// Decoy value to ensure we ignore commented out lines.
// classpath 'com.android.application' version '6.1.0' apply false
classpath 'com.android.tools.build:gradle:$expectedVersion' classpath 'com.android.tools.build:gradle:$expectedVersion'
} }
} }
...@@ -417,7 +421,9 @@ allprojects { ...@@ -417,7 +421,9 @@ allprojects {
); );
}); });
testWithoutContext('returns the AGP version when set in Kotlin', () async { testWithoutContext(
'returns the AGP version when set in Kotlin build file as classpath',
() async {
const String expectedVersion = '7.3.0'; const String expectedVersion = '7.3.0';
final Directory androidDirectory = fileSystem.directory('/android') final Directory androidDirectory = fileSystem.directory('/android')
..createSync(); ..createSync();
...@@ -447,7 +453,77 @@ allprojects { ...@@ -447,7 +453,77 @@ allprojects {
); );
}); });
testWithoutContext('prefers the AGP version when set in Groovy, ignores Kotlin', () async {
testWithoutContext(
'returns the AGP version when set in Groovy build file as compileOnly with double quotes',
() async {
const String expectedVersion = '7.1.0';
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
androidDirectory.childFile('build.gradle.kts').writeAsStringSync('''
dependencies {
compileOnly "com.android.tools.build:gradle:$expectedVersion"
}
''');
expect(
getAgpVersion(androidDirectory, BufferLogger.test()),
expectedVersion,
);
});
testWithoutContext(
'returns the AGP version when set in Kotlin build file as compileOnly',
() async {
const String expectedVersion = '7.1.0';
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
androidDirectory.childFile('build.gradle.kts').writeAsStringSync('''
dependencies {
compileOnly("com.android.tools.build:gradle:$expectedVersion")
}
''');
expect(
getAgpVersion(androidDirectory, BufferLogger.test()),
expectedVersion,
);
});
testWithoutContext(
'returns the AGP version when set in Groovy build file as plugin',
() async {
const String expectedVersion = '6.8';
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
androidDirectory.childFile('build.gradle').writeAsStringSync('''
plugins {
id 'com.android.application' version '$expectedVersion' apply false
}
''');
expect(
getAgpVersion(androidDirectory, BufferLogger.test()),
expectedVersion,
);
});
testWithoutContext(
'returns the AGP version when set in Kotlin build file as plugin',
() async {
const String expectedVersion = '7.2.0';
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
androidDirectory.childFile('build.gradle.kts').writeAsStringSync('''
plugins {
id("com.android.application") version "$expectedVersion" apply false
}
''');
expect(
getAgpVersion(androidDirectory, BufferLogger.test()),
expectedVersion,
);
});
testWithoutContext(
'prefers the AGP version when set in Groovy, ignores Kotlin', () async {
const String versionInGroovy = '7.3.0'; const String versionInGroovy = '7.3.0';
const String versionInKotlin = '7.4.2'; const String versionInKotlin = '7.4.2';
final Directory androidDirectory = fileSystem.directory('/android') final Directory androidDirectory = fileSystem.directory('/android')
...@@ -554,48 +630,73 @@ allprojects { ...@@ -554,48 +630,73 @@ allprojects {
); );
}); });
testWithoutContext('returns the AGP version when in settings', () async { testWithoutContext('returns the AGP version when in Groovy settings as plugin',
() async {
final Directory androidDirectory = fileSystem.directory('/android') final Directory androidDirectory = fileSystem.directory('/android')
..createSync(); ..createSync();
// File must exist and can not have agp defined. // File must exist and can not have agp defined.
androidDirectory.childFile('build.gradle').writeAsStringSync(r''); androidDirectory.childFile('build.gradle').writeAsStringSync(r'');
androidDirectory.childFile('settings.gradle').writeAsStringSync(r''' androidDirectory.childFile('settings.gradle').writeAsStringSync(r'''
pluginManagement { pluginManagement {
def flutterSdkPath = { plugins {
def properties = new Properties() id 'dev.flutter.flutter-gradle-plugin' version '1.0.0' apply false
file("local.properties").withInputStream { properties.load(it) } id 'dev.flutter.flutter-plugin-loader' version '1.0.0'
def flutterSdkPath = properties.getProperty("flutter.sdk") // Decoy value to ensure we ignore commented out lines.
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" // id 'com.android.application' version '6.1.0' apply false
return flutterSdkPath id 'com.android.application' version '8.1.0' apply false
} }
settings.ext.flutterSdkPath = flutterSdkPath() }
''');
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories { expect(
google() getAgpVersion(androidDirectory, BufferLogger.test()),
mavenCentral() '8.1.0',
gradlePluginPortal() );
} });
plugins { testWithoutContext(
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 'returns the AGP version when in Kotlin settings as plugin', () async {
} final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
// File must exist and cannot have agp 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("com.android.application") version "6.1.0" apply false
id("com.android.application") version "7.5.0" apply false
}
} }
''');
expect(
getAgpVersion(androidDirectory, BufferLogger.test()),
'7.5.0',
);
});
testWithoutContext(
'returns null when agp version is misconfigured',
() async {
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
androidDirectory.childFile('build.gradle.kts').writeAsStringSync('''
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" `java-gradle-plugin`
// Decoy value to ensure we ignore commented out lines. `groovy`
// id "com.android.application" version "6.1.0" apply false
id "com.android.application" version "7.3.0" apply false
} }
include ":app" dependencies {
// intentional typo
compileOnl("com.android.tools.build:gradle:7.3.0")
}
'''); ''');
expect( expect(
getAgpVersion(androidDirectory, BufferLogger.test()), getAgpVersion(androidDirectory, BufferLogger.test()),
'7.3.0', null,
); );
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment