Skip to content

Commit

Permalink
Add support for Gradle Kotlin DSL (#140744)
Browse files Browse the repository at this point in the history
This PR resolves #140548. It's based on my work in #118067.
  • Loading branch information
bartekpacia authored Jan 12, 2024
1 parent 5887d6c commit 0a1af8a
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 66 deletions.
34 changes: 0 additions & 34 deletions examples/hello_world/android/settings.gradle

This file was deleted.

31 changes: 31 additions & 0 deletions examples/hello_world/android/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2014 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.

// Contents of this file automatically generated by dev/tools/bin/generate_gradle_lockfiles.dart.
// Do not merge changes to this file. See #140115.

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 "7.4.2" apply false
}

include(":app")
50 changes: 39 additions & 11 deletions packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import org.gradle.api.tasks.bundling.Jar
import org.gradle.internal.os.OperatingSystem

/**
* For apps only. Provides the flutter extension used in app/build.gradle.
* For apps only. Provides the flutter extension used in the app-level Gradle
* build file (app/build.gradle or app/build.gradle.kts).
*
* The versions specified here should match the values in
* packages/flutter_tools/lib/src/android/gradle_utils.dart, so when bumping,
Expand Down Expand Up @@ -62,7 +63,7 @@ class FlutterExtension {

/**
* Specifies the relative directory to the Flutter project directory.
* In an app project, this is ../.. since the app's build.gradle is under android/app.
* In an app project, this is ../.. since the app's Gradle build file is under android/app.
*/
String source = "../.."

Expand Down Expand Up @@ -90,7 +91,8 @@ buildscript {

/**
* Some apps don't set default compile options.
* Apps can change these values in android/app/build.gradle.
* Apps can change these values in the app-level Gradle build file
* (android/app/build.gradle or android/app/build.gradle.kts).
* This just ensures that default values are set.
*/
android {
Expand Down Expand Up @@ -423,13 +425,12 @@ class FlutterPlugin implements Plugin<Project> {
* just using the `plugins.android` list.
*/
private configureLegacyPluginEachProjects(Project project) {
File settingsGradle = new File(project.projectDir.parentFile, "settings.gradle")
try {
if (!settingsGradle.text.contains("'.flutter-plugins'")) {
if (!settingsGradleFile(project).text.contains("'.flutter-plugins'")) {
return
}
} catch (FileNotFoundException ignored) {
throw new GradleException("settings.gradle does not exist: ${settingsGradle.absolutePath}")
throw new GradleException("settings.gradle/settings.gradle.kts does not exist: ${settingsGradleFile(project).absolutePath}")
}
List<Map<String, Object>> deps = getPluginDependencies(project)
List<String> plugins = getPluginList(project).collect { it.name as String }
Expand All @@ -438,7 +439,7 @@ class FlutterPlugin implements Plugin<Project> {
Project pluginProject = project.rootProject.findProject(":${it.name}")
if (pluginProject == null) {
// Plugin was not included in `settings.gradle`, but is listed in `.flutter-plugins`.
project.logger.error("Plugin project :${it.name} listed, but not found. Please fix your settings.gradle.")
project.logger.error("Plugin project :${it.name} listed, but not found. Please fix your settings.gradle/settings.gradle.kts.")
} else if (doesSupportAndroidPlatform(pluginProject.projectDir.parentFile.path as String)) {
// Plugin has a functioning `android` folder and is included successfully, although it's not supported.
// It must be configured nonetheless, to not throw an "Unresolved reference" exception.
Expand All @@ -451,11 +452,38 @@ class FlutterPlugin implements Plugin<Project> {

// TODO(54566): Can remove this function and its call sites once resolved.
/**
* Returns `true` if the given path contains an `android/build.gradle` file.
* Returns `true` if the given path contains an `android` directory
* containing a `build.gradle` or `build.gradle.kts` file.
*/
private Boolean doesSupportAndroidPlatform(String path) {
File buildGradle = new File(path, 'android' + File.separator + 'build.gradle')
File buildGradleKts = new File(path, 'android' + File.separator + 'build.gradle.kts')
if (buildGradle.exists() && buildGradleKts.exists()) {
logger.error(
"Both build.gradle and build.gradle.kts exist, so " +
"build.gradle.kts is ignored. This is likely a mistake."
)
}

return buildGradle.exists() || buildGradleKts.exists()
}

/**
* Returns the Gradle settings script for the build. When both Groovy and
* Kotlin variants exist, then Groovy (settings.gradle) is preferred over
* Kotlin (settings.gradle.kts). This is the same behavior as Gradle 8.5.
*/
private static Boolean doesSupportAndroidPlatform(String path) {
File editableAndroidProject = new File(path, "android" + File.separator + "build.gradle")
return editableAndroidProject.exists()
private File settingsGradleFile(Project project) {
File settingsGradle = new File(project.projectDir.parentFile, "settings.gradle")
File settingsGradleKts = new File(project.projectDir.parentFile, "settings.gradle.kts")
if (settingsGradle.exists() && settingsGradleKts.exists()) {
logger.error(
"Both settings.gradle and settings.gradle.kts exist, so " +
"settings.gradle.kts is ignored. This is likely a mistake."
)
}

return settingsGradle.exists() ? settingsGradle : settingsGradleKts
}

/** Adds the plugin project dependency to the app project. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,15 @@ class _DeferredComponentAndroidFiles {
Directory get componentDir => androidDir.childDirectory(name);

File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
File get buildGradleFile => componentDir.childFile('build.gradle');
File get buildGradleFile {
if (componentDir.childFile('build.gradle').existsSync()) {
return componentDir.childFile('build.gradle');
}
return componentDir.childFile('build.gradle.kts');
}

// True when AndroidManifest.xml and build.gradle exist for the android dynamic feature.
// True when AndroidManifest.xml and build.gradle/build.gradle.kts exist for
// the android dynamic feature.
bool verifyFilesExist() {
return androidManifestFile.existsSync() && buildGradleFile.existsSync();
}
Expand Down
22 changes: 16 additions & 6 deletions packages/flutter_tools/lib/src/android/gradle_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,26 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio
/// The Android plugin version is specified in the [build.gradle] file within
/// the project's Android directory.
String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
final File buildFile = directory.childFile('build.gradle');
const String buildFileName = 'build.gradle/build.gradle.kts';

File buildFile = directory.childFile('build.gradle');
if (!buildFile.existsSync()) {
buildFile = directory.childFile('build.gradle.kts');
}

if (!buildFile.existsSync()) {
logger.printTrace(
"$buildFile doesn't exist, assuming Gradle version: $templateDefaultGradleVersion");
"$buildFileName doesn't exist, assuming Gradle version: $templateDefaultGradleVersion");
return templateDefaultGradleVersion;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> pluginMatches = _buildAndroidGradlePluginRegExp.allMatches(buildFileContent);
if (pluginMatches.isEmpty) {
logger.printTrace("$buildFile doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion");
logger.printTrace("$buildFileName doesn't provide an AGP version, assuming Gradle version: $templateDefaultGradleVersion");
return templateDefaultGradleVersion;
}
final String? androidPluginVersion = pluginMatches.first.group(1);
logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
logger.printTrace('$buildFileName provides AGP version: $androidPluginVersion');
return getGradleVersionFor(androidPluginVersion ?? 'unknown');
}

Expand Down Expand Up @@ -325,9 +331,13 @@ OS: Mac OS X 13.2.1 aarch64
/// [settings.gradle] file within the project's
/// Android directory ([androidDirectory]).
String? getAgpVersion(Directory androidDirectory, Logger logger) {
final File buildFile = androidDirectory.childFile('build.gradle');
File buildFile = androidDirectory.childFile('build.gradle');
if (!buildFile.existsSync()) {
buildFile = androidDirectory.childFile('build.gradle.kts');
}

if (!buildFile.existsSync()) {
logger.printTrace('Can not find build.gradle in $androidDirectory');
logger.printTrace('Can not find build.gradle/build.gradle.kts in $androidDirectory');
return null;
}
final String buildFileContent = buildFile.readAsStringSync();
Expand Down
57 changes: 52 additions & 5 deletions packages/flutter_tools/lib/src/project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,10 @@ class AndroidProject extends FlutterProjectPlatform {
static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\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*\$');

/// Pattern used to find the assignment of the "group" property in Gradle.
/// Expected example: `group "dev.flutter.plugin"`
/// Regex is used in both Groovy and Kotlin Gradle files.
static final RegExp _groupPattern = RegExp('^\\s*group\\s+[\'"](.*)[\'"]\\s*\$');

/// The Gradle root directory of the Android host app. This is the directory
Expand Down Expand Up @@ -552,10 +556,54 @@ class AndroidProject extends FlutterProjectPlatform {
return imperativeMatch || declarativeMatch;
}

/// Gets top-level Gradle build file.
/// See https://developer.android.com/build#top-level.
///
/// The file must exist and it must be written in either Groovy (build.gradle)
/// or Kotlin (build.gradle.kts).
File get hostAppGradleFile {
final File buildGroovy = hostAppGradleRoot.childFile('build.gradle');
final File buildKotlin = hostAppGradleRoot.childFile('build.gradle.kts');

if (buildGroovy.existsSync() && buildKotlin.existsSync()) {
// We mimic Gradle's behavior of preferring Groovy over Kotlin when both files exist.
return buildGroovy;
}

if (buildKotlin.existsSync()) {
return buildKotlin;
}

// TODO(bartekpacia): An exception should be thrown when neither
// build.gradle nor build.gradle.kts exist, instead of falling back to the
// Groovy file. See #141180.
return buildGroovy;
}

/// Gets the module-level build.gradle file.
/// See https://developer.android.com/build#module-level.
File get appGradleFile => hostAppGradleRoot.childDirectory('app')
.childFile('build.gradle');
///
/// The file must exist and it must be written in either Groovy (build.gradle)
/// or Kotlin (build.gradle.kts).
File get appGradleFile {
final Directory appDir = hostAppGradleRoot.childDirectory('app');
final File buildGroovy = appDir.childFile('build.gradle');
final File buildKotlin = appDir.childFile('build.gradle.kts');

if (buildGroovy.existsSync() && buildKotlin.existsSync()) {
// We mimic Gradle's behavior of preferring Groovy over Kotlin when both files exist.
return buildGroovy;
}

if (buildKotlin.existsSync()) {
return buildKotlin;
}

// TODO(bartekpacia): An exception should be thrown when neither
// build.gradle nor build.gradle.kts exist, instead of falling back to the
// Groovy file. See #141180.
return buildGroovy;
}

File get appManifestFile {
if (isUsingGradle) {
Expand Down Expand Up @@ -652,7 +700,7 @@ $javaGradleCompatUrl
}

bool get isUsingGradle {
return hostAppGradleRoot.childFile('build.gradle').existsSync();
return hostAppGradleFile.existsSync();
}

String? get applicationId {
Expand All @@ -671,8 +719,7 @@ $javaGradleCompatUrl
}

String? get group {
final File gradleFile = hostAppGradleRoot.childFile('build.gradle');
return firstMatchInFile(gradleFile, _groupPattern)?.group(1);
return firstMatchInFile(hostAppGradleFile, _groupPattern)?.group(1);
}

/// The build directory where the Android artifacts are placed.
Expand Down
Loading

0 comments on commit 0a1af8a

Please sign in to comment.