Use Gradle KTS in new Android app projects by default (2nd try) (#157195)

This PR resolves #151166

This PR relands #154061
This commit is contained in:
Bartek Pacia 2024-10-22 19:28:31 +02:00 committed by GitHub
parent 9edd1ae008
commit eb5ef69d6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 162 additions and 131 deletions

View File

@ -29,14 +29,17 @@ Future<void> main() async {
await runProjectTest((FlutterProject flutterProject) async {
await inDirectory(path.join(flutterProject.rootPath, 'android'), () async {
section('Insert gradle testing script');
final File build = File(path.join(
flutterProject.rootPath, 'android', 'app', 'build.gradle',
));
build.writeAsStringSync(
final File buildFile = getAndroidBuildFile(path.join(flutterProject.rootPath, 'android', 'app'));
buildFile.writeAsStringSync(
'''
task printEngineMavenUrl() {
tasks.register("printEngineMavenUrl") {
doLast {
println project.repositories.find { it.name == 'maven' }.url
project.repositories.forEach { repo ->
if (repo.name == "maven") {
repo as MavenArtifactRepository
logger.quiet(repo.url.toString())
}
}
}
}
''',

View File

@ -256,18 +256,17 @@ class FlutterProject {
String get rootPath => path.join(parent.path, name);
String get androidPath => path.join(rootPath, 'android');
String get iosPath => path.join(rootPath, 'ios');
File get appBuildFile => getAndroidBuildFile(path.join(androidPath, 'app'));
Future<void> addCustomBuildType(String name, {required String initWith}) async {
final File buildScript = File(
path.join(androidPath, 'app', 'build.gradle'),
);
final File buildScript = appBuildFile;
buildScript.openWrite(mode: FileMode.append).write('''
android {
buildTypes {
$name {
initWith $initWith
create("$name") {
initWith(getByName("$initWith"))
}
}
}
@ -288,14 +287,12 @@ android {
}
Future<void> setMinSdkVersion(int sdkVersion) async {
final File buildScript = File(
path.join(androidPath, 'app', 'build.gradle'),
);
final File buildScript = appBuildFile;
buildScript.openWrite(mode: FileMode.append).write('''
android {
defaultConfig {
minSdkVersion $sdkVersion
minSdk = $sdkVersion
}
}
''');
@ -308,22 +305,20 @@ android {
}
Future<void> addProductFlavors(Iterable<String> flavors) async {
final File buildScript = File(
path.join(androidPath, 'app', 'build.gradle'),
);
final File buildScript = appBuildFile;
final String flavorConfig = flavors.map((String name) {
return '''
$name {
applicationIdSuffix ".$name"
versionNameSuffix "-$name"
create("$name") {
applicationIdSuffix = ".$name"
versionNameSuffix = "-$name"
}
''';
}).join('\n');
buildScript.openWrite(mode: FileMode.append).write('''
android {
flavorDimensions "mode"
flavorDimensions.add("mode")
productFlavors {
$flavorConfig
}
@ -332,9 +327,7 @@ android {
}
Future<void> introduceError() async {
final File buildScript = File(
path.join(androidPath, 'app', 'build.gradle'),
);
final File buildScript = appBuildFile;
await buildScript.writeAsString((await buildScript.readAsString()).replaceAll('buildTypes', 'builTypes'));
}
@ -477,3 +470,14 @@ String? validateSnapshotDependency(FlutterProject project, String expectedTarget
return contentSnapshot.contains('$expectedTarget ')
? null : 'Dependency file should have $expectedTarget as target. Instead found $contentSnapshot';
}
File getAndroidBuildFile(String androidAppPath, {bool settings = false}) {
final File groovyFile = File(path.join(androidAppPath, settings ? 'settings.gradle' : 'build.gradle'));
final File kotlinFile = File(path.join(androidAppPath, settings ? 'settings.gradle.kts' : 'build.gradle.kts'));
if (groovyFile.existsSync()) {
return groovyFile;
}
return kotlinFile;
}

View File

@ -5,6 +5,8 @@
import 'dart:io';
import 'package:file/local.dart';
import 'apk_utils.dart';
import 'task_result.dart';
import 'utils.dart';
@ -16,13 +18,13 @@ import 'utils.dart';
/// by an easily find/replaceable string.
const String gradleSettingsFileContent = r'''
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
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")
@ -34,12 +36,12 @@ pluginManagement {
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "AGP_REPLACE_ME" apply false
id "org.jetbrains.kotlin.android" version "KGP_REPLACE_ME" apply false
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "AGP_REPLACE_ME" apply false
id("org.jetbrains.kotlin.android") version "KGP_REPLACE_ME" apply false
}
include ":app"
include(":app")
''';
@ -58,7 +60,7 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-GRADLE_REPLACE
''';
const String gradleReplacementString = 'GRADLE_REPLACE_ME';
const String flutterCompileSdkString = 'flutter.compileSdkVersion';
final RegExp flutterCompileSdkString = RegExp(r'flutter\.compileSdkVersion|flutter\.compileSdk');
/// A simple class containing a Kotlin, Gradle, and AGP version.
class VersionTuple {
@ -110,8 +112,7 @@ Future<TaskResult> buildFlutterApkWithSpecifiedDependencyVersions({
final String appPath = '${innerTempDir.absolute.path}/dependency_checker_app';
if (versions.compileSdkVersion != null) {
final File appGradleBuild = localFileSystem.file(localFileSystem.path.join(
appPath, 'android', 'app', 'build.gradle'));
final File appGradleBuild = getAndroidBuildFile(localFileSystem.path.join(appPath, 'android', 'app'));
final String appBuildContent = appGradleBuild.readAsStringSync()
.replaceFirst(flutterCompileSdkString, versions.compileSdkVersion!);
appGradleBuild.writeAsStringSync(appBuildContent);
@ -126,12 +127,14 @@ Future<TaskResult> buildFlutterApkWithSpecifiedDependencyVersions({
);
await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
final File gradleSettings = localFileSystem.file(localFileSystem.path.join(
appPath, 'android', 'settings.gradle'));
final File gradleSettingsFile = getAndroidBuildFile(
localFileSystem.path.join(appPath, 'android'),
settings: true,
);
final String settingsContent = gradleSettingsFileContent
.replaceFirst(agpReplacementString, versions.agpVersion)
.replaceFirst(kgpReplacementString, versions.kotlinVersion);
await gradleSettings.writeAsString(settingsContent, flush: true);
await gradleSettingsFile.writeAsString(settingsContent, flush: true);
// Ensure that gradle files exists from templates.

View File

@ -467,7 +467,19 @@ class AndroidProject extends FlutterProjectPlatform {
static final RegExp _androidNamespacePattern = RegExp('android {[\\S\\s]+namespace\\s*=?\\s*[\'"](.+)[\'"]');
static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\s*=?\\s*[\'"](.*)[\'"]\\s*\$');
static final RegExp _imperativeKotlinPluginPattern = RegExp('^\\s*apply plugin\\:\\s+[\'"]kotlin-android[\'"]\\s*\$');
static final RegExp _declarativeKotlinPluginPattern = RegExp('^\\s*id\\s+[\'"]kotlin-android[\'"]\\s*\$');
/// Examples of strings that this regex matches:
/// - `id "kotlin-android"`
/// - `id("kotlin-android")`
/// - `id ( "kotlin-android" ) `
/// - `id "org.jetbrains.kotlin.android"`
/// - `id("org.jetbrains.kotlin.android")`
/// - `id ( "org.jetbrains.kotlin.android" )`
static final List<RegExp> _declarativeKotlinPluginPatterns = <RegExp>[
RegExp('^\\s*id\\s*\\(?\\s*[\'"]kotlin-android[\'"]\\s*\\)?\\s*\$'),
RegExp('^\\s*id\\s*\\(?\\s*[\'"]org.jetbrains.kotlin.android[\'"]\\s*\\)?\\s*\$'),
];
/// Pattern used to find the assignment of the "group" property in Gradle.
/// Expected example: `group "dev.flutter.plugin"`
@ -563,7 +575,9 @@ class AndroidProject extends FlutterProjectPlatform {
/// True, if the app project is using Kotlin.
bool get isKotlin {
final bool imperativeMatch = firstMatchInFile(appGradleFile, _imperativeKotlinPluginPattern) != null;
final bool declarativeMatch = firstMatchInFile(appGradleFile, _declarativeKotlinPluginPattern) != null;
final bool declarativeMatch = _declarativeKotlinPluginPatterns.any((RegExp pattern) {
return (firstMatchInFile(appGradleFile, pattern) != null);
});
return imperativeMatch || declarativeMatch;
}

View File

@ -1,8 +1,8 @@
plugins {
id "com.android.application"
id "kotlin-android"
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
id("dev.flutter.flutter-gradle-plugin")
}
android {
@ -16,7 +16,7 @@ android {
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
defaultConfig {
@ -34,7 +34,7 @@ android {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
signingConfig = signingConfigs.getByName("debug")
}
}
}

View File

@ -0,0 +1,18 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = file("../build")
subprojects {
project.buildDir = file("${rootProject.buildDir}/${project.name}")
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}

View File

@ -1,18 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@ -1,8 +1,8 @@
plugins {
id "com.android.application"
id "kotlin-android"
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
id("dev.flutter.flutter-gradle-plugin")
}
android {
@ -16,7 +16,7 @@ android {
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
defaultConfig {
@ -34,7 +34,7 @@ android {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
signingConfig = signingConfigs.getByName("debug")
}
}
}

View File

@ -0,0 +1,18 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = file("../build")
subprojects {
project.buildDir = file("${rootProject.buildDir}/${project.name}")
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}

View File

@ -1,18 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,25 @@
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()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "{{agpVersion}}" apply false
id("org.jetbrains.kotlin.android") version "{{kotlinVersion}}" apply false
}
include(":app")

View File

@ -1,25 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "{{agpVersion}}" apply false
id "org.jetbrains.kotlin.android" version "{{kotlinVersion}}" apply false
}
include ":app"

View File

@ -14,13 +14,13 @@
"templates/app_shared/.idea/workspace.xml.tmpl",
"templates/app_shared/.metadata.tmpl",
"templates/app_shared/analysis_options.yaml.tmpl",
"templates/app_shared/android-java.tmpl/app/build.gradle.tmpl",
"templates/app_shared/android-java.tmpl/app/build.gradle.kts.tmpl",
"templates/app_shared/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl",
"templates/app_shared/android-java.tmpl/build.gradle.tmpl",
"templates/app_shared/android-java.tmpl/build.gradle.kts.tmpl",
"templates/app_shared/android-java.tmpl/projectName_android.iml.tmpl",
"templates/app_shared/android-kotlin.tmpl/app/build.gradle.tmpl",
"templates/app_shared/android-kotlin.tmpl/app/build.gradle.kts.tmpl",
"templates/app_shared/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl",
"templates/app_shared/android-kotlin.tmpl/build.gradle.tmpl",
"templates/app_shared/android-kotlin.tmpl/build.gradle.kts.tmpl",
"templates/app_shared/android-kotlin.tmpl/projectName_android.iml.tmpl",
"templates/app_shared/android.tmpl/.gitignore",
"templates/app_shared/android.tmpl/app/src/debug/AndroidManifest.xml.tmpl",
@ -36,7 +36,7 @@
"templates/app_shared/android.tmpl/app/src/main/res/values/styles.xml",
"templates/app_shared/android.tmpl/app/src/profile/AndroidManifest.xml.tmpl",
"templates/app_shared/android.tmpl/gradle.properties.tmpl",
"templates/app_shared/android.tmpl/settings.gradle.tmpl",
"templates/app_shared/android.tmpl/settings.gradle.kts.tmpl",
"templates/app_shared/android.tmpl/gradle/wrapper/gradle-wrapper.properties.tmpl",
"templates/app_shared/android.tmpl/settings.gradle",
"templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl",

View File

@ -44,7 +44,7 @@ void main() {
test(
'build succeeds targeting string compileSdkVersion',
() async {
final File buildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
final File buildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle.kts');
// write a build.gradle with compileSdkVersion as `android-UpsideDownCake` which is a string preview version
buildGradleFile.writeAsStringSync(
buildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkVersion = "android-UpsideDownCake"'),
@ -72,7 +72,7 @@ void main() {
test(
'build succeeds targeting string compileSdkPreview',
() async {
final File buildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
final File buildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle.kts');
// write a build.gradle with compileSdkPreview as `UpsideDownCake` which is a string preview version
buildGradleFile.writeAsStringSync(
buildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkPreview = "UpsideDownCake"'),
@ -100,7 +100,7 @@ void main() {
test(
'build succeeds when both example app and plugin target compileSdkPreview',
() async {
final File appBuildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
final File appBuildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle.kts');
// write a build.gradle with compileSdkPreview as `UpsideDownCake` which is a string preview version
appBuildGradleFile.writeAsStringSync(
appBuildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkPreview = "UpsideDownCake"'),

View File

@ -3126,9 +3126,9 @@ void main() {
await runner.run(<String>['create', '--no-pub', projectDir.path]);
expect(globals.fs.isFileSync('${projectDir.path}/android/app/build.gradle'), true);
expect(globals.fs.isFileSync('${projectDir.path}/android/app/build.gradle.kts'), true);
final String buildContent = await globals.fs.file('${projectDir.path}/android/app/build.gradle').readAsString();
final String buildContent = await globals.fs.file('${projectDir.path}/android/app/build.gradle.kts').readAsString();
expect(buildContent.contains('compileSdk = flutter.compileSdkVersion'), true);
expect(buildContent.contains('ndkVersion = flutter.ndkVersion'), true);

View File

@ -54,7 +54,7 @@ void main() {
final Directory pluginExampleAppDir = pluginAppDir.childDirectory('example');
final File projectGradleFile = pluginExampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
final File projectGradleFile = pluginExampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle.kts');
expect(projectGradleFile, exists);
final String projectBuildGradle = projectGradleFile.readAsStringSync();

View File

@ -51,7 +51,7 @@ void main() {
final Directory exampleAppDir =
tempDir.childDirectory(testName).childDirectory('example');
final File buildGradleFile = exampleAppDir.childDirectory('android').childFile('build.gradle');
final File buildGradleFile = exampleAppDir.childDirectory('android').childFile('build.gradle.kts');
expect(buildGradleFile, exists);
final String buildGradle = buildGradleFile.readAsStringSync();

View File

@ -53,7 +53,7 @@ void main() {
final Directory pluginExampleAppDir = pluginAppDir.childDirectory('example');
final File projectGradleFile = pluginExampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
final File projectGradleFile = pluginExampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle.kts');
expect(projectGradleFile, exists);
final String projectBuildGradle = projectGradleFile.readAsStringSync();

View File

@ -54,8 +54,15 @@ for (final String agpVersion in agpVersions) {
final Directory packageDirectory = await createTestProject(packageName, tempDirectory);
final Directory exampleDirectory = packageDirectory.childDirectory('example');
final File buildGradleFile = exampleDirectory.childDirectory('android').childFile('build.gradle');
final File appBuildGradleFile = exampleDirectory.childDirectory('android').childDirectory('app').childFile('build.gradle');
File buildGradleFile = exampleDirectory.childDirectory('android').childFile('build.gradle');
if (!buildGradleFile.existsSync()) {
buildGradleFile = exampleDirectory.childDirectory('android').childFile('build.gradle.kts');
}
File appBuildGradleFile = exampleDirectory.childDirectory('android').childDirectory('app').childFile('build.gradle');
if (!appBuildGradleFile.existsSync()) {
appBuildGradleFile = exampleDirectory.childDirectory('android').childDirectory('app').childFile('build.gradle.kts');
}
expect(buildGradleFile, exists);
expect(appBuildGradleFile, exists);

View File

@ -160,7 +160,7 @@ Future<ProcessResult> buildFlutterApkWithSpecifiedDependencyVersions({
if (versions.compileSdkVersion != null) {
final File appGradleBuild = File(fileSystem.path.join(
app.path, 'android', 'app', 'build.gradle'));
app.path, 'android', 'app', 'build.gradle.kts'));
final String appBuildContent = appGradleBuild.readAsStringSync()
.replaceFirst(flutterCompileSdkString, versions.compileSdkVersion!);
appGradleBuild.writeAsStringSync(appBuildContent);