blob: d9a1ff916109b0855191bfac456950827625020e [file]
// Copyright 2013 The Flutter Authors
// 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:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
import 'package:flutter_plugin_tools/src/validate_command.dart';
import 'package:flutter_plugin_tools/src/validators/gradle_validator.dart';
import 'package:git/git.dart';
import 'package:test/test.dart';
import 'util.dart';
const String _defaultFakeNamespace = 'dev.flutter.foo';
void main() {
late CommandRunner<void> runner;
late Directory packagesDir;
const javaIncompatabilityIndicator =
'build.gradle.kts must set an explicit Java compatibility version.';
setUp(() {
final GitDir gitDir;
(:packagesDir, processRunner: _, gitProcessRunner: _, :gitDir) = configureBaseCommandMocks();
final command = ValidateCommand(
packagesDir,
gitDir: gitDir,
targetedValidators: {Validator.gradle},
);
runner = CommandRunner<void>('validate_gradle_test', 'Test for gradle validations');
runner.addCommand(command);
});
/// Writes a fake android/build.gradle.kts file for plugin [package] with the
/// given options.
void writeFakePluginBuildGradle(
RepositoryPackage package, {
bool includeLanguageVersion = false,
bool includeSourceCompat = false,
bool includeTargetCompat = false,
bool commentSourceLanguage = false,
bool includeNamespace = true,
bool commentNamespace = false,
bool warningsConfigured = true,
String compileSdk = '36',
bool includeKotlinCompilerOptions = true,
bool commentKotlinCompilerOptions = false,
bool useDeprecatedJvmTargetStyle = false,
bool useJavaVersionStringForJvmTarget = false,
int jvmTargetValue = 17,
int kotlinJvmValue = 17,
bool includeKotlinGradlePlugin = false,
bool includeDeprecatedKotlinOptionsInsideAndroid = false,
bool includeDeprecatedKotlinOptionsInsideKotlin = false,
}) {
final File buildGradle = package
.platformDirectory(FlutterPlatform.android)
.childFile('build.gradle.kts');
buildGradle.createSync(recursive: true);
const warningConfig = '''
lint {
checkAllWarnings = true
warningsAsErrors = true
disable.addAll(setOf("AndroidGradlePluginVersion", "InvalidPackage", "GradleDependency", "NewerVersionAvailable"))
baseline = file("lint-baseline.xml")
}
''';
final javaSection =
'''
java {
toolchain {
${commentSourceLanguage ? '// ' : ''}languageVersion = JavaLanguageVersion.of(8)
}
}
''';
final sourceCompat =
'${commentSourceLanguage ? '// ' : ''}sourceCompatibility = JavaVersion.VERSION_$jvmTargetValue';
final targetCompat =
'${commentSourceLanguage ? '// ' : ''}targetCompatibility = JavaVersion.VERSION_$jvmTargetValue';
final namespace = ' ${commentNamespace ? '// ' : ''}namespace = "$_defaultFakeNamespace"';
final _KotlinConfigParts kotlinConfigParts = _generateKotlinConfigParts(
commentKotlinCompilerOptions: commentKotlinCompilerOptions,
useJavaVersionStringForJvmTarget: useJavaVersionStringForJvmTarget,
useDeprecatedJvmTargetStyle: useDeprecatedJvmTargetStyle,
jvmTargetValue: jvmTargetValue,
kotlinJvmValue: kotlinJvmValue,
);
buildGradle.writeAsStringSync('''
group = "dev.flutter.plugins.fake"
version = "1.0-SNAPSHOT"
buildscript {
repositories {
google()
mavenCentral()
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
plugins {
id("com.android.library")
${includeKotlinGradlePlugin ? 'id("kotlin-android")' : ''}
}
${includeLanguageVersion ? javaSection : ''}
${includeKotlinCompilerOptions ? kotlinConfigParts.kotlinConfig : ''}
${includeDeprecatedKotlinOptionsInsideKotlin ? kotlinConfigParts.kotlinDeprecatedInKotlinConfig : ''}
android {
${includeNamespace ? namespace : ''}
compileSdk = $compileSdk
defaultConfig {
minSdk = 30
}
${warningsConfigured ? warningConfig : ''}
compileOptions {
${includeSourceCompat ? sourceCompat : ''}
${includeTargetCompat ? targetCompat : ''}
}
${includeDeprecatedKotlinOptionsInsideAndroid ? kotlinConfigParts.kotlinDeprecatedInAndroidConfig : ''}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
dependencies {
implementation("fake.package:fake:1.0.0")
}
''');
}
/// Writes a fake android/build.gradle.kts file for an example [package] with
/// the given options.
void writeFakeExampleTopLevelBuildGradle(
RepositoryPackage package, {
required String pluginName,
bool warningsConfigured = true,
bool includeArtifactHub = true,
}) {
final File buildGradle = package
.platformDirectory(FlutterPlatform.android)
.childFile('build.gradle.kts');
buildGradle.createSync(recursive: true);
final warningConfig =
'''
gradle.projectsEvaluated {
project(":$pluginName") {
tasks.withType<JavaCompile> {
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
}
}
}
''';
buildGradle.writeAsStringSync('''
allprojects {
repositories {
${includeArtifactHub ? GradleValidator.exampleRootGradleArtifactHubString : ''}
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
${warningsConfigured ? warningConfig : ''}
''');
}
/// Writes a fake android/settings.gradle.kts file for an example [package]
/// with the given options.
void writeFakeExampleSettingsGradle(
RepositoryPackage package, {
required String kotlinVersion,
bool includeArtifactHub = true,
bool includeArtifactDocumentation = true,
}) {
final File settingsGradle = package
.platformDirectory(FlutterPlatform.android)
.childFile('settings.gradle.kts');
settingsGradle.createSync(recursive: true);
settingsGradle.writeAsStringSync('''
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("\$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
${includeArtifactDocumentation ? '// See ${GradleValidator.artifactHubDocumentationString} for more info.' : ''}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "$kotlinVersion" apply false
${includeArtifactHub ? 'id("com.google.cloud.artifactregistry.gradle-plugin") version "2.2.1"' : ''}
}
include ":app"
''');
}
/// Writes a fake android/app/build.gradle.kts file for an example [package]
/// with the given options.
void writeFakeExampleAppBuildGradle(
RepositoryPackage package, {
required bool includeNamespace,
required bool commentNamespace,
bool includeKotlinCompilerOptions = true,
bool commentKotlinCompilerOptions = false,
bool useDeprecatedJvmTargetStyle = false,
bool useJavaVersionStringForJvmTarget = false,
int jvmTargetValue = 17,
int kotlinJvmValue = 17,
bool includeKotlinGradlePlugin = false,
bool includeDeprecatedKotlinOptionsInsideAndroid = false,
bool includeDeprecatedKotlinOptionsInsideKotlin = false,
}) {
final File buildGradle = package
.platformDirectory(FlutterPlatform.android)
.childDirectory('app')
.childFile('build.gradle.kts');
buildGradle.createSync(recursive: true);
final namespace = '${commentNamespace ? '// ' : ''}namespace = "$_defaultFakeNamespace"';
final _KotlinConfigParts kotlinConfigParts = _generateKotlinConfigParts(
commentKotlinCompilerOptions: commentKotlinCompilerOptions,
useJavaVersionStringForJvmTarget: useJavaVersionStringForJvmTarget,
useDeprecatedJvmTargetStyle: useDeprecatedJvmTargetStyle,
jvmTargetValue: jvmTargetValue,
kotlinJvmValue: kotlinJvmValue,
);
buildGradle.writeAsStringSync('''
plugins {
id("com.android.application")
${includeKotlinGradlePlugin ? 'id("kotlin-android")' : ''}
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
${includeKotlinCompilerOptions ? kotlinConfigParts.kotlinConfig : ''}
${includeDeprecatedKotlinOptionsInsideKotlin ? kotlinConfigParts.kotlinDeprecatedInKotlinConfig : ''}
android {
${includeNamespace ? namespace : ''}
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_$jvmTargetValue
targetCompatibility = JavaVersion.VERSION_$jvmTargetValue
}
defaultConfig {
applicationId = "$_defaultFakeNamespace.example"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("debug")
}
}
${includeDeprecatedKotlinOptionsInsideAndroid ? kotlinConfigParts.kotlinDeprecatedInAndroidConfig : ''}
}
flutter {
source = "../.."
}
''');
}
void writeFakeExampleBuildGradles(
RepositoryPackage package, {
required String pluginName,
String? kotlinVersion,
bool includeNamespace = true,
bool commentNamespace = false,
bool warningsConfigured = true,
bool includeBuildArtifactHub = true,
bool includeSettingsArtifactHub = true,
bool includeSettingsDocumentationArtifactHub = true,
bool includeKotlinCompilerOptions = true,
bool commentKotlinCompilerOptions = false,
bool useDeprecatedJvmTargetStyle = false,
bool useJavaVersionStringForJvmTarget = false,
int jvmTargetValue = 17,
int kotlinJvmValue = 17,
bool includeKotlinGradlePlugin = false,
bool includeDeprecatedKotlinOptionsInsideAndroid = false,
bool includeDeprecatedKotlinOptionsInsideKotlin = false,
}) {
writeFakeExampleTopLevelBuildGradle(
package,
pluginName: pluginName,
warningsConfigured: warningsConfigured,
includeArtifactHub: includeBuildArtifactHub,
);
writeFakeExampleAppBuildGradle(
package,
includeNamespace: includeNamespace,
commentNamespace: commentNamespace,
includeKotlinCompilerOptions: includeKotlinCompilerOptions,
commentKotlinCompilerOptions: commentKotlinCompilerOptions,
useDeprecatedJvmTargetStyle: useDeprecatedJvmTargetStyle,
useJavaVersionStringForJvmTarget: useJavaVersionStringForJvmTarget,
jvmTargetValue: jvmTargetValue,
kotlinJvmValue: kotlinJvmValue,
includeKotlinGradlePlugin: includeKotlinGradlePlugin,
includeDeprecatedKotlinOptionsInsideAndroid: includeDeprecatedKotlinOptionsInsideAndroid,
includeDeprecatedKotlinOptionsInsideKotlin: includeDeprecatedKotlinOptionsInsideKotlin,
);
writeFakeExampleSettingsGradle(
package,
kotlinVersion: kotlinVersion ?? '2.2.20',
includeArtifactHub: includeSettingsArtifactHub,
includeArtifactDocumentation: includeSettingsDocumentationArtifactHub,
);
}
void writeFakeManifest(
RepositoryPackage package, {
bool isApp = false,
String packageName = _defaultFakeNamespace,
}) {
final Directory androidDir = package.platformDirectory(FlutterPlatform.android);
final Directory startDir = isApp ? androidDir.childDirectory('app') : androidDir;
final File manifest = startDir
.childDirectory('src')
.childDirectory('main')
.childFile('AndroidManifest.xml');
manifest.createSync(recursive: true);
manifest.writeAsString('''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="$packageName">
</manifest>''');
}
test('passes when package has no Android directory', () async {
createFakePackage('a_package', packagesDir, examples: <String>[]);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(output, containsAllInOrder(<Matcher>[contains('Running for a_package')]));
});
test('fails when build.gradle.kts has no java compatibility version', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(output, containsAllInOrder(<Matcher>[contains(javaIncompatabilityIndicator)]));
});
test('fails when sourceCompatibility is provided with out targetCompatibility', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeSourceCompat: true);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(output, containsAllInOrder(<Matcher>[contains(javaIncompatabilityIndicator)]));
});
test('fails when sourceCompatibility/targetCompatibility are below minimum', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeSourceCompat: true,
includeTargetCompat: true,
jvmTargetValue: 11,
kotlinJvmValue: 11,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Which is below the minimum required. Use at least "JavaVersion.VERSION_'),
]),
);
});
test('fails when compatibility values do not match kotlin compiler options', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeSourceCompat: true,
includeTargetCompat: true,
jvmTargetValue: 21,
// ignore: avoid_redundant_argument_values
kotlinJvmValue: 17,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'If build.gradle.kts uses JavaVersion.* and JvmTarget.*, the versions must be the same.',
),
]),
);
});
test('passes when jvmValues are higher than minimim', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeSourceCompat: true,
includeTargetCompat: true,
jvmTargetValue: 21,
kotlinJvmValue: 21,
);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(output, containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]));
});
test('passes when sourceCompatibility and targetCompatibility are specified', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeSourceCompat: true, includeTargetCompat: true);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(output, containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]));
});
test('passes when toolchain languageVersion is specified', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(output, containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]));
});
test('does not require java version in examples', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: pluginName);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_plugin/example'),
contains('Validating android/build.gradle.kts'),
]),
);
});
test('fails when java compatibility version is commented out', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeSourceCompat: true,
includeTargetCompat: true,
commentSourceLanguage: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(output, containsAllInOrder(<Matcher>[contains(javaIncompatabilityIndicator)]));
});
test('fails when languageVersion is commented out', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeLanguageVersion: true, commentSourceLanguage: true);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(output, containsAllInOrder(<Matcher>[contains(javaIncompatabilityIndicator)]));
});
test('fails when plugin namespace does not match AndroidManifest.xml', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package, packageName: 'wrong.package.name');
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts "namespace" must match the "package" attribute in AndroidManifest.xml',
),
]),
);
});
test('fails when namespace is missing', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeLanguageVersion: true, includeNamespace: false);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[contains('build.gradle.kts must set a "namespace"')]),
);
});
test('fails when namespace is missing from example', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePlugin(pluginName, packagesDir);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: pluginName, includeNamespace: false);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[contains('build.gradle.kts must set a "namespace"')]),
);
});
// TODO(stuartmorgan): Consider removing this in the future; we may at some
// point decide that we have a use case of example apps having different
// app IDs and namespaces. For now, it's enforced for consistency so they
// don't just accidentally diverge.
test('fails when namespace in example does not match AndroidManifest.xml', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePlugin(pluginName, packagesDir);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: pluginName);
writeFakeManifest(example, isApp: true, packageName: 'wrong.package.name');
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts "namespace" must match the "package" attribute in AndroidManifest.xml',
),
]),
);
});
test('fails when namespace is commented out', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeLanguageVersion: true, commentNamespace: true);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[contains('build.gradle.kts must set a "namespace"')]),
);
});
test('fails if gradle-driven lint-warnings-as-errors is missing', () async {
const pluginName = 'a_plugin';
final RepositoryPackage plugin = createFakePlugin(
pluginName,
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(plugin, includeLanguageVersion: true, warningsConfigured: false);
writeFakeManifest(plugin);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'This package is not configured to enable all '
'Gradle-driven lint warnings and treat them as errors.',
),
contains('The following packages had errors:'),
]),
);
});
test('fails if plugin example javac lint-warnings-as-errors is missing', () async {
const pluginName = 'a_plugin';
final RepositoryPackage plugin = createFakePlugin(
pluginName,
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
},
);
writeFakePluginBuildGradle(plugin, includeLanguageVersion: true);
writeFakeManifest(plugin);
final RepositoryPackage example = plugin.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: pluginName, warningsConfigured: false);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'The example "example" is not configured to treat javac '
'lints and warnings as errors.',
),
contains('The following packages had errors:'),
]),
);
});
test('passes if non-plugin package example javac lint-warnings-as-errors is missing', () async {
const packageName = 'a_package';
final RepositoryPackage plugin = createFakePackage(packageName, packagesDir);
final RepositoryPackage example = plugin.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: packageName, warningsConfigured: false);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(output, containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]));
});
group('Artifact Hub check', () {
test('passes build.gradle.kts artifact hub check when set', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: packageName,
// ignore: avoid_redundant_argument_values
includeBuildArtifactHub: true,
// ignore: avoid_redundant_argument_values
includeSettingsArtifactHub: true,
// ignore: avoid_redundant_argument_values
includeSettingsDocumentationArtifactHub: true,
);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Validating android/build.gradle.kts'),
contains('Validating android/settings.gradle.kts'),
]),
);
});
test('fails artifact hub check when build and settings sections missing', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: packageName,
includeBuildArtifactHub: false,
includeSettingsArtifactHub: false,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(GradleValidator.exampleRootGradleArtifactHubString),
contains(GradleValidator.exampleSettingsArtifactHubString),
]),
);
});
test('fails build.gradle.kts artifact hub check when missing', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: packageName,
includeBuildArtifactHub: false,
// ignore: avoid_redundant_argument_values
includeSettingsArtifactHub: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[contains(GradleValidator.exampleRootGradleArtifactHubString)]),
);
});
test('fails settings.gradle.kts artifact hub check when missing', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: packageName,
// ignore: avoid_redundant_argument_values
includeBuildArtifactHub: true,
includeSettingsArtifactHub: false,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[contains(GradleValidator.exampleSettingsArtifactHubString)]),
);
expect(output, isNot(contains(GradleValidator.exampleRootGradleArtifactHubString)));
});
test('error message is printed when documentation link is missing', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: packageName,
// ignore: avoid_redundant_argument_values
includeBuildArtifactHub: true,
// ignore: avoid_redundant_argument_values
includeSettingsArtifactHub: true,
includeSettingsDocumentationArtifactHub: false,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[contains(GradleValidator.artifactHubDocumentationString)]),
);
});
});
group('Kotlin version check', () {
test('passes if not set', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: packageName);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('passes if at the minimum allowed version', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
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>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('passes if above the minimum allowed version', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
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>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('fails if below the minimum allowed version', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage('a_package', packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: packageName, kotlinVersion: '1.9.20');
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'settings.gradle.kts sets the "org.jetbrains.kotlin.android" plugin '
'version to "1.9.20". The minimum Kotlin version that can be '
'specified is $minKotlinVersion, for compatibility with modern '
'dependencies.',
),
]),
);
});
});
group('compileSdk check', () {
test('passes if set to a version higher than flutter.compileSdkVersion', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage(
packageName,
packagesDir,
isFlutter: true,
);
// Current flutter.compileSdkVersion is 36.
writeFakePluginBuildGradle(package, includeLanguageVersion: true, compileSdk: '37');
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: packageName);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('passes if set to flutter.compileSdkVersion with Flutter 3.27+', () async {
const 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<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('fails if set to a version lower than flutter.compileSdkVersion', () async {
const packageName = 'a_package';
final RepositoryPackage package = createFakePackage(
packageName,
packagesDir,
isFlutter: true,
);
// Current flutter.compileSdkVersion is 36.
const minCompileSdkVersion = '36';
const testCompileSdkVersion = '35';
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
compileSdk: testCompileSdkVersion,
);
writeFakeManifest(package);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: packageName);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'compileSdk version $testCompileSdkVersion is too low. '
'Minimum required version is $minCompileSdkVersion.',
),
]),
);
});
test('fails if set to flutter.compileSdkVersion with Flutter <3.27', () async {
const 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<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'Use of flutter.compileSdkVersion requires a minimum '
'Flutter version of 3.27, but this package currently supports '
'3.24.0',
),
]),
);
});
});
group('kotlin gradle plugin check', () {
test('passes when kotlin gradle plugin is not applied', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(package, includeLanguageVersion: true);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('fails when kotlin gradle plugin is applied', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
includeKotlinGradlePlugin: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
"The kotlin-android plugin should not be applied in the plugin module's build.gradle.kts",
),
]),
);
});
test('passes when Kotlin Gradle plugin is not applied in app/build.gradle.kts', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: pluginName);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_plugin/example'),
contains('Validating android/app/build.gradle.kts'),
]),
);
});
test('fails when Kotlin Gradle plugin is applied in app/build.gradle.kts', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
includeKotlinGradlePlugin: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
"The kotlin-android plugin should not be applied in the app module's build.gradle.kts",
),
]),
);
});
});
group('kotlin compiler options check', () {
test('passes when kotlin compiler options are specified', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
// ignore: avoid_redundant_argument_values ensure codepath is tested if defaults change.
includeKotlinCompilerOptions: true,
);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('passes when kotlin compiler options are not specified', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
includeKotlinCompilerOptions: false,
);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('passes when kotlin compiler options is commented out', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
commentKotlinCompilerOptions: true,
);
writeFakeManifest(package);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[contains('Validating android/build.gradle.kts')]),
);
});
test('fails when kotlin compiler options uses string jvm version', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
useDeprecatedJvmTargetStyle: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'If build.gradle.kts sets jvmTarget inside kotlin.compilerOptions, it must use JvmTarget syntax.',
),
]),
);
});
test('fails when kotlin compiler options uses JavaVersion string', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
useJavaVersionStringForJvmTarget: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'If build.gradle.kts sets jvmTarget inside kotlin.compilerOptions, it must use JvmTarget syntax.',
),
]),
);
});
test('fails when there is a kotlin compiler options DSL block in the android block', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
includeKotlinCompilerOptions: false,
);
final File buildGradle = package
.platformDirectory(FlutterPlatform.android)
.childFile('build.gradle.kts');
final String contents = buildGradle.readAsStringSync();
final String updatedContents = contents.replaceFirst(
'android {',
'android {\n kotlin {\n compilerOptions {\n jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17\n }\n }',
);
buildGradle.writeAsStringSync(updatedContents);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not nest "kotlin" or "compilerOptions" inside the "android" block. It must be at the top-level',
),
]),
);
});
test('fails when kotlinOptions is used in the android block', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
includeDeprecatedKotlinOptionsInsideAndroid: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not use the deprecated "kotlinOptions" DSL. Use "kotlin.compilerOptions" instead',
),
]),
);
});
test('fails when kotlinOptions is used in the kotlin block', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
includeDeprecatedKotlinOptionsInsideKotlin: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not use the deprecated "kotlinOptions" DSL. Use "kotlin.compilerOptions" instead',
),
]),
);
});
test('fails when kotlinOptions is used in both the android and kotlin blocks', () async {
final RepositoryPackage package = createFakePlugin(
'a_plugin',
packagesDir,
examples: <String>[],
);
writeFakePluginBuildGradle(
package,
includeLanguageVersion: true,
includeDeprecatedKotlinOptionsInsideAndroid: true,
includeDeprecatedKotlinOptionsInsideKotlin: true,
);
writeFakeManifest(package);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not use the deprecated "kotlinOptions" DSL. Use "kotlin.compilerOptions" instead',
),
]),
);
});
});
group('example app kotlin compiler options check', () {
test('passes when kotlin compiler options are specified', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(example, pluginName: pluginName);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_plugin/example'),
contains('Validating android/app/build.gradle.kts'),
]),
);
});
test('passes when kotlin compiler options are not specified', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
includeKotlinCompilerOptions: false,
);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_plugin/example'),
contains('Validating android/app/build.gradle.kts'),
]),
);
});
test('passes when kotlin compiler options is commented out', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
commentKotlinCompilerOptions: true,
);
writeFakeManifest(example, isApp: true);
final List<String> output = await runCapturingPrint(runner, <String>['validate']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for a_plugin/example'),
contains('Validating android/app/build.gradle.kts'),
]),
);
});
test('fails when kotlin compiler options uses string jvm version', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
useDeprecatedJvmTargetStyle: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'If build.gradle.kts sets jvmTarget inside kotlin.compilerOptions, it must use JvmTarget syntax.',
),
]),
);
});
test('fails when kotlin compiler options uses JavaVersion string', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
useJavaVersionStringForJvmTarget: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'If build.gradle.kts sets jvmTarget inside kotlin.compilerOptions, it must use JvmTarget syntax.',
),
]),
);
});
test('fails when there is a kotlin compiler options DSL block in the android block', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
includeKotlinCompilerOptions: false,
);
final File buildGradle = example
.platformDirectory(FlutterPlatform.android)
.childDirectory('app')
.childFile('build.gradle.kts');
final String contents = buildGradle.readAsStringSync();
final String updatedContents = contents.replaceFirst(
'android {',
'android {\n kotlin {\n compilerOptions {\n jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17\n }\n }',
);
buildGradle.writeAsStringSync(updatedContents);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not nest "kotlin" or "compilerOptions" inside the "android" block. It must be at the top-level',
),
]),
);
});
test('fails when kotlinOptions is used in the android block', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
includeKotlinCompilerOptions: false,
includeDeprecatedKotlinOptionsInsideAndroid: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not use the deprecated "kotlinOptions" DSL. Use "kotlin.compilerOptions" instead',
),
]),
);
});
test('fails when kotlinOptions is used in the kotlin block', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
includeKotlinCompilerOptions: false,
includeDeprecatedKotlinOptionsInsideKotlin: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not use the deprecated "kotlinOptions" DSL. Use "kotlin.compilerOptions" instead',
),
]),
);
});
test('fails when kotlinOptions is used in both the android and kotlin blocks', () async {
const pluginName = 'a_plugin';
final RepositoryPackage package = createFakePackage(pluginName, packagesDir);
final RepositoryPackage example = package.getExamples().first;
writeFakeExampleBuildGradles(
example,
pluginName: pluginName,
includeKotlinCompilerOptions: false,
includeDeprecatedKotlinOptionsInsideAndroid: true,
includeDeprecatedKotlinOptionsInsideKotlin: true,
);
writeFakeManifest(example, isApp: true);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner,
<String>['validate'],
errorHandler: (Error e) {
commandError = e;
},
);
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'build.gradle.kts must not use the deprecated "kotlinOptions" DSL. Use "kotlin.compilerOptions" instead',
),
]),
);
});
});
}
class _KotlinConfigParts {
const _KotlinConfigParts({
required this.kotlinConfig,
required this.kotlinDeprecatedInAndroidConfig,
required this.kotlinDeprecatedInKotlinConfig,
});
final String kotlinConfig;
final String kotlinDeprecatedInAndroidConfig;
final String kotlinDeprecatedInKotlinConfig;
}
_KotlinConfigParts _generateKotlinConfigParts({
required bool commentKotlinCompilerOptions,
required bool useJavaVersionStringForJvmTarget,
required bool useDeprecatedJvmTargetStyle,
required int jvmTargetValue,
required int kotlinJvmValue,
}) {
final String kotlinJvmTarget;
if (useJavaVersionStringForJvmTarget) {
kotlinJvmTarget = 'JavaVersion.VERSION_$kotlinJvmValue.toString()';
} else if (useDeprecatedJvmTargetStyle) {
kotlinJvmTarget = '"$jvmTargetValue"';
} else {
kotlinJvmTarget = 'org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_$kotlinJvmValue';
}
final kotlinConfig =
'''
${commentKotlinCompilerOptions ? '// ' : ''}kotlin {
${commentKotlinCompilerOptions ? '// ' : ''} compilerOptions {
${commentKotlinCompilerOptions ? '// ' : ''} jvmTarget = $kotlinJvmTarget
${commentKotlinCompilerOptions ? '// ' : ''} }
${commentKotlinCompilerOptions ? '// ' : ''}}''';
final kotlinDeprecatedInAndroidConfig =
'''
kotlinOptions {
jvmTarget = JavaVersion.VERSION_$kotlinJvmValue.toString()
}''';
final kotlinDeprecatedInKotlinConfig =
'''
kotlin {
$kotlinDeprecatedInAndroidConfig
}''';
return _KotlinConfigParts(
kotlinConfig: kotlinConfig,
kotlinDeprecatedInAndroidConfig: kotlinDeprecatedInAndroidConfig,
kotlinDeprecatedInKotlinConfig: kotlinDeprecatedInKotlinConfig,
);
}