Skip to content

Commit 4d21a02

Browse files
authored
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 flutter/flutter@dbe0ccd#diff-20537fb84ee37894a3f3d9723a06bcf2674290ee25aa83332c2524a1f7546a6d
1 parent 969a875 commit 4d21a02

File tree

6 files changed

+144
-18
lines changed

6 files changed

+144
-18
lines changed

packages/flutter_tools/lib/src/android/gradle_utils.dart

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,31 @@ const String maxKnownAgpVersion = '8.3';
7171
// compatible Java version.
7272
const String oldestDocumentedJavaAgpCompatibilityVersion = '4.2';
7373

74+
// Constant used in [_buildAndroidGradlePluginRegExp] and
75+
// [_settingsAndroidGradlePluginRegExp] to identify the version section.
76+
const String _versionGroupName = 'version';
77+
78+
// AGP can be defined in build.gradle
7479
// Expected content:
7580
// "classpath 'com.android.tools.build:gradle:7.3.0'"
76-
// Parentheticals are use to group which helps with version extraction.
77-
// "...build:gradle:(...)" where group(1) should be the version string.
78-
final RegExp _androidGradlePluginRegExp =
79-
RegExp(r'com\.android\.tools\.build:gradle:(\d+\.\d+\.\d+)');
81+
// ?<version> is used to name the version group which helps with extraction.
82+
final RegExp _buildAndroidGradlePluginRegExp =
83+
RegExp(r'com\.android\.tools\.build:gradle:(?<version>\d+\.\d+\.\d+)');
84+
85+
// AGP can be defined in settings.gradle.
86+
// Expected content:
87+
// "id "com.android.application" version "{{agpVersion}}""
88+
// ?<version> is used to name the version group which helps with extraction.
89+
final RegExp _settingsAndroidGradlePluginRegExp = RegExp(
90+
r'^\s+id\s+"com.android.application"\s+version\s+"(?<version>\d+\.\d+\.\d+)"',
91+
multiLine: true);
8092

8193
// Expected content format (with lines above and below).
8294
// Version can have 2 or 3 numbers.
8395
// 'distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip'
8496
// '^\s*' protects against commented out lines.
8597
final RegExp distributionUrlRegex =
86-
RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true);
98+
RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true);
8799

88100
// Modified version of the gradle distribution url match designed to only match
89101
// gradle.org urls so that we can guarantee any modifications to the url
@@ -199,7 +211,7 @@ String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
199211
return templateDefaultGradleVersion;
200212
}
201213
final String buildFileContent = buildFile.readAsStringSync();
202-
final Iterable<Match> pluginMatches = _androidGradlePluginRegExp.allMatches(buildFileContent);
214+
final Iterable<Match> pluginMatches = _buildAndroidGradlePluginRegExp.allMatches(buildFileContent);
203215
if (pluginMatches.isEmpty) {
204216
logger.printTrace("$buildFile doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion");
205217
return templateDefaultGradleVersion;
@@ -309,24 +321,44 @@ OS: Mac OS X 13.2.1 aarch64
309321
/// Returns the Android Gradle Plugin (AGP) version that the current project
310322
/// depends on when found, null otherwise.
311323
///
312-
/// The Android plugin version is specified in the [build.gradle] file within
313-
/// the project's Android directory ([androidDirectory]).
324+
/// The Android plugin version is specified in the [build.gradle] or
325+
/// [settings.gradle] file within the project's
326+
/// Android directory ([androidDirectory]).
314327
String? getAgpVersion(Directory androidDirectory, Logger logger) {
315328
final File buildFile = androidDirectory.childFile('build.gradle');
316329
if (!buildFile.existsSync()) {
317330
logger.printTrace('Can not find build.gradle in $androidDirectory');
318331
return null;
319332
}
320333
final String buildFileContent = buildFile.readAsStringSync();
321-
final Iterable<Match> pluginMatches =
322-
_androidGradlePluginRegExp.allMatches(buildFileContent);
323-
if (pluginMatches.isEmpty) {
324-
logger.printTrace("$buildFile doesn't provide an AGP version");
334+
final RegExpMatch? buildMatch =
335+
_buildAndroidGradlePluginRegExp.firstMatch(buildFileContent);
336+
if (buildMatch != null) {
337+
final String? androidPluginVersion =
338+
buildMatch.namedGroup(_versionGroupName);
339+
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
340+
return androidPluginVersion;
341+
}
342+
logger.printTrace(
343+
"$buildFile doesn't provide an AGP version. Checking settings.");
344+
final File settingsFile = androidDirectory.childFile('settings.gradle');
345+
if (!settingsFile.existsSync()) {
346+
logger.printTrace('$settingsFile does not exist.');
325347
return null;
326348
}
327-
final String? androidPluginVersion = pluginMatches.first.group(1);
328-
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
329-
return androidPluginVersion;
349+
final String settingsFileContent = settingsFile.readAsStringSync();
350+
final RegExpMatch? settingsMatch =
351+
_settingsAndroidGradlePluginRegExp.firstMatch(settingsFileContent);
352+
353+
if (settingsMatch != null) {
354+
final String? androidPluginVersion =
355+
settingsMatch.namedGroup(_versionGroupName);
356+
logger.printTrace(
357+
'$settingsFile provides AGP version: $androidPluginVersion');
358+
return androidPluginVersion;
359+
}
360+
logger.printTrace("$settingsFile doesn't provide an AGP version.");
361+
return null;
330362
}
331363

332364
String _formatParseWarning(String content) {

packages/flutter_tools/lib/src/project.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,15 +608,20 @@ class AndroidProject extends FlutterProjectPlatform {
608608
final bool compatibleGradleAgp = gradle.validateGradleAndAgp(globals.logger,
609609
gradleV: gradleVersion, agpV: agpVersion);
610610

611-
final bool compatibleJavaGradle = gradle.validateJavaAndGradle(globals.logger,
612-
javaV: javaVersion, gradleV: gradleVersion);
611+
final bool compatibleJavaGradle = gradle.validateJavaAndGradle(
612+
globals.logger,
613+
javaV: javaVersion,
614+
gradleV: gradleVersion);
613615

614616
// Begin description formatting.
615617
if (!compatibleGradleAgp) {
618+
final String gradleDescription = agpVersion != null
619+
? 'Update Gradle to at least "${gradle.getGradleVersionFor(agpVersion)}".'
620+
: '';
616621
description = '''
617622
Incompatible Gradle/AGP versions. \n
618623
Gradle Version: $gradleVersion, AGP Version: $agpVersion
619-
Update Gradle to at least "${gradle.getGradleVersionFor(agpVersion!)}".\n
624+
$gradleDescription\n
620625
See the link below for more information:
621626
$gradleAgpCompatUrl
622627
''';

packages/flutter_tools/templates/app_shared/android-java.tmpl/build.gradle.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ buildscript {
66
}
77

88
dependencies {
9+
// AGP version is set in settings.gradle.
910
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1011
}
1112
}

packages/flutter_tools/templates/app_shared/android-kotlin.tmpl/build.gradle.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ buildscript {
66
}
77

88
dependencies {
9+
// AGP version is set in settings.gradle.
910
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1011
}
1112
}

packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,51 @@ allprojects {
469469
);
470470
});
471471

472+
testWithoutContext('returns the AGP version when in settings', () async {
473+
final Directory androidDirectory = fileSystem.directory('/android')
474+
..createSync();
475+
// File must exist and can not have agp defined.
476+
androidDirectory.childFile('build.gradle').writeAsStringSync(r'');
477+
androidDirectory.childFile('settings.gradle').writeAsStringSync(r'''
478+
pluginManagement {
479+
def flutterSdkPath = {
480+
def properties = new Properties()
481+
file("local.properties").withInputStream { properties.load(it) }
482+
def flutterSdkPath = properties.getProperty("flutter.sdk")
483+
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
484+
return flutterSdkPath
485+
}
486+
settings.ext.flutterSdkPath = flutterSdkPath()
487+
488+
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
489+
490+
repositories {
491+
google()
492+
mavenCentral()
493+
gradlePluginPortal()
494+
}
495+
496+
plugins {
497+
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
498+
}
499+
}
500+
501+
plugins {
502+
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
503+
// Decoy value to ensure we ignore commented out lines.
504+
// id "com.android.application" version "6.1.0" apply false
505+
id "com.android.application" version "7.3.0" apply false
506+
}
507+
508+
include ":app"
509+
''');
510+
511+
expect(
512+
getAgpVersion(androidDirectory, BufferLogger.test()),
513+
'7.3.0',
514+
);
515+
});
516+
472517
group('validates gradle/agp versions', () {
473518
final List<GradleAgpTestData> testData = <GradleAgpTestData>[
474519
// Values too new *these need to be updated* when

packages/flutter_tools/test/general.shard/project_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,48 @@ dependencies {
659659
androidSdk: androidSdk,
660660
);
661661
});
662+
group('_', () {
663+
final FakeProcessManager processManager;
664+
final Java java;
665+
final AndroidStudio androidStudio;
666+
final FakeAndroidSdkWithDir androidSdk;
667+
final FileSystem fileSystem = getFileSystemForPlatform();
668+
java = FakeJava(version: Version(11, 0, 2));
669+
processManager = FakeProcessManager.empty();
670+
androidStudio = FakeAndroidStudio();
671+
androidSdk =
672+
FakeAndroidSdkWithDir(fileSystem.currentDirectory);
673+
fileSystem.currentDirectory
674+
.childDirectory(androidStudio.javaPath!)
675+
.createSync();
676+
_testInMemory(
677+
'null agp only',
678+
() async {
679+
const String gradleV = '7.0.3';
680+
final FlutterProject? project = await configureGradleAgpForTest(
681+
gradleV: gradleV,
682+
agpV: '',
683+
);
684+
final CompatibilityResult value =
685+
await project!.android.hasValidJavaGradleAgpVersions();
686+
expect(value.success, isFalse);
687+
// Should not have the valid string.
688+
expect(
689+
value.description,
690+
isNot(
691+
contains(RegExp(AndroidProject.validJavaGradleAgpString))));
692+
// On gradle/agp error print help url null value for agp.
693+
expect(value.description,
694+
contains(RegExp(AndroidProject.gradleAgpCompatUrl)));
695+
expect(value.description, contains(RegExp(gradleV)));
696+
expect(value.description, contains(RegExp('null')));
697+
},
698+
java: java,
699+
androidStudio: androidStudio,
700+
processManager: processManager,
701+
androidSdk: androidSdk,
702+
);
703+
});
662704
});
663705

664706
group('language', () {

0 commit comments

Comments
 (0)