diff --git a/script/tool/lib/src/update_dependency_command.dart b/script/tool/lib/src/update_dependency_command.dart index 75b9e7c8baf..33adb389fcf 100644 --- a/script/tool/lib/src/update_dependency_command.dart +++ b/script/tool/lib/src/update_dependency_command.dart @@ -45,6 +45,7 @@ class UpdateDependencyCommand extends PackageLoopingCommand { allowed: [ _AndroidDependencyType.gradle, _AndroidDependencyType.androidGradlePlugin, + _AndroidDependencyType.kotlinGradlePlugin, _AndroidDependencyType.compileSdk, _AndroidDependencyType.compileSdkForExamples, ], @@ -53,6 +54,8 @@ class UpdateDependencyCommand extends PackageLoopingCommand { 'Updates Gradle version used in plugin example apps.', _AndroidDependencyType.androidGradlePlugin: 'Updates AGP version used in plugin example apps.', + _AndroidDependencyType.kotlinGradlePlugin: + 'Updates KGP version used in plugin example apps.', _AndroidDependencyType.compileSdk: 'Updates compileSdk version used to compile plugins.', _AndroidDependencyType.compileSdkForExamples: @@ -155,6 +158,19 @@ 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 == + _AndroidDependencyType.kotlinGradlePlugin) { + final RegExp validKgpVersionPattern = RegExp(r'^\d\.\d\.\d{1,2}$'); + final bool isValidKgpVersion = + validKgpVersionPattern.stringMatch(version) == version; + if (!isValidKgpVersion) { + printError(''' +A version with a valid format (3 numbers separated by 2 periods) must be provided. + 1. The first number must have one digit + 2. The second number must have one digit + 3. The third number must have one or two digits'''); + throw ToolExit(_exitInvalidTargetVersion); + } } else if (_targetAndroidDependency == _AndroidDependencyType.compileSdk || _targetAndroidDependency == @@ -266,7 +282,8 @@ A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) mus _targetAndroidDependency == _AndroidDependencyType.compileSdkForExamples || _targetAndroidDependency == - _AndroidDependencyType.androidGradlePlugin) { + _AndroidDependencyType.androidGradlePlugin || + _targetAndroidDependency == _AndroidDependencyType.kotlinGradlePlugin) { return _runForAndroidDependencyOnExamples(package); } @@ -334,7 +351,17 @@ A version with a valid format (maximum 2-3 numbers separated by 1-2 periods) mus 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'; + 'id "com.android.application" version "$_targetVersion" apply false'; + } else if (_targetAndroidDependency == + _AndroidDependencyType.kotlinGradlePlugin) { + if (androidDirectory.childFile('settings.gradle').existsSync()) { + filesToUpdate.add(androidDirectory.childFile('settings.gradle')); + } + dependencyVersionPattern = RegExp( + r'^\s*id\s+"org\.jetbrains\.kotlin\.android"\s+version\s+"(\d\.\d\.\d{1,2})"\s+apply\s+false\s*$', + multiLine: true); + newDependencyVersionEntry = + ' id "org.jetbrains.kotlin.android" version "$_targetVersion" apply false'; } else { printError( 'Target Android dependency $_targetAndroidDependency is unrecognized.'); @@ -525,6 +552,7 @@ enum _PubDependencyType { normal, dev } class _AndroidDependencyType { static const String gradle = 'gradle'; static const String androidGradlePlugin = 'androidGradlePlugin'; + static const String kotlinGradlePlugin = 'kotlinGradlePlugin'; 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 b2621435673..738e57d0c57 100644 --- a/script/tool/test/update_dependency_command_test.dart +++ b/script/tool/test/update_dependency_command_test.dart @@ -614,6 +614,18 @@ dev_dependencies: '8.12.12' ]; + const String invalidGradleAgpVersionError = ''' +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'''; + + const String invalidKgpVersionError = ''' +A version with a valid format (3 numbers separated by 2 periods) must be provided. + 1. The first number must have one digit + 2. The second number must have one digit + 3. The third number must have one or two digits'''; + group('gradle', () { for (final String gradleVersion in invalidGradleAgpVersionsFormat) { test('throws because gradleVersion: $gradleVersion is invalid', @@ -633,11 +645,7 @@ dev_dependencies: 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'''), + contains(invalidGradleAgpVersionError), ]), ); }); @@ -925,11 +933,7 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip 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'''), + contains(invalidGradleAgpVersionError), ]), ); }); @@ -994,7 +998,7 @@ plugins { expect( updatedGradleSettingsContents, - contains(r' id "com.android.application" version ' + contains(r'id "com.android.application" version ' '"$newAgpVersion" apply false')); }); @@ -1037,10 +1041,152 @@ plugins { expect( updatedGradleSettingsContents, - contains(r' id "com.android.application" version ' + contains(r'id "com.android.application" version ' '"$newAgpVersion" apply false')); }); }); + group('kgp', () { + final List invalidKgpVersionsFormat = [ + '81', + '81.1', + '8.123', + '8.12.12', + '8.12.1', + ]; + + for (final String kgpVersion in invalidKgpVersionsFormat) { + test('throws because kgpVersion: $kgpVersion is invalid', () async { + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'update-dependency', + '--android-dependency', + 'kotlinGradlePlugin', + '--version', + kgpVersion, + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains(invalidKgpVersionError), + ]), + ); + }); + } + + 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', + 'kotlinGradlePlugin', + '--version', + '2.2.20', + ], errorHandler: (Error e) { + print((e as ToolExit).stackTrace); + }); + + 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 newKgpVersion = '2.2.20'; + + 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 "org.jetbrains.kotlin.android" version "2.2.20" apply false +... +} +... +'''); + + await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'kotlinGradlePlugin', + '--version', + newKgpVersion, + ]); + + final String updatedGradleSettingsContents = + gradleSettingsFile.readAsStringSync(); + + expect( + updatedGradleSettingsContents, + contains(r' id "org.jetbrains.kotlin.android" version ' + '"$newKgpVersion" 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 newKgpVersion = '2.2.20'; + + 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 "org.jetbrains.kotlin.android" version "2.2.20" apply false +... +} +... +'''); + + await runCapturingPrint(runner, [ + 'update-dependency', + '--packages', + package.displayName, + '--android-dependency', + 'kotlinGradlePlugin', + '--version', + newKgpVersion, + ]); + + final String updatedGradleSettingsContents = + gradleSettingsFile.readAsStringSync(); + + expect( + updatedGradleSettingsContents, + contains(r' id "org.jetbrains.kotlin.android" version ' + '"$newKgpVersion" apply false')); + }); + }); group('compileSdk/compileSdkForExamples', () { // Tests if the compileSdk version is updated for the provided