The reason why we emphasize using Kotlin to write Gradle scripts is because it can reduce the user’s understanding and use cost, and there is really no need to learn a DSL (such as Groovy) to write Gradle scripts. This article will not be too systematic about the knowledge of Gradle. If you want to learn Gradle systematically, you can refer to the notes of Android Gradle learning notes.

Gradle Build life cycle

Gradle goes through three life cycles when it builds:

  • Initialization phase
  • The configuration phase
  • Execution phase

Initialization phase

The initialization phase determines how many projects need to be built, creates the entire Project hierarchy, and creates a Project object for each module. Gradle files have a project object for each build.gradle file, a project object for the build.gradle file, and a project object for the build.gradle files under many modules. Setting. gradle. The module path configured in setting.gradle determines which projects Gradle creates.

The configuration phase

The code of Project object and Task object will be executed in the configuration phase, which can be called the configuration phase. In the configuration phase, configuration parameters are read, Task objects are created, and directed acyclic graphs are constructed according to the dependencies between tasks, so as to specify the execution sequence of tasks. For Tasks, there needs to be a clear distinction between the configuration and execution phases. Gradle -> setting.gradle -> setting.gradle -> setting.gradle -> setting.gradle

Execution phase

During the execution phase, all tasks are executed in the order specified in the configuration. Closures passed in by the doFirst and doLast methods that call the Task are stored in the Actions list of the Task (both doFirst and doLast methods in the Task can be called multiple times).

The Gradle lifecycle provides a rich callback interface to Hook users easily throughout the Build process. The available functions are shown in the figure above. Also, if you are using Android Studio or IntelliJ and see the configuration phase and the execution phase in the Build window, go to Build Output and Configure Project: At the beginning, the execution phase begins with > Task:.

Differences between Kotlin DSL and Groovy DSL

The Kotlin DSL is written just like a regular Kotlin program, and unlike Groovy’s flexible syntax, Kotlin’s syntax is much more concise. The main differences, and preparations for the recommendations of the official migration guidelines:

  • Groovy strings can be quoted in single quotes' 'Kotlin can only use double quotes
  • Groovy calls to methods can omit parentheses(a)Kotlin cannot be omitted
  • Groovy assignments can be omitted=Kotlin cannot be omitted either

In addition to the syntax differences, don’t forget to add the.kts suffix to all Groovy script extensions. The experience of writing Gradle scripts using Kotlin DSL is similar to that of writing Gradle scripts in Java. The user must have some understanding of the Project, Settings, and Gradle object apis. Following the gradle.properties configuration & ext, project-Extensions, and Task, see how to implement them using Kotlin DSL:

project-extensions

Here’s an example of a common configuration in Android projects (build.gradle in App Module) :

Groovy DSL version

/ / 1
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.example.taskqueue"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        / / 2
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'}}Copy the code

Kotlin DSL version

/ / 1
configure<com.android.build.gradle.internal.dsl.BaseAppModuleExtension> {
    compileSdkVersion(30)
    buildToolsVersion("30.0.2")

    defaultConfig {
        applicationId("com.example.taskqueue")
        minSdkVersion(16)
        targetSdkVersion(30)
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        / / 2
        named("release") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"}}Copy the code

As you can see, the differences between the Same Groovy DSL version and the Kotlin DSL version are not obvious. To rule out simple syntax differences (such as double quotes and Spaces mentioned above), note the two comparisons marked in the code:

Comparison 1: The Android {} used in Groovy DSL is actually a project-extension that corresponds to the BaseAppModuleExtension Extension class in the AppModule. The reason we can assign values to variables in the Groovy DSL as in the BaseAppModuleExtension class is because Groovy provides a syntax-sugar that allows users to manipulate variables directly in this way. You can’t just write that in Kotlin’s DSL, Need to use the configure < com. Android. Build. Gradle. Internal. DSL. BaseAppModuleExtension > {} to access the contents of the BaseAppModuleExtension, The same Groovy DSL-like effect can be achieved here, using Kotlin’s own capabilities.

/**
 * Executes the given configuration block against the [plugin convention]
 * [Convention.getPlugin] or extension of the specified type.
 *
 * @param T the plugin convention type.
 * @param configuration the configuration block.
 * @see [Convention.getPlugin]
 */
inline fun <reified T : Any> Project.configure(noinline configuration: T. () - >Unit): Unit= typeOf<T>().let { type -> convention.findByType(type)? .let(configuration) ? : convention.findPlugin<T>()? .let(configuration) ? : convention.configure(type, configuration) }Copy the code

Configure is an extension of the Project object. It calls the findPlugin(Class Type) method of ExtensionContainer.

extensions.findByType<com.android.build.gradle.internal.dsl.BaseAppModuleExtension>().run { 
    // TODO
}
Copy the code

For details about the project-Extension class, please refer to Android Gradle learning (5) : Extension

Compare 2: Groovy configuration in the DSL buildTypes can directly use the release {} configuration, this is because the actual configuration buildTypes method is a NamedDomainObjectContainer < BuildType > object, NamedDomainObjectContainer support flexible configuration (. Um participant we can define a default name is release or debug object, can also customize a full object, they are all BuildType the corresponding objects). This flexible configuration is also supported by the syntax sugar provided by the Groovy DSL. Kotlin DSL need to call the NamedDomainObjectContainer class method, named method is one of the methods of NamedDomainObjectContainer, Want to learn more about NamedDomainObjectContainer class, please refer to the Android Gradle learning (6) : NamedDomainObjectContainer explanation.

Gradle.properties configuration & ext

When writing Gradle scripts in an Android project using the Groovy DSL, variables are usually defined in both gradle.properties and Ext. Here’s an example in the App Module:

Gradle.properties in app Module

versionCode = 1
Copy the code

Build. gradle in the Project Module

Define the kotlin_version variable

allprojects {
    ext {
        kotlinVersion = "1.4.10"}}Copy the code

Build. Gradle in app Moudle

Read variables defined in gradle.properties and build.gradle in the Project Module

println versionCode
println kotlinVersion
Copy the code

Gradle. properties and variables defined in build.gradle in the Project Module can be accessed directly from groovy files. By default, the project creates only the gradle.properties file at the project level.

The Kotlin DSL defines and accesses the gradle.properties configuration & ext

val versionCode: String by project / / 1
val kotlinVersion: String by rootProject.extra  / / 2
val newKotlinVersion: String by extra("1.3.61")  / / 3
Copy the code

The above Kotlin DSL code implements this, respectively:

  1. Read the versionCode variable defined in the project gradle.properties file
  2. Read the kotlinVersion variable defined in the build.gradle file corresponding to project
  3. Define a new newKotlinVersion variable

You can see that accessing gradle.properties in the Kotlin DSL delegates to the Project object; Defining ext variables equivalent to reading a Groovy DSL also delegates to the Extra method of the ExtensionAware interface

Task

The Groovy way of defining a task with a task name doesn’t work in Kotlin’s DSL either. In Kotlin’s DSL, tasks are defined as follows:

Defining a single Task

val MyCopy by tasks.create(Copy::class) {
        group = "Copy"
        description = "This is MyCopy Task"
        dependsOn("Copy")
        doFirst {
            println("MyCopy Do First")
        }
        doLast {
            println("MyCopy Do Last")}}Copy the code

Getting a single Task

val MyCopy by tasks.existing(Copy::class) {
        group = "Copy"
        description = "This is MyCopy Task"
        dependsOn("Copy")
        doFirst {
            println("MyCopy Do First")
        }
        doLast {
            println("MyCopy Do Last")}}Copy the code

Configuring Multiple Tasks

tasks {
    val myCopy1 by register(Copy::class) {
        // TODO
    }
    val myCopy2 by existing(Copy::class) {
        // TODO}}Copy the code

If you need to configure multiple tasks, use the Tasks {} method to create tasks in batches.() -> Unit. The user can call TaskContainer’s methods directly from the Tasks {} method without reference.

Pros and cons of Kotlin DSL

advantages

  • Integrality: The Kotlin DSL has the advantage of ensuring that Build scripts and business code are written in the same language when the whole project migrates to Kotlin in the future, reducing the difficulty of understanding Build scripts.
  • Groovy compatibility: The compatibility between Kotlin and Groovy is guaranteed by the excellent compatibility between Kotlin and Java.

disadvantages

  • Lack of errors: It’s not ideal in terms of the experience, and there’s a lot of room for improvement, such as the lack of errors during the migration from Groovy DSL to Kotlin DSL.
  • Some of them are more complex: Some of them look more complex than Groovy DSLS, somewhat similar to the experience of writing Gradle plugins.
  • Some features are not fully supported, such as those mentioned in the documentationType-safe model accessorsThere are limits to the way in which plugins are referenced, and elements introduced in these ways cannot be usedType-safe model accessors:
    • The plug-in applied by the apply(plugin = “id”) method.
    • Project build scripts.
    • Script plugin by apply(from = “script-plugin.gradle.kts”).
    • Configure plug-ins for applications across projects.
    • If the above conditions are met, it looks like a successful build is required before it can be called properly (this is my real experience).

reference

  • Official Kotlin DSL migration tutorial
  • Official Kotlin DSL primer
  • Android Gradle learning notes