diff --git a/script/tool/lib/src/update_dependency_command.dart b/script/tool/lib/src/update_dependency_command.dart index 02c118e0d20..75b9e7c8baf 100644 --- a/script/tool/lib/src/update_dependency_command.dart +++ b/script/tool/lib/src/update_dependency_command.dart @@ -43,16 +43,19 @@ class UpdateDependencyCommand extends PackageLoopingCommand { argParser.addOption(_androidDependency, help: 'An Android dependency to update.', allowed: [ - _AndroidDepdencyType.gradle, - _AndroidDepdencyType.compileSdk, - _AndroidDepdencyType.compileSdkForExamples, + _AndroidDependencyType.gradle, + _AndroidDependencyType.androidGradlePlugin, + _AndroidDependencyType.compileSdk, + _AndroidDependencyType.compileSdkForExamples, ], allowedHelp: { - _AndroidDepdencyType.gradle: + _AndroidDependencyType.gradle: 'Updates Gradle version used in plugin example apps.', - _AndroidDepdencyType.compileSdk: + _AndroidDependencyType.androidGradlePlugin: + 'Updates AGP version used in plugin example apps.', + _AndroidDependencyType.compileSdk: 'Updates compileSdk version used to compile plugins.', - _AndroidDepdencyType.compileSdkForExamples: + _AndroidDependencyType.compileSdkForExamples: 'Updates compileSdk version used to compile plugin examples.', }); argParser.addOption( @@ -137,12 +140,14 @@ ${response.httpResponse.body} if (version == null) { printError('A version must be provided to update this dependency.'); throw ToolExit(_exitNoTargetVersion); - } else if (_targetAndroidDependency == _AndroidDepdencyType.gradle) { - final RegExp validGradleVersionPattern = + } else if (_targetAndroidDependency == _AndroidDependencyType.gradle || + _targetAndroidDependency == + _AndroidDependencyType.androidGradlePlugin) { + final RegExp validGradleAGPVersionPattern = RegExp(r'^\d{1,2}\.\d{1,2}(?:\.\d)?$'); - final bool isValidGradleVersion = - validGradleVersionPattern.stringMatch(version) == version; - if (!isValidGradleVersion) { + final bool isValidGradleAGPVersion = + validGradleAGPVersionPattern.stringMatch(version) == version; + if (!isValidGradleAGPVersion) { printError(''' A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) must be provided. 1. The first number must have one or two digits @@ -150,9 +155,10 @@ A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) mus 3. If present, the third number must have a single digit'''); throw ToolExit(_exitInvalidTargetVersion); } - } else if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk || + } else if (_targetAndroidDependency == + _AndroidDependencyType.compileSdk || _targetAndroidDependency == - _AndroidDepdencyType.compileSdkForExamples) { + _AndroidDependencyType.compileSdkForExamples) { final RegExp validSdkVersion = RegExp(r'^\d{1,2}$'); final bool isValidSdkVersion = validSdkVersion.stringMatch(version) == version; @@ -254,11 +260,13 @@ A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) mus /// an Android dependency. Future _runForAndroidDependency( RepositoryPackage package) async { - if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk) { + if (_targetAndroidDependency == _AndroidDependencyType.compileSdk) { return _runForCompileSdkVersion(package); - } else if (_targetAndroidDependency == _AndroidDepdencyType.gradle || + } else if (_targetAndroidDependency == _AndroidDependencyType.gradle || _targetAndroidDependency == - _AndroidDepdencyType.compileSdkForExamples) { + _AndroidDependencyType.compileSdkForExamples || + _targetAndroidDependency == + _AndroidDependencyType.androidGradlePlugin) { return _runForAndroidDependencyOnExamples(package); } @@ -283,7 +291,7 @@ A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) mus final RegExp dependencyVersionPattern; final String newDependencyVersionEntry; - if (_targetAndroidDependency == _AndroidDepdencyType.gradle) { + if (_targetAndroidDependency == _AndroidDependencyType.gradle) { if (androidDirectory .childDirectory('gradle') .childDirectory('wrapper') @@ -311,12 +319,22 @@ A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) mus newDependencyVersionEntry = 'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip'; } else if (_targetAndroidDependency == - _AndroidDepdencyType.compileSdkForExamples) { + _AndroidDependencyType.compileSdkForExamples) { filesToUpdate.add( androidDirectory.childDirectory('app').childFile('build.gradle')); dependencyVersionPattern = RegExp( r'(compileSdk|compileSdkVersion) (\d{1,2}|flutter.compileSdkVersion)'); newDependencyVersionEntry = 'compileSdk $_targetVersion'; + } else if (_targetAndroidDependency == + _AndroidDependencyType.androidGradlePlugin) { + if (androidDirectory.childFile('settings.gradle').existsSync()) { + filesToUpdate.add(androidDirectory.childFile('settings.gradle')); + } + dependencyVersionPattern = RegExp( + r'^\s*id\s+"com\.android\.application"\s+version\s+"(\d{1,2}\.\d{1,2}(?:\.\d)?)"\s+apply\s+false\s*$', + multiLine: true); + newDependencyVersionEntry = + ' id "com.android.application" version "$_targetVersion" apply false'; } else { printError( 'Target Android dependency $_targetAndroidDependency is unrecognized.'); @@ -504,8 +522,9 @@ class _PubDependencyInfo { enum _PubDependencyType { normal, dev } -class _AndroidDepdencyType { +class _AndroidDependencyType { static const String gradle = 'gradle'; + static const String androidGradlePlugin = 'androidGradlePlugin'; static const String compileSdk = 'compileSdk'; static const String compileSdkForExamples = 'compileSdkForExamples'; } diff --git a/script/tool/test/update_dependency_command_test.dart b/script/tool/test/update_dependency_command_test.dart index 6488c673fe4..b2621435673 100644 --- a/script/tool/test/update_dependency_command_test.dart +++ b/script/tool/test/update_dependency_command_test.dart @@ -607,15 +607,15 @@ dev_dependencies: }); group('Android dependencies', () { - group('gradle', () { - final List invalidGradleVersionsFormat = [ - '81', - '811.1', - '8.123', - '8.12.12' - ]; + final List invalidGradleAgpVersionsFormat = [ + '81', + '811.1', + '8.123', + '8.12.12' + ]; - for (final String gradleVersion in invalidGradleVersionsFormat) { + group('gradle', () { + for (final String gradleVersion in invalidGradleAgpVersionsFormat) { test('throws because gradleVersion: $gradleVersion is invalid', () async { Error? commandError; @@ -907,6 +907,140 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip 'gradle-$newGradleVersion-all.zip')); }); }); + group('agp', () { + for (final String agpVersion in invalidGradleAgpVersionsFormat) { + test('throws because agpVersion: $agpVersion is invalid', () async { + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--android-dependency', + 'androidGradlePlugin', + '--version', + agpVersion, + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains(''' +A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) must be provided. + 1. The first number must have one or two digits + 2. The second number must have one or two digits + 3. If present, the third number must have a single digit'''), + ]), + ); + }); + } + + test('skips if example app does not run on Android', () async { + final RepositoryPackage package = + createFakePlugin('fake_plugin', packagesDir); + + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'androidGradlePlugin', + '--version', + '8.11.1', + ]); + + expect( + output, + containsAllInOrder([ + contains('SKIPPING: No example apps run on Android.'), + ]), + ); + }); + + test('succeeds if example app has android/settings.gradle structure', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, + extraFiles: ['example/android/settings.gradle']); + const String newAgpVersion = '9.9'; + + final File gradleSettingsFile = package.directory + .childDirectory('example') + .childDirectory('android') + .childFile('settings.gradle'); + + gradleSettingsFile.writeAsStringSync(r''' +... +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.11.1" apply false +... +} +... +'''); + + await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'androidGradlePlugin', + '--version', + newAgpVersion, + ]); + + final String updatedGradleSettingsContents = + gradleSettingsFile.readAsStringSync(); + + expect( + updatedGradleSettingsContents, + contains(r' id "com.android.application" version ' + '"$newAgpVersion" apply false')); + }); + + test('succeeds if one example app runs on Android and another does not', + () async { + final RepositoryPackage package = createFakePlugin( + 'fake_plugin', packagesDir, + examples: ['example_1', 'example_2'], + extraFiles: ['example/example_2/android/settings.gradle']); + const String newAgpVersion = '9.9'; + + final File gradleSettingsFile = package.directory + .childDirectory('example') + .childDirectory('example_2') + .childDirectory('android') + .childFile('settings.gradle'); + + gradleSettingsFile.writeAsStringSync(r''' +... +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.11.1" apply false +... +} +... +'''); + + await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'androidGradlePlugin', + '--version', + newAgpVersion, + ]); + + final String updatedGradleSettingsContents = + gradleSettingsFile.readAsStringSync(); + + expect( + updatedGradleSettingsContents, + contains(r' id "com.android.application" version ' + '"$newAgpVersion" apply false')); + }); + }); group('compileSdk/compileSdkForExamples', () { // Tests if the compileSdk version is updated for the provided