如何将 Gradle 构建文件从 Groovy DSL 迁移到 Kotlin DSL

将 Android Gradle 文件 从 Groovy DSL 迁移到 Kotlin DSL 的百科全书

自从 Android Studio Giraffe, Kotlin DSL 便成为了 Android 开发中 Gradle 脚本的新标准. 当你使用集成开发环境的内置模板创建新项目时, 你将被提供 Kotlin DSL 文件, 而不是基于 Groovy 的 Gradle 文件.

这一即将到来的变化给了我信心, 让我最终迈出了将基于 Groovy DSL 的 Gradle 配置迁移到 Kotlin DSL 的一步. 在本文中, 我将带领大家了解我必须采取的步骤.

本文旨在成为一本百科全书和指南, 在你迁移到 Kotlin DSL 的过程中为你提供帮助. 虽然本文主要针对 Android 项目, 但讨论的要点同样适用于其他基于 Gradle 的项目, 例如 Spring Boot 应用.

在第一部分, 我将简要介绍从 Groovy 迁移到 Kotlin DSL 的一般过程. 在接下来的章节中, 我将列出迁移过程中必须经历的所有环节.

迁移过程

单模块 Android 项目的常规结构在大多数情况下都非常相似. 当然, 项目可能会使用一些额外的 Gradle 配置文件, 但总的来说, 迁移过程基本相同.

多模块项目也是如此, 每个模块都有单独的build.gradle.

常规单模块 Android 项目 Gradle 文件结构.

从这个结构中, 我们可以找出三个要转换为 Kotlin DSL 的基于 groovy 的 Gradle 文件.

  • settings.gradle: settings.gradle文件负责在Android项目中配置和定义项目的模块结构. 它位于项目的根目录中, 在组织和管理构建过程中起着至关重要的作用.
  • 项目级 build.gradle: 项目级build.gradle文件也位于根目录中, 负责为整个项目的构建过程设置配置. 它定义了适用于项目中所有模块的全局设置, 资源库和依赖关系.
  • 模块(应用)级 build.gradle: Android 项目中的每个模块(如应用模块)中都有一个"模块级"build.gradle文件. 该文件负责配置特定模块的特定构建设置, 依赖关系和行为.

从这个 Gradle 文件结构来看, 对我来说最合理的迁移结构如下:

  1. settings.gradle文件迁移到settings.gradle.kts文件中
  2. 迁移所有外部 gradle 文件, 例如将dependencies.gradle文件迁移到包含依赖版本名称的dependencies.gradle.kts文件.
  3. 将项目级的build.gradle文件迁移到build.gradle.kts文件
  4. 将模块级的build.gradle文件迁移到build.gradle.kts文件.

用这种方式你将会遇到最少的冲突. 此外, 在每个迁移步骤中, 如果 Kotlin DSL 因为尚未成功构建而尚未应用, 注释掉这些部分通常会有所帮助. 一旦用转换后的文件构建了项目, 你就会得到集成开发环境的支持, 调试问题也会比猜测 Groovy 属性在 Kotlin DSL 中的名称容易得多.

迁移百科

正如最开始的时候解释的那样, 大多数项目结构都是相同的. 即使是在复杂的 Gradle 设置中, 在迁移过程中也会面临同样的挑战.

在下文中, 你可以找到我偶然发现的问题以及解决方法. 在每个小节中, 我都会解释 Gradle Groovy 代码段的用途, 以及迁移后的 Kotlin DSL 代码是什么样的.

如果你发现有任何遗漏的部分可能与更多读者相关, 请随时评论, 我会把它们添加进来中.

用双引号替换单引号

在使用 Kotlin DSL 时, 我们需要确保在字符串中使用双引号(")而不是单引号('). 因此, 请查看你的文件并查找单引号.

你可以使用 IntelliJ 查找函数 (Mac ⌘ + F ; Windows Strg + F ) 或直接使用替换函数 (Mac ⌘ + R ; Windows Strg + R) 加快这一过程.

Groovy:

arduino 复制代码
implementation 'androidx.activity:activity-ktx:1.7.2'

Kotlin DSL 中变成:

arduino 复制代码
implementation "androidx.activity:activity-ktx:1.7.2"

访问另一个 Gradle 文件中的值

要访问另一个 Gradle 文件中的值, 在 Groovy 中可以使用下面的示例:

Groovy:

css 复制代码
apply from: rootProject.file('dependencies.gradle')

在 Kotlin DSL 中, 它现在变成:

ini 复制代码
apply(from = rootProject.file("dependencies.gradle"))

apply plugin

如果你在使用apply plugin语法, 下面是该语法的迁移方法:

Groovy:

arduino 复制代码
apply plugin: "org.jlleitschuh.gradle.ktlint"

Kotlin DSL 中变成:

ini 复制代码
apply(plugin= "com.android.application")

plugins块

使用插件的新语法是plugins { .. }块. 在下面的示例中, 你可以看到有和没有设置插件显式版本的声明示例.

Groovy:

bash 复制代码
plugins {
    id 'com.android.application'
    id 'com.google.devtools.ksp' version Ƈ.8.21-1.0.11'
    ..
}

Kotlin DSL 中变成:

bash 复制代码
plugins {
    id("com.android.application")
    id("com.google.devtools.ksp") version "1.8.20-1.0.10"
    ..
}

迁移额外变量

如果你的项目使用ext { .. }块来定义依赖项的版本名称或其他参数, 下面就告诉你如何迁移.

假设你的ext { .. }块如下所示:

Groovy

ini 复制代码
ext {
    accompanist_version = "0.30.0"
    compose_compiler_version = "1.4.7"
    ..
}

通过 Kotlin DSL 定义版本名称有两种方法.

首先, 通过访问额外的映射

Kotlin DSL 中变成:

arduino 复制代码
extra["accompanist_version"] = "0.30.0"
extra["compose_compiler_version"] = "1.4.7"

或者, 也可以使用by extra委托.

Kotlin DSL:

kotlin 复制代码
val accompanist_version by extra("0.30.0")
val compose_compiler_version by extra("1.4.7")

访问额外参数

在 Groovy 中, 你可以通过各自的变量名直接访问 Strings 中的项目额外参数, 而在 Kotlin DSL 中, 我们必须使用委托.

在应用模块build.gradle.kts中, 你可以以下列方式使用by project委托访问额外变量:

Kotlin DSL:

kotlin 复制代码
val accompanist_version: String by project
..
dependencies {
  implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
}

引入 Gradle 模块

settings.gradle中包含 Gradle 模块. 对于单模块项目, 这只是应用模块. 如果迁移的是多模块项目, 则应迁移所有包含的模块.

对于settings.gradle, 你可以按以下方式转换include:

Groovy:

php 复制代码
include ':app'

Kotlin DSL 中变成:

php 复制代码
include(":app")

声明仓库

repositories块用于定义可以解析依赖关系的仓库. 对于每个仓库, 我们都使用函数调用.

然而, 在 Kotlin DSL 中声明资源库与 Groovy 非常相似. 请看下面的内容:

Groovy:

scss 复制代码
repositories {
    google()
    mavenCentral()
    gradlePluginPortal()
    maven { url "https://plugins.gradle.org/m2/" }
}

Kotlin DSL 中变成:

scss 复制代码
repositories {
    google()
    mavenCentral()
    gradlePluginPortal()
    maven(url = "https://plugins.gradle.org/m2/")
}

依赖关系配置

在我们的项目中有多种方法来声明依赖关系. 例如, classpath用于为我们的构建过程本身声明依赖关系. 它用于项目级build.gradle文件中的buildscript块.

另一方面, implementation配置负责声明在代码中和运行时实际使用的依赖项.

另一个常见的关键字是platform关键字, 例如, 在通过物料清单(BOM)声明依赖关系时使用, 就像我们从 FirebaseJetpack Compose 中了解到的那样.

在使用 KAPT 或 KSP 等注释处理器时, 我们也会在代码中使用相应的关键字kaptksp声明相应的依赖关系.

此外, 当包含来自libs等目录的文件时, 我们可以使用fileTree关键字.

下面是所有上述关键字的代码片段:

Groovy:

java 复制代码
// Buildscript dependency
classpath 'com.android.tools.build:gradle:8.0.2'

// Regular dependency 
implementation "androidx.room:room-ktx:2.5.1"

// Include .jar files from libs folder
implementation fileTree (dir: "libs", include: ["*.jar"])

// Platform
implementation platform('androidx.compose:compose-bom:2023.05.01')

// Annotation processor
kapt "androidx.room:room-compiler:2.5.1"
// Or
ksp "androidx.room:room-compiler:2.5.1"

Kotlin DSL 中变成:

less 复制代码
// Buildscript dependency
classpath("com.android.tools.build:gradle:8.0.2")

// Regular dependency
implementation("androidx.room:room-ktx:2.5.1")

// Include .jar files from libs folder
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))

// Platform
implementation(platform("androidx.compose:compose-bom:2023.05.01"))

// Annotation processor
kapt("androidx.room:room-compiler:2.5.1")
// Or
ksp("androidx.room:room-compiler:2.5.1")

BuildType 和 Flavor 依赖配置

当你只想将依赖关系分配给特定的构建类型时, 以前使用 Groovy DSL 时, 你可以直接使用构建类型名称并添加implementation, 就像我们在上一节为常规依赖关系所做的那样.

但在 Kotlin DSL 中, 这不再可能. 取而代之的是, 我们使用带有构建类型或构建类型名称的配置委托, 以及与特定依赖关系的风味组合.

看看下面的例子就知道了:

Groovy:

arduino 复制代码
dependencies {
    ..
    myCustomBuildTypeImplementation 'com.mydependency:1.0.0'
}

Kotlin DSL 中变成:

kotlin 复制代码
val myCustomBuildType by configurations.creating

dependencies {
    ..
    myCustomBuildType("com.mydependency:1.0.0")
}

任务注册

通过注册任务, 我们可以自定义并控制构建流程. 我们可以实现符合各自要求的自定义代码.

在下面的示例任务中, 我们看到创建了一个任务"clean", 该任务在执行时使用Delete任务删除各自根项目的构建目录.

与 Groovy 不同, 在 Kotlin DSL 中, 我们使用函数register来创建新任务.

Groovy:

arduino 复制代码
task clean (type: Delete) {
    delete rootProject.buildDir
}

Kotlin DSL 中变成:

javascript 复制代码
tasks.register("clean", Delete::class) {
    delete(rootProject.buildDir)
}

赋值和 Android 默认配置

配置参数时, 在 Groovy 中使用的语法是parameterName value. 在 Kotlin DSL 中, 如果底层代码提供了可变变量, 我们只需在这两个值之间使用=操作符即可为参数赋值.

对于 Android 配置和defaultConfig而言, 生成的脚本可以在下面的示例迁移片段中找到.

Groovy:

arduino 复制代码
android {
    namespace 'com.my.project'
    compileSdkVersion 33
    defaultConfig {
        applicationId "com.my.project"
        minSdkVersion 24
        targetSdkVersion 33
        versionCode 1
        versionName "1.0.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
  ..
}

Kotlin DSL 中变成:

ini 复制代码
android {
    namespace = "com.my.project"
    compileSdk = 33

    defaultConfig {
        applicationId = "com.my.project"
        minSdk = 28
        targetSdk = 33
        versionCode = 1
        versionName = "1.0.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
  ..
}

kotlinOptions块

Kotlin 选项块的迁移相对简单. 如果你使用freeCompilerArgs, 你只需稍微调整一下语法.

+=替换为:

Groovy:

ini 复制代码
    kotlinOptions {
        jvmTarget = "17"
        freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
    }

Kotlin DSL 中变成:

ini 复制代码
kotlinOptions {
    jvmTarget = "17"
    freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
}

打包选项排除

在某些情况下, 你希望从最终构建的应用bundle中排除某些资源. 为此, 你可以使用packagingOptionsresources添加排除规则.

Groovy:

css 复制代码
    packagingOptions {
        resources {
            excludes += ["META-INF/LICENSE.txt", "META-INF/NOTICE.txt"]
        }
    }

Kotlin DSL 中变成:

arduino 复制代码
    packagingOptions {
        resources {
            excludes += setOf("META-INF/LICENSE.txt", "META-INF/NOTICE.txt")
        }
    }

添加productFlavors

添加productFlavors是一项方便的功能, 可用于定义应用的特定类型. 常见的productFlavors味是"dev", 你可以在其中为后端服务器设置不同的路由, 或设置与生产构建不同的其他配置.

让我们来看一个定义dev类型的迁移示例. 注意我们是如何使用.add(..)函数而不是直接调用flavorDimensions()函数的, 因为底层值是一个MutableList<String>.

在创建风味时, 我们现在必须使用create("ourFlavorName")语法, 而不是在配置块前直接声明名称.

此外, 在添加基础 URL 时, 由于现在必须使用双引号, 我们需要通过""转义 URL 字符串内部的双引号.

Groovy:

arduino 复制代码
    flavorDimensions("app")

    productFlavors {
        dev {
            dimension = 'app'
            applicationId 'dev.com.my.project'
            buildConfigField 'String', 'BASE_URL', '"https://api.yourdevserver.com"'
            buildConfigField 'Boolean', 'ANALYTICS_ENABLED', 'true'
        }
    }

Kotlin DSL 中变成:

javascript 复制代码
    flavorDimensions.add("app")

    productFlavors {
        create("dev") {
            dimension = "app"
            applicationId = "dev.com.my.project"
            buildConfigField("String", "BASE_URL", ""https://api.yourdevserver.com"")
            buildConfigField("Boolean", "ANALYTICS_ENABLED", "true")
        }
    }

签名配置

要动态创建签名配置, 我们可以使用signingConfigs块.

迁移与添加新productFlavors的迁移类似. 我们现在使用create(..)任务, 而不是直接在配置块前面命名相应的配置名称:

Groovy:

lua 复制代码
signingConfigs {
    release {
        storeFile file("pathToOurKeystore")
        storePassword System.getenv ("STORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }
}

Kotlin DSL 中变成:

ini 复制代码
signingConfigs {
    create("release") {
        storeFile = file("pathToOurKeystore")
        storePassword = System.getenv("STORE_PASSWORD")
        keyAlias = System.getenv("KEY_ALIAS")
        keyPassword = System.getenv("KEY_PASSWORD")
    }
}

定义构建类型

在 Gradle 中, buildTypes块可用于为 Android 项目定义不同的构建配置. 每种构建类型都代表应用的一个特定变体, 如debugrelease, 都有自己的一组配置选项.

通过buildTypes块, 你可以自定义构建的各个方面, 如启用或禁用调试, 启用代码和资源缩减, 指定 ProGuard 规则, 自定义输出文件名以及为签名配置赋值.

要使用 Kotlin DSL 创建新的buildType, 就像创建 flavorssigningConfigs 一样, 我们可以使用create(...). 不过, 在配置默认构建类型releasedebug时, 我们需要使用getByName(..)代替.

boolean类型的配置参数也会稍有变化, 例如从debuggable变为isDebuggable.

此外, 在这种情况下, 有时你想通过遍历 applicationVariants 和更新outputFileName变量来调整输出文件的文件名.

Kotlin DSL 的情况则有些不同. 请看下面的代码段, 了解迁移示例:

Groovy:

arduino 复制代码
buildTypes {
    release {
        debuggable false
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"

        android.applicationVariants.all { variant ->
            variant.outputs.all { output ->
                outputFileName = "${variant.name}-${variant.versionName}-${versionCode}.aab"
            }
        }

        signingConfig signingConfigs.release
    }
    
    myCustomBuildType {
    ..
    }
}

Kotlin DSL 中变成:

kotlin 复制代码
// This line goes to the top of your build.gradle.kts file
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
..

buildTypes {
    getByName("release") {
        isDebuggable = false
        isMinifyEnabled = true
        isShrinkResources = true
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")

        android.applicationVariants.all {
            outputs.all {
                val output: BaseVariantOutputImpl = (this as BaseVariantOutputImpl)
                output.outputFileName = "${name}-${versionName}-${versionCode}.aab"
            }
        }

        signingConfig = signingConfigs.getByName("release")
    }
    
    create("myCustomBuildType) {
    ..
    }
}

配置testOptions

为了调整单元测试的配置, 我们可以使用包含在testOptions块中的unitTests块.

在这里, 我们只需将includeAndroidResources更改为isIncludeAndroidResources即可, 如下代码片段所示:

Groovy

ini 复制代码
testOptions {
    unitTests {
        includeAndroidResources = true
    }
}

Kotlin DSL 中变成:

ini 复制代码
testOptions {
    unitTests {
        isIncludeAndroidResources = true
    }
}

总结一下

将 Gradle 文件从 Groovy 转换为 Kotlin DSL 可以显著改善 Android 开发工作流程. 如果你已经熟悉 Kotlin, 效果会更好. 转用熟悉的单一语言不仅能提高你的工作效率, 还能避免在两种语言之间跳来跳去地完成开发和配置任务. Kotlin DSL 的健壮性和直观性让你有信心创建自定义的 Gradle 任务, 而不必使用 Groovy 经常出现的模糊语法.

尽管 Groovy 在 Gradle 脚本配置中占据主导地位, 而且它与 Java 相似, 许多 Android 或 JVM 开发人员一般都应该熟悉 Java, 但很少有开发人员完全学会 Groovy. 很多时候, Gradle 脚本依赖于从相关框架文档, StackOverflow 答案或其他地方复制粘贴的片段, 而这些片段可能并不总是最佳解决方案. 因此, 切换到 Kotlin DSL 不仅是一个可操作的改变, 也是提高代码可维护性和可读性的一个步骤.

随着 Android 开发生态系统的发展, 我们的方法也应随之发展. 本指南基于我的迁移过程, 旨在涵盖你在迁移过程中可能遇到的各种情况. 如果你遇到此处未涵盖的情况, 请随时评论, 以便我对指南进行相应更新.

编码快乐! Stay GOLD!

相关推荐
数据猎手小k2 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小103 小时前
JavaWeb项目-----博客系统
android
风和先行4 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.4 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰5 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶5 小时前
Android——网络请求
android
干一行,爱一行5 小时前
android camera data -> surface 显示
android
断墨先生6 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员7 小时前
PHP常量
android·ide·android studio
萌面小侠Plus8 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机