Skip to content

Commit

Permalink
Protect flutter analyze --suggestions from erroring on missing AGP va…
Browse files Browse the repository at this point in the history
…lue. (#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 flutter/flutter@dbe0ccd#diff-20537fb84ee37894a3f3d9723a06bcf2674290ee25aa83332c2524a1f7546a6d
  • Loading branch information
reidbaker authored Nov 2, 2023
1 parent 969a875 commit 4d21a02
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 18 deletions.
62 changes: 47 additions & 15 deletions packages/flutter_tools/lib/src/android/gradle_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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+)');
// ?<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+)');

// AGP can be defined in settings.gradle.
// Expected content:
// "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+)"',
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
Expand Down Expand Up @@ -199,7 +211,7 @@ String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
return templateDefaultGradleVersion;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> pluginMatches = _androidGradlePluginRegExp.allMatches(buildFileContent);
final Iterable<Match> pluginMatches = _buildAndroidGradlePluginRegExp.allMatches(buildFileContent);
if (pluginMatches.isEmpty) {
logger.printTrace("$buildFile doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion");
return templateDefaultGradleVersion;
Expand Down Expand Up @@ -309,24 +321,44 @@ 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()) {
logger.printTrace('Can not find build.gradle in $androidDirectory');
return null;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> 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) {
Expand Down
11 changes: 8 additions & 3 deletions packages/flutter_tools/lib/src/project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
''';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ buildscript {
}

dependencies {
// AGP version is set in settings.gradle.
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ buildscript {
}

dependencies {
// AGP version is set in settings.gradle.
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<GradleAgpTestData> testData = <GradleAgpTestData>[
// Values too new *these need to be updated* when
Expand Down
42 changes: 42 additions & 0 deletions packages/flutter_tools/test/general.shard/project_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down

0 comments on commit 4d21a02

Please sign in to comment.