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';
// [_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'"
// AGP can be defined in the dependencies block of [build.gradle] or [build.gradle.kts].
// Expected content (covers both classpath and compileOnly cases):
// 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.
final RegExp _buildAndroidGradlePluginRegExp =
RegExp(r'com\.android\.tools\.build:gradle:(?<version>\d+\.\d+\.\d+)');
final RegExp _androidGradlePluginRegExpFromDependencies = RegExp(
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:
// "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.
final RegExp _settingsAndroidGradlePluginRegExp = RegExp(
r'^\s+id\s+"com.android.application"\s+version\s+"(?<version>\d+\.\d+\.\d+)"',
final RegExp _androidGradlePluginRegExpFromId = RegExp(
r"""[^\/]*s*id\s*\(?['"]com\.android\.application['"]\)?\s+version\s+['"](?<version>\d+(\.\d+){1,2})\)?""",
multiLine: true);
// Expected content format (with lines above and below).
......@@ -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,
/// 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.
String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
const String buildFileName = 'build.gradle/build.gradle.kts';
File buildFile = directory.childFile('build.gradle');
if (!buildFile.existsSync()) {
buildFile = directory.childFile('build.gradle.kts');
}
if (!buildFile.existsSync()) {
final String? androidPluginVersion = getAgpVersion(directory, logger);
if (androidPluginVersion == null) {
logger.printTrace(
"$buildFileName doesn't exist, 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");
'AGP version cannot be determined, assuming Gradle version: $templateDefaultGradleVersion');
return templateDefaultGradleVersion;
}
final String? androidPluginVersion = pluginMatches.first.group(1);
logger.printTrace('$buildFileName provides AGP version: $androidPluginVersion');
return getGradleVersionFor(androidPluginVersion ?? 'unknown');
return getGradleVersionFor(androidPluginVersion);
}
/// Returns the gradle file from the top level directory.
......@@ -329,38 +322,51 @@ 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] or
/// [settings.gradle] file within the project's
/// Android directory ([androidDirectory]).
/// 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 ([androidDirectory]).
String? getAgpVersion(Directory androidDirectory, Logger logger) {
File buildFile = androidDirectory.childFile('build.gradle');
if (!buildFile.existsSync()) {
buildFile = androidDirectory.childFile('build.gradle.kts');
}
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;
}
final String buildFileContent = buildFile.readAsStringSync();
final RegExpMatch? buildMatch =
_buildAndroidGradlePluginRegExp.firstMatch(buildFileContent);
if (buildMatch != null) {
final RegExpMatch? buildMatchClasspath =
_androidGradlePluginRegExpFromDependencies.firstMatch(buildFileContent);
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 =
buildMatch.namedGroup(_versionGroupName);
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
buildMatchId.namedGroup(_versionGroupName);
logger.printTrace('$buildFile provides AGP version from plugin id: $androidPluginVersion');
return androidPluginVersion;
}
logger.printTrace(
"$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()) {
logger.printTrace('$settingsFile does not exist.');
logger.printTrace(
'Cannot find settings.gradle/settings.gradle.kts in $androidDirectory');
return null;
}
final String settingsFileContent = settingsFile.readAsStringSync();
final RegExpMatch? settingsMatch =
_settingsAndroidGradlePluginRegExp.firstMatch(settingsFileContent);
_androidGradlePluginRegExpFromId.firstMatch(settingsFileContent);
if (settingsMatch != null) {
final String? androidPluginVersion =
......@@ -385,7 +391,7 @@ String _formatParseWarning(String content) {
//
// Returns true if versions are compatible.
// 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
// returned.
//
......
......@@ -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';
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
......@@ -399,6 +401,8 @@ buildscript {
}
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'
}
}
......@@ -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';
final Directory androidDirectory = fileSystem.directory('/android')
..createSync();
......@@ -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 versionInKotlin = '7.4.2';
final Directory androidDirectory = fileSystem.directory('/android')
......@@ -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')
..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
plugins {
id 'dev.flutter.flutter-gradle-plugin' version '1.0.0' apply false
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 '8.1.0' apply false
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
}
''');
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
expect(
getAgpVersion(androidDirectory, BufferLogger.test()),
'8.1.0',
);
});
testWithoutContext(
'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-gradle-plugin" version "1.0.0" apply false
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 {
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
`java-gradle-plugin`
`groovy`
}
include ":app"
dependencies {
// intentional typo
compileOnl("com.android.tools.build:gradle:7.3.0")
}
''');
expect(
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