Skip to content

Commit 8febbba

Browse files
[ci] Enforce a minimum Kotlin version in examples (#3979)
#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).
1 parent 19c6414 commit 8febbba

File tree

11 files changed

+141
-10
lines changed

11 files changed

+141
-10
lines changed

packages/animations/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.21'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/dynamic_layouts/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.21'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/flutter_markdown/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.21'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/go_router/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.21'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/rfw/example/hello/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.0'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/rfw/example/local/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.0'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/rfw/example/remote/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.0'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/shared_preferences/shared_preferences/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.21'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

packages/shared_preferences/shared_preferences_android/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.21'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
mavenCentral()

script/tool/lib/src/gradle_check_command.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
// found in the LICENSE file.
44

55
import 'package:file/file.dart';
6+
import 'package:meta/meta.dart';
7+
import 'package:pub_semver/pub_semver.dart';
68

79
import 'common/core.dart';
810
import 'common/package_looping_command.dart';
911
import 'common/plugin_utils.dart';
1012
import 'common/repository_package.dart';
1113

14+
/// The lowest `ext.kotlin_version` that example apps are allowed to use.
15+
@visibleForTesting
16+
final Version minKotlinVersion = Version(1, 7, 10);
17+
1218
/// A command to enforce gradle file conventions and best practices.
1319
class GradleCheckCommand extends PackageLoopingCommand {
1420
/// Creates an instance of the gradle check command.
@@ -125,6 +131,9 @@ class GradleCheckCommand extends PackageLoopingCommand {
125131
if (!_validateJavacLintConfig(package, lines)) {
126132
succeeded = false;
127133
}
134+
if (!_validateKotlinVersion(package, lines)) {
135+
succeeded = false;
136+
}
128137
return succeeded;
129138
}
130139

@@ -347,4 +356,26 @@ gradle.projectsEvaluated {
347356
}
348357
return true;
349358
}
359+
360+
/// Validates whether the given [example] has its Kotlin version set to at
361+
/// least a minimum value, if it is set at all.
362+
bool _validateKotlinVersion(
363+
RepositoryPackage example, List<String> gradleLines) {
364+
final RegExp kotlinVersionRegex =
365+
RegExp(r"ext\.kotlin_version\s*=\s*'([\d.]+)'");
366+
RegExpMatch? match;
367+
if (gradleLines.any((String line) {
368+
match = kotlinVersionRegex.firstMatch(line);
369+
return match != null;
370+
})) {
371+
final Version version = Version.parse(match!.group(1)!);
372+
if (version < minKotlinVersion) {
373+
printError('build.gradle sets "ext.kotlin_version" to "$version". The '
374+
'minimum Kotlin version that can be specified is '
375+
'$minKotlinVersion, for compatibility with modern dependencies.');
376+
return false;
377+
}
378+
}
379+
return true;
380+
}
350381
}

script/tool/test/gradle_check_command_test.dart

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ dependencies {
123123
RepositoryPackage package, {
124124
required String pluginName,
125125
required bool warningsConfigured,
126+
String? kotlinVersion,
126127
}) {
127128
final File buildGradle = package
128129
.platformDirectory(FlutterPlatform.android)
@@ -140,6 +141,7 @@ gradle.projectsEvaluated {
140141
''';
141142
buildGradle.writeAsStringSync('''
142143
buildscript {
144+
${kotlinVersion == null ? '' : "ext.kotlin_version = '$kotlinVersion'"}
143145
repositories {
144146
google()
145147
mavenCentral()
@@ -228,9 +230,12 @@ dependencies {
228230
bool includeNamespace = true,
229231
bool commentNamespace = false,
230232
bool warningsConfigured = true,
233+
String? kotlinVersion,
231234
}) {
232235
writeFakeExampleTopLevelBuildGradle(package,
233-
pluginName: pluginName, warningsConfigured: warningsConfigured);
236+
pluginName: pluginName,
237+
warningsConfigured: warningsConfigured,
238+
kotlinVersion: kotlinVersion);
234239
writeFakeExampleAppBuildGradle(package,
235240
includeNamespace: includeNamespace, commentNamespace: commentNamespace);
236241
}
@@ -644,4 +649,99 @@ dependencies {
644649
],
645650
));
646651
});
652+
653+
group('Kotlin version check', () {
654+
test('passes if not set', () async {
655+
const String packageName = 'a_package';
656+
final RepositoryPackage package =
657+
createFakePackage('a_package', packagesDir);
658+
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
659+
writeFakeManifest(package);
660+
final RepositoryPackage example = package.getExamples().first;
661+
writeFakeExampleBuildGradles(example, pluginName: packageName);
662+
writeFakeManifest(example, isApp: true);
663+
664+
final List<String> output =
665+
await runCapturingPrint(runner, <String>['gradle-check']);
666+
667+
expect(
668+
output,
669+
containsAllInOrder(<Matcher>[
670+
contains('Validating android/build.gradle'),
671+
]),
672+
);
673+
});
674+
675+
test('passes if at the minimum allowed version', () async {
676+
const String packageName = 'a_package';
677+
final RepositoryPackage package =
678+
createFakePackage('a_package', packagesDir);
679+
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
680+
writeFakeManifest(package);
681+
final RepositoryPackage example = package.getExamples().first;
682+
writeFakeExampleBuildGradles(example,
683+
pluginName: packageName, kotlinVersion: minKotlinVersion.toString());
684+
writeFakeManifest(example, isApp: true);
685+
686+
final List<String> output =
687+
await runCapturingPrint(runner, <String>['gradle-check']);
688+
689+
expect(
690+
output,
691+
containsAllInOrder(<Matcher>[
692+
contains('Validating android/build.gradle'),
693+
]),
694+
);
695+
});
696+
697+
test('passes if above the minimum allowed version', () async {
698+
const String packageName = 'a_package';
699+
final RepositoryPackage package =
700+
createFakePackage('a_package', packagesDir);
701+
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
702+
writeFakeManifest(package);
703+
final RepositoryPackage example = package.getExamples().first;
704+
writeFakeExampleBuildGradles(example,
705+
pluginName: packageName, kotlinVersion: '99.99.0');
706+
writeFakeManifest(example, isApp: true);
707+
708+
final List<String> output =
709+
await runCapturingPrint(runner, <String>['gradle-check']);
710+
711+
expect(
712+
output,
713+
containsAllInOrder(<Matcher>[
714+
contains('Validating android/build.gradle'),
715+
]),
716+
);
717+
});
718+
719+
test('fails if below the minimum allowed version', () async {
720+
const String packageName = 'a_package';
721+
final RepositoryPackage package =
722+
createFakePackage('a_package', packagesDir);
723+
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
724+
writeFakeManifest(package);
725+
final RepositoryPackage example = package.getExamples().first;
726+
writeFakeExampleBuildGradles(example,
727+
pluginName: packageName, kotlinVersion: '1.6.21');
728+
writeFakeManifest(example, isApp: true);
729+
730+
Error? commandError;
731+
final List<String> output = await runCapturingPrint(
732+
runner, <String>['gradle-check'], errorHandler: (Error e) {
733+
commandError = e;
734+
});
735+
736+
expect(commandError, isA<ToolExit>());
737+
expect(
738+
output,
739+
containsAllInOrder(<Matcher>[
740+
contains('build.gradle sets "ext.kotlin_version" to "1.6.21". The '
741+
'minimum Kotlin version that can be specified is '
742+
'$minKotlinVersion, for compatibility with modern dependencies.'),
743+
]),
744+
);
745+
});
746+
});
647747
}

0 commit comments

Comments
 (0)