[ci] Enforce a minimum Kotlin version in examples (#3979)

https://github.com/flutter/packages/pull/3973 caused an out-of-band
failure after publishing, because an example that uses `url_launcher`
had a too-old Kotlin version set. This is not something we consider
client-breaking because `flutter` prodives a very clear error message
with a straightforward and actionable fix step (update the app's Kotlin
version), so the fix for the breakage is just to update our own
examples.

Since increasingly we're likely to hit problems where modern version of
dependencies don't work with old version of Kotlin, this adds repo-wide
CI enforcement that examples are set to a minimum version (matching the
current `flutter/flutter` template; we can increase this over time as we
feel it's useful to do so).
This commit is contained in:
stuartmorgan
2023-05-15 07:54:35 -07:00
committed by GitHub
parent 19c6414d54
commit 8febbba0c3
11 changed files with 141 additions and 10 deletions

View File

@ -3,12 +3,18 @@
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:meta/meta.dart';
import 'package:pub_semver/pub_semver.dart';
import 'common/core.dart';
import 'common/package_looping_command.dart';
import 'common/plugin_utils.dart';
import 'common/repository_package.dart';
/// The lowest `ext.kotlin_version` that example apps are allowed to use.
@visibleForTesting
final Version minKotlinVersion = Version(1, 7, 10);
/// A command to enforce gradle file conventions and best practices.
class GradleCheckCommand extends PackageLoopingCommand {
/// Creates an instance of the gradle check command.
@ -125,6 +131,9 @@ class GradleCheckCommand extends PackageLoopingCommand {
if (!_validateJavacLintConfig(package, lines)) {
succeeded = false;
}
if (!_validateKotlinVersion(package, lines)) {
succeeded = false;
}
return succeeded;
}
@ -347,4 +356,26 @@ gradle.projectsEvaluated {
}
return true;
}
/// Validates whether the given [example] has its Kotlin version set to at
/// least a minimum value, if it is set at all.
bool _validateKotlinVersion(
RepositoryPackage example, List<String> gradleLines) {
final RegExp kotlinVersionRegex =
RegExp(r"ext\.kotlin_version\s*=\s*'([\d.]+)'");
RegExpMatch? match;
if (gradleLines.any((String line) {
match = kotlinVersionRegex.firstMatch(line);
return match != null;
})) {
final Version version = Version.parse(match!.group(1)!);
if (version < minKotlinVersion) {
printError('build.gradle sets "ext.kotlin_version" to "$version". The '
'minimum Kotlin version that can be specified is '
'$minKotlinVersion, for compatibility with modern dependencies.');
return false;
}
}
return true;
}
}

View File

@ -123,6 +123,7 @@ dependencies {
RepositoryPackage package, {
required String pluginName,
required bool warningsConfigured,
String? kotlinVersion,
}) {
final File buildGradle = package
.platformDirectory(FlutterPlatform.android)
@ -140,6 +141,7 @@ gradle.projectsEvaluated {
''';
buildGradle.writeAsStringSync('''
buildscript {
${kotlinVersion == null ? '' : "ext.kotlin_version = '$kotlinVersion'"}
repositories {
google()
mavenCentral()
@ -228,9 +230,12 @@ dependencies {
bool includeNamespace = true,
bool commentNamespace = false,
bool warningsConfigured = true,
String? kotlinVersion,
}) {
writeFakeExampleTopLevelBuildGradle(package,
pluginName: pluginName, warningsConfigured: warningsConfigured);
pluginName: pluginName,
warningsConfigured: warningsConfigured,
kotlinVersion: kotlinVersion);
writeFakeExampleAppBuildGradle(package,
includeNamespace: includeNamespace, commentNamespace: commentNamespace);
}
@ -644,4 +649,99 @@ dependencies {
],
));
});
group('Kotlin version check', () {
test('passes if not set', () async {
const String packageName = 'a_package';
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: packageName);
writeFakeManifest(example, isApp: true);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle'),
]),
);
});
test('passes if at the minimum allowed version', () async {
const String packageName = 'a_package';
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example,
pluginName: packageName, kotlinVersion: minKotlinVersion.toString());
writeFakeManifest(example, isApp: true);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle'),
]),
);
});
test('passes if above the minimum allowed version', () async {
const String packageName = 'a_package';
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example,
pluginName: packageName, kotlinVersion: '99.99.0');
writeFakeManifest(example, isApp: true);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle'),
]),
);
});
test('fails if below the minimum allowed version', () async {
const String packageName = 'a_package';
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example,
pluginName: packageName, kotlinVersion: '1.6.21');
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['gradle-check'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('build.gradle sets "ext.kotlin_version" to "1.6.21". The '
'minimum Kotlin version that can be specified is '
'$minKotlinVersion, for compatibility with modern dependencies.'),
]),
);
});
});
}