[tool] Add initial gradle validation command (#3715)

Adds a repo tool command to validate that all plugins set an explicit Java compatibility version, instead of using whatever the local toolchain happens to be (which creates the potential for issues like https://github.com/flutter/flutter/issues/124839 where our CI passes but builds fail for some clients because our default is newer than theirs).

Currently that's all it checks, but we can add any other gradle best practices we want to enforce here in the future.

This also enables the new check in CI, and fixes all the violations it found.

Fixes https://github.com/flutter/flutter/issues/124839
This commit is contained in:
stuartmorgan
2023-04-17 09:35:22 -07:00
committed by GitHub
parent 0277f2ad9d
commit a4ced6bd36
19 changed files with 362 additions and 6 deletions

View File

@ -106,6 +106,7 @@ task:
# run with --require-excerpts and no exclusions.
- ./script/tool_runner.sh readme-check --require-excerpts --exclude=script/configs/temp_exclude_excerpt.yaml
dependabot_script: $PLUGIN_TOOL_COMMAND dependabot-check
gradle_script: $PLUGIN_TOOL_COMMAND gradle-check
version_script:
# For pre-submit, pass the PR labels to the script to allow for
# check overrides.

View File

@ -1,3 +1,7 @@
## 0.3.0+1
* Sets an explicit Java compatibility version.
## 0.3.0
* **BREAKING CHANGE**: Migrates uses of the deprecated `@Beta` annotation to the new `@ExperimentalApi` annotation.

View File

@ -28,6 +28,11 @@ android {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkAllWarnings true
warningsAsErrors true

View File

@ -3,7 +3,7 @@ description: Java classes for testing Flutter apps using Espresso.
Allows driving Flutter widgets from a native Espresso test.
repository: https://github.com/flutter/packages/tree/main/packages/espresso
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+espresso%22
version: 0.3.0
version: 0.3.0+1
environment:
sdk: ">=2.17.0 <4.0.0"

View File

@ -1,5 +1,6 @@
## NEXT
## 2.0.10
* Sets an explicit Java compatibility version.
* Aligns Dart and Flutter SDK constraints.
## 2.0.9

View File

@ -29,6 +29,11 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'proguard.txt'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkAllWarnings true
warningsAsErrors true

View File

@ -2,7 +2,7 @@ name: flutter_plugin_android_lifecycle
description: Flutter plugin for accessing an Android Lifecycle within other plugins.
repository: https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_plugin_android_lifecycle%22
version: 2.0.9
version: 2.0.10
environment:
sdk: ">=2.17.0 <4.0.0"

View File

@ -1,3 +1,7 @@
## 6.1.10
* Sets an explicit Java compatibility version.
## 6.1.9
* Updates play-services-auth version to 20.5.0.

View File

@ -28,6 +28,11 @@ android {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkAllWarnings true
warningsAsErrors true

View File

@ -2,7 +2,7 @@ name: google_sign_in_android
description: Android implementation of the google_sign_in plugin.
repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
version: 6.1.9
version: 6.1.10
environment:
sdk: ">=2.17.0 <4.0.0"

View File

@ -1,3 +1,7 @@
## 1.0.22
* Sets an explicit Java compatibility version.
## 1.0.21
* Clarifies explanation of endorsement in README.

View File

@ -28,6 +28,11 @@ android {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkAllWarnings true
warningsAsErrors true

View File

@ -2,7 +2,7 @@ name: local_auth_android
description: Android implementation of the local_auth plugin.
repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
version: 1.0.21
version: 1.0.22
environment:
sdk: ">=2.17.0 <4.0.0"

View File

@ -1,3 +1,7 @@
## 6.0.28
* Sets an explicit Java compatibility version.
## 6.0.27
* Fixes Java warnings.

View File

@ -28,6 +28,11 @@ android {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkAllWarnings true
warningsAsErrors true

View File

@ -2,7 +2,7 @@ name: url_launcher_android
description: Android implementation of the url_launcher plugin.
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
version: 6.0.27
version: 6.0.28
environment:
sdk: ">=2.18.0 <4.0.0"

View File

@ -0,0 +1,92 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'common/core.dart';
import 'common/package_looping_command.dart';
import 'common/repository_package.dart';
/// A command to enforce gradle file conventions and best practices.
class GradleCheckCommand extends PackageLoopingCommand {
/// Creates an instance of the gradle check command.
GradleCheckCommand(Directory packagesDir) : super(packagesDir);
@override
final String name = 'gradle-check';
@override
final String description =
'Checks that gradle files follow repository conventions.';
@override
bool get hasLongOutput => false;
@override
PackageLoopingType get packageLoopingType =>
PackageLoopingType.includeAllSubpackages;
@override
Future<PackageResult> runForPackage(RepositoryPackage package) async {
if (!package.platformDirectory(FlutterPlatform.android).existsSync()) {
return PackageResult.skip('No android/ directory.');
}
const String exampleDirName = 'example';
final bool isExample = package.directory.basename == exampleDirName ||
package.directory.parent.basename == exampleDirName;
if (!_validateBuildGradle(package, isExample: isExample)) {
return PackageResult.fail();
}
return PackageResult.success();
}
bool _validateBuildGradle(RepositoryPackage package,
{required bool isExample}) {
// Currently the only check is not relevant to examples; checks that apply
// to both plugins and examples should go above here.
if (!isExample) {
print('${indentation}Validating android/build.gradle.');
final String contents = package
.platformDirectory(FlutterPlatform.android)
.childFile('build.gradle')
.readAsStringSync();
final List<String> lines = contents.split('\n');
if (!lines.any((String line) =>
line.contains('languageVersion') &&
!line.trim().startsWith('//')) &&
!lines.any((String line) =>
line.contains('sourceCompatibility') &&
!line.trim().startsWith('//'))) {
const String errorMessage = '''
build.gradle must set an explicit Java compatibility version.
This can be done either via "sourceCompatibility":
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
}
or "toolchain":
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
See:
https://docs.gradle.org/current/userguide/java_plugin.html#toolchain_and_compatibility
for more details.''';
printError(
'$indentation${errorMessage.split('\n').join('\n$indentation')}');
return false;
}
}
return true;
}
}

View File

@ -19,6 +19,7 @@ import 'federation_safety_check_command.dart';
import 'firebase_test_lab_command.dart';
import 'fix_command.dart';
import 'format_command.dart';
import 'gradle_check_command.dart';
import 'license_check_command.dart';
import 'lint_android_command.dart';
import 'list_command.dart';
@ -66,6 +67,7 @@ void main(List<String> args) {
..addCommand(FirebaseTestLabCommand(packagesDir))
..addCommand(FixCommand(packagesDir))
..addCommand(FormatCommand(packagesDir))
..addCommand(GradleCheckCommand(packagesDir))
..addCommand(LicenseCheckCommand(packagesDir))
..addCommand(LintAndroidCommand(packagesDir))
..addCommand(PodspecCheckCommand(packagesDir))

View File

@ -0,0 +1,219 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/gradle_check_command.dart';
import 'package:test/test.dart';
import 'util.dart';
void main() {
late CommandRunner<void> runner;
late FileSystem fileSystem;
late Directory packagesDir;
setUp(() {
fileSystem = MemoryFileSystem();
packagesDir = fileSystem.currentDirectory.childDirectory('packages');
createPackagesDirectory(parentDir: packagesDir.parent);
final GradleCheckCommand command = GradleCheckCommand(
packagesDir,
);
runner = CommandRunner<void>(
'gradle_check_command', 'Test for gradle_check_command');
runner.addCommand(command);
});
void writeFakeBuildGradle(
RepositoryPackage package, {
bool includeLanguageVersion = false,
bool includeSourceCompat = false,
bool commentRequiredLine = false,
}) {
final File buildGradle = package
.platformDirectory(FlutterPlatform.android)
.childFile('build.gradle');
buildGradle.createSync(recursive: true);
final String compileOptionsSection = '''
compileOptions {
${commentRequiredLine ? '// ' : ''}sourceCompatibility JavaVersion.VERSION_1_8
}
''';
final String javaSection = '''
java {
toolchain {
${commentRequiredLine ? '// ' : ''}languageVersion = JavaLanguageVersion.of(8)
}
}
''';
buildGradle.writeAsStringSync('''
group 'dev.flutter.plugins.fake'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
${includeLanguageVersion ? javaSection : ''}
android {
compileSdkVersion 33
defaultConfig {
minSdkVersion 30
}
lintOptions {
checkAllWarnings true
}
testOptions {
unitTests.includeAndroidResources = true
}
${includeSourceCompat ? compileOptionsSection : ''}
}
dependencies {
implementation 'fake.package:fake:1.0.0'
}
''');
}
test('skips when package has no Android directory', () async {
createFakePackage('a_package', packagesDir, examples: <String>[]);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Skipped 1 package(s)'),
]),
);
});
test('fails when build.gradle has no java compatibility version', () async {
final RepositoryPackage package =
createFakePlugin('a_plugin', packagesDir, examples: <String>[]);
writeFakeBuildGradle(package);
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 must set an explicit Java compatibility version.'),
]),
);
});
test('passes when sourceCompatibility is specified', () async {
final RepositoryPackage package =
createFakePlugin('a_plugin', packagesDir, examples: <String>[]);
writeFakeBuildGradle(package, includeSourceCompat: true);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle'),
]),
);
});
test('passes when toolchain languageVersion is specified', () async {
final RepositoryPackage package =
createFakePlugin('a_plugin', packagesDir, examples: <String>[]);
writeFakeBuildGradle(package, includeLanguageVersion: true);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle'),
]),
);
});
test('does not require java version in examples', () async {
final RepositoryPackage package = createFakePlugin('a_plugin', packagesDir);
writeFakeBuildGradle(package, includeLanguageVersion: true);
writeFakeBuildGradle(package.getExamples().first);
final List<String> output =
await runCapturingPrint(runner, <String>['gradle-check']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle'),
contains('Ran for 2 package(s)'),
]),
);
});
test('fails when java compatibility version is commented out', () async {
final RepositoryPackage package =
createFakePlugin('a_plugin', packagesDir, examples: <String>[]);
writeFakeBuildGradle(package,
includeSourceCompat: true, commentRequiredLine: 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 must set an explicit Java compatibility version.'),
]),
);
});
test('fails when languageVersion is commented out', () async {
final RepositoryPackage package =
createFakePlugin('a_plugin', packagesDir, examples: <String>[]);
writeFakeBuildGradle(package,
includeLanguageVersion: true, commentRequiredLine: 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 must set an explicit Java compatibility version.'),
]),
);
});
}