diff --git a/script/tool/lib/src/gradle_check_command.dart b/script/tool/lib/src/gradle_check_command.dart index c7486a9b09c..4bdec9d6641 100644 --- a/script/tool/lib/src/gradle_check_command.dart +++ b/script/tool/lib/src/gradle_check_command.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:collection/collection.dart'; import 'package:file/file.dart'; import 'package:meta/meta.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -127,6 +128,9 @@ class GradleCheckCommand extends PackageLoopingCommand { if (!_validateGradleDrivenLintConfig(package, lines)) { succeeded = false; } + if (!_validateCompileSdkUsage(package, lines)) { + succeeded = false; + } return succeeded; } @@ -415,6 +419,47 @@ for more details.'''; return true; } + bool _validateCompileSdkUsage( + RepositoryPackage package, List gradleLines) { + final RegExp linePattern = RegExp(r'^\s*compileSdk'); + final RegExp legacySettingPattern = RegExp(r'^\s*compileSdkVersion'); + final String? compileSdkLine = gradleLines + .firstWhereOrNull((String line) => linePattern.hasMatch(line)); + if (compileSdkLine == null) { + printError('${indentation}No compileSdk or compileSdkVersion found.'); + return false; + } + if (legacySettingPattern.hasMatch(compileSdkLine)) { + printError('${indentation}Please replace the deprecated ' + '"compileSdkVersion" setting with the newer "compileSdk"'); + return false; + } + if (compileSdkLine.contains('flutter.compileSdkVersion')) { + final Pubspec pubspec = package.parsePubspec(); + final VersionConstraint? flutterConstraint = + pubspec.environment['flutter']; + final Version? minFlutterVersion = + flutterConstraint != null && flutterConstraint is VersionRange + ? flutterConstraint.min + : null; + if (minFlutterVersion == null) { + printError('${indentation}Unable to find a Flutter SDK version ' + 'constraint. Use of flutter.compileSdkVersion requires a minimum ' + 'Flutter version of 3.27'); + return false; + } + if (minFlutterVersion < Version(3, 27, 0)) { + printError('${indentation}Use of flutter.compileSdkVersion requires a ' + 'minimum Flutter version of 3.27, but this package currently ' + 'supports $minFlutterVersion.\n' + "${indentation}Please update the package's minimum Flutter SDK " + 'version to at least 3.27.'); + return false; + } + } + return true; + } + /// Validates whether the given [example]'s gradle content is configured to /// build its plugin target with javac lints enabled and treated as errors, /// if the enclosing package is a plugin. diff --git a/script/tool/test/gradle_check_command_test.dart b/script/tool/test/gradle_check_command_test.dart index d5f33767ccc..8cca215862b 100644 --- a/script/tool/test/gradle_check_command_test.dart +++ b/script/tool/test/gradle_check_command_test.dart @@ -43,6 +43,8 @@ void main() { bool includeNamespace = true, bool commentNamespace = false, bool warningsConfigured = true, + bool useDeprecatedCompileSdkVersion = false, + String compileSdk = '33', }) { final File buildGradle = package .platformDirectory(FlutterPlatform.android) @@ -88,7 +90,7 @@ apply plugin: 'com.android.library' ${includeLanguageVersion ? javaSection : ''} android { ${includeNamespace ? namespace : ''} - compileSdk 33 + ${useDeprecatedCompileSdkVersion ? 'compileSdkVersion' : 'compileSdk'} $compileSdk defaultConfig { minSdkVersion 30 @@ -989,4 +991,111 @@ dependencies { ); }); }); + + group('compileSdk check', () { + test('passes if set to a number', () async { + const String packageName = 'a_package'; + final RepositoryPackage package = + createFakePackage(packageName, packagesDir, isFlutter: true); + writeFakePluginBuildGradle(package, + includeLanguageVersion: true, compileSdk: '35'); + writeFakeManifest(package); + final RepositoryPackage example = package.getExamples().first; + writeFakeExampleBuildGradles(example, pluginName: packageName); + writeFakeManifest(example, isApp: true); + + final List output = + await runCapturingPrint(runner, ['gradle-check']); + + expect( + output, + containsAllInOrder([ + contains('Validating android/build.gradle'), + ]), + ); + }); + + test('passes if set to flutter.compileSdkVersion with Flutter 3.27+', + () async { + const String packageName = 'a_package'; + final RepositoryPackage package = createFakePackage( + packageName, packagesDir, + isFlutter: true, flutterConstraint: '>=3.27.0'); + writeFakePluginBuildGradle(package, + includeLanguageVersion: true, + compileSdk: 'flutter.compileSdkVersion'); + writeFakeManifest(package); + final RepositoryPackage example = package.getExamples().first; + writeFakeExampleBuildGradles(example, pluginName: packageName); + writeFakeManifest(example, isApp: true); + + final List output = + await runCapturingPrint(runner, ['gradle-check']); + + expect( + output, + containsAllInOrder([ + contains('Validating android/build.gradle'), + ]), + ); + }); + + test('fails if set to flutter.compileSdkVersion with Flutter <3.27', + () async { + const String packageName = 'a_package'; + final RepositoryPackage package = createFakePackage( + packageName, packagesDir, + isFlutter: true, flutterConstraint: '>=3.24.0'); + writeFakePluginBuildGradle(package, + includeLanguageVersion: true, + compileSdk: 'flutter.compileSdkVersion'); + writeFakeManifest(package); + final RepositoryPackage example = package.getExamples().first; + writeFakeExampleBuildGradles(example, pluginName: packageName); + writeFakeManifest(example, isApp: true); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['gradle-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Use of flutter.compileSdkVersion requires a minimum ' + 'Flutter version of 3.27, but this package currently supports ' + '3.24.0'), + ]), + ); + }); + + test('fails if uses the legacy key', () async { + const String packageName = 'a_package'; + final RepositoryPackage package = + createFakePackage(packageName, packagesDir, isFlutter: true); + writeFakePluginBuildGradle(package, + includeLanguageVersion: true, useDeprecatedCompileSdkVersion: true); + writeFakeManifest(package); + final RepositoryPackage example = package.getExamples().first; + writeFakeExampleBuildGradles(example, pluginName: packageName); + writeFakeManifest(example, isApp: true); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['gradle-check'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Please replace the deprecated "compileSdkVersion" setting ' + 'with the newer "compileSdk"'), + ]), + ); + }); + }); }