Android Gradle —— 从 Groovy 快速迁移到 Kotlin DSL

Kotlin 脚本 (KTS) 比 Groovy 更适合用于编写 Gradle 脚本,因为采用 Kotlin 编写的代码可读性更高,并且 Kotlin 提供了更好的编译时检查和 IDE 支持。Android Gradle 插件 4.0 支持在 Gradle build 配置中使用 KTS。

本页介绍了有关将 Android 应用的 Gradle build 文件从 Groovy 转换为 KTS 的基本信息。我们建议您按照所列顺序执行这些步骤。另请参阅 Gradle 的迁移指南Kotlin DSL Primer。 如果要迁移多个项目,请从最小的项目开始以便积累经验,然后再继续迁移。

常用术语

KTS :是指 Kotlin 脚本,这是 Gradle 在 build 配置文件中使用的一种 Kotlin 语言形式。Kotlin 脚本是可从命令行运行的 Kotlin 代码。

Kotlin DSL :主要是指 Android Gradle 插件 Kotlin DSL,有时也指底层 Gradle Kotlin DSL

在讨论从 Groovy 迁移时,术语"KTS"和"Kotlin DSL"可以互换使用。换句话说,"将 Android 项目从 Groovy 转换为 KTS"与"将 Android 项目从 Groovy 转换为 Kotlin DSL"实际上是一个意思。

脚本文件命名

脚本文件扩展名取决于编写 build 文件所用的语言:

  • 用 Groovy 编写的 Gradle build 文件使用 .gradle 文件扩展名。
  • 用 Kotlin 编写的 Gradle build 文件使用 .gradle.kts 文件扩展名。

准备工作

一些简单的 Kotlin 和 Groovy 语言差异可能会使转换脚本变得繁琐:

  • Groovy 的字符串可以用单引号 'string' 或双引号 "string" 引用,而 Kotlin 要求使用双引号 "string"。
  • Groovy 允许省略调用函数时的括号,而 Kotlin 总是要求括号。
  • Gradle Groovy DSL 允许在分配属性时省略 = 赋值运算符,而 Kotlin 总是需要赋值运算符。

首先,建议采取以下措施来准备您的 Groovy 构建脚本:

  • 使用双引号统一引用样式
  • 通过分别使用括号和赋值运算符来消除函数调用和属性分配的歧义。

您可以通过查找 ' 并将其替换为 " 轻松完成引号的一致性修改。例如,

// build.gradle

group 'com.acme'
dependencies {
    implementation 'com.acme:example:1.0'
}

修改成:

// build.gradle

group "com.acme"
dependencies {
    implementation "com.acme:example:1.0"
}

后者稍微麻烦,因为在 Groovy 脚本中区分函数调用和属性分配可能并不容易。一个好的策略是首先将所有不确定的语句视作属性分配,然后通过将失败的语句更改为函数调用来修复构建。例如,

// build.gradle

group "com.acme"
dependencies {
    implementation "com.acme:example:1.0"
}

修改成:

// build.gradle

group = "com.acme"                            // 赋值               
dependencies {
    implementation("com.acme:example:1.0")    // 函数调用
}

虽然这种方式仍然符合有效的 Groovy 语法,但现在已经消除了歧义,并且非常接近 Kotlin 的语法,这使得更名脚本以将其转换为 Gradle Kotlin DSL 脚本变得更加容易。

插件相关迁移

使用插件

与 Groovy DSL 类似,使用 Gradle 插件有两种方式:

  • 通过 plugins {} 块进行声明式应用
  • 通过旧版的 apply(..) 函数进行命令式应用

以下是使用声明式 plugins {} 块的示例:

// build.gradle.kts

plugins {
    java
    jacoco
    `maven-publish`
    id("org.springframework.boot") version "2.7.5"
}

Kotlin DSL 为所有Gradle核心插件 提供了属性扩展,如上面的 javajacocomaven-publish 声明所示。

第三方插件可以以与 Groovy DSL 相同的方式应用,除了双引号和括号的用法不同。您也可以使用该样式来应用核心插件。但是,推荐使用静态类型访问器,因为它们是类型安全的,并且将通过您的 IDE 自动完成。

您也可以使用命令式的 apply 语法,但此时非核心插件必须包含在构建脚本的类路径中:

// build.gradle.kts

buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.7.5")
    }
}

apply(plugin = "java")
apply(plugin = "jacoco")
apply(plugin = "org.springframework.boot")

我们强烈建议您优先使用 plugins {} 块,而非 apply() 函数。

plugins {} 块的声明性质使 Kotlin DSL 能够提供类型安全的访问器,以访问应用插件所提供的扩展、配置和其他特性,使 IDE 能够轻松发现插件模型的详细信息并轻松配置它们。

请参阅 Gradle 用户手册中的 plugins {}文档,以获取更多信息。

配置插件

许多插件都具有用于配置它们的扩展。如果使用声明式 plugins {} 块应用这些插件,则会提供 Kotlin 扩展函数来配置它们的扩展,就像在 Groovy 中一样。以下示例展示了如何对 Jacoco 插件进行配置:

// build.gradle.kts

plugins {
    jacoco
}

jacoco {
    toolVersion = "0.8.1"
}

相比之下,如果您使用命令式的 apply() 函数来应用插件,则必须使用 configure<T>() 函数来配置该插件。以下示例演示了如何明确声明插件的扩展类(CheckstyleExtension),并将其传递给 configure<T>() 函数,以配置 Checkstyle 插件:

// build.gradle.kts

apply(plugin = "checkstyle")

configure<CheckstyleExtension> {
    maxErrors = 10
}

再次强烈建议您通过 plugins {} 块以声明性方式应用插件。

了解可用的 插件 扩展

由于 IDE 知道插件提供的配置元素,因此在您要求 IDE 提供建议时,它将包括那些元素。这将在构建脚本顶层(大多数插件扩展都添加到 Project 对象中)以及扩展配置块内部发生。

您还可以运行 :kotlinDslAccessorsReport 任务,以了解所有应用插件提供的扩展。它会打印出您可以使用的 Kotlin 代码来访问这些扩展,并提供访问器方法的名称和类型。

如果您想要配置的插件在其方法签名中依赖于 groovy.lang.Closure 或使用其他动态 Groovy 语义,那么就需要在 Kotlin DSL 构建脚本中进行更多的工作来配置该插件。有关如何从 Kotlin 代码中调用 Groovy 代码或在 Groovy 脚本中保留该插件配置的更多信息,请参见本文档中的 互操作性 部分。

插件还会提供您可能希望直接配置的任务。此部分会在后面的 配置任务 中介绍。

保持构建脚本的声明性

为了获得 Gradle Kotlin DSL 的最大好处,您应该努力保持构建脚本的声明性。这里需要记住的主要事情是,为了获得类型安全的访问器,插件必须在构建脚本的正文之前应用。

强烈建议阅读 Gradle 用户手册中有关 使用 Gradle Kotlin DSL 配置插件的内容

如果您的构建是一个多项目构建(例如大多数 Android 构建),请阅读有关多项目构建的后续部分

最后,有一些策略可以使用 plugins {}来处理未发布正确元数据的插件,例如 Android Gradle 插件。

任务相关迁移

配置避免

配置避免是 Gradle 中的一项优化技术,用于节省构建时间。它根据任务之间的依赖关系,仅在需要重新配置时才为任务执行配置操作,从而避免了不必要的配置。这些配置操作包括检索和解析依赖项、计算属性和执行插件中的代码等。

在 Gradle 5.0 及更高版本中,默认启用了配置避免。配置避免通过懒惰执行的方式工作,只有当需要重新执行任务时才会计算配置。这意味着任何与配置操作相关的代码都只在需要时才会执行。

配置避免特别适用于大型项目,其中存在大量任务、依赖项和配置代码。通过减少不必要的计算,可以显著提高构建时间。可以使用 Gradle 的性能分析工具来评估配置避免的性能以及配置操作的开销。

Gradle Kotlin DSL 采用配置避免优化技术,通过利用新 API 并提供 DSL 构造,使类型安全的模型访问器更易于使用。请放心,整个 Gradle API 仍然可用。

配置任务

在配置任务的语法上,Groovy DSL 和 Kotlin DSL 开始有显著的不同。在 Kotlin 中,任务被放置在 tasks 容器中进行命名空间管理。

// build.gradle.kts

tasks.jar {
    archiveFileName.set("foo.jar")
}

请注意,在 Kotlin 中,tasks.jar {} 的表示法利用了配置避免 API 并推迟了 Jar 任务的配置。

如果类型安全的任务访问器 tasks.jar 不可用,请参见上面的 配置插件 部分,您可以回退到使用 tasks 容器 API。以下示例的 Kotlin 版本与上面使用类型安全访问器的代码完全等效:

// build.gradle.kts

tasks.named<Jar>("jar") {
    archiveFileName.set("foo.jar")
}

请注意,由于 Kotlin 是一种静态类型语言,因此需要显式指定任务的类型。否则,脚本将无法编译,因为推断出的类型将是 Task,而不是 Jar,而 archiveName 属性是特定于 Jar 任务类型的。

如果配置避免导致迁移过程中遇到困难,您可以像在 Groovy 中一样,使用 tasks 容器API 进行非配置避免(eager) 的任务的配置:

// build.gradle.kts

tasks.getByName<Jar>("jar") {
    archiveFileName.set("foo.jar")
}

在 Gradle Kotlin DSL 中使用容器的详细文档可以在此处找到

如果您不知道任务的类型,那么您可以通过内置的 help 任务来找到该信息。只需使用 --task 选项并传递感兴趣的任务名称,就像下面这样:

❯ ./gradlew help --task jar
...
Type
     Jar (org.gradle.api.tasks.bundling.Jar)

我们来通过一个快速的示例来综合展示这些内容,这个示例是针对 Spring Boot 项目来配置 bootJarbootRun 任务的。

使用类型安全访问器来配置 Spring Boot:

// build.gradle.kts

plugins {
    java
    id("org.springframework.boot") version "2.4.5"
}

tasks.bootJar {
    archiveFileName.set("app.jar")
    mainClassName = "com.example.demo.Demo"
}

tasks.bootRun {
    mainClass.set("com.example.demo.Demo")
    args("--spring.profiles.active=demo")
}

这很容易理解。当使用 Kotlin DSL 访问器时,任务配置自动变为懒加载。

现在,为了举例说明,让我们看一下使用 API 而不是类型安全访问器来进行相同配置的示例。这取决于构建逻辑结构,有关更多信息,请参见 Gradle 用户手册中的相应文档。

首先,我们使用 help 任务确定 bootJarbootRun 任务的类型:

❯ ./gradlew help --task bootJar
...
Type
     BootJar (org.springframework.boot.gradle.tasks.bundling.BootJar)
     
❯ ./gradlew help --task bootRun
...
Type
     BootRun (org.springframework.boot.gradle.tasks.run.BootRun)

既然我们知道了这两个任务的类型,我们可以导入相关类型(BootJarBootRun),并按需要配置任务。请注意,IDE 可以帮助我们获取所需的导入包,因此我们只需要简单的名称,即不带完整包名。这里是包含导入的最终构建脚本:

// build.gradle.kts

import org.springframework.boot.gradle.tasks.bundling.BootJar
import org.springframework.boot.gradle.tasks.run.BootRun

plugins {
    java
    id("org.springframework.boot") version "2.4.5"
}

tasks.named<BootJar>("bootJar") {
    archiveFileName.set("app.jar")
    mainClassName = "com.example.demo.Demo"
}

tasks.named<BootRun>("bootRun") {
    mainClass.set("com.example.demo.Demo")
    args("--spring.profiles.active=demo")
}

创建任务

可以使用名为 task(...) 的脚本顶级函数来创建任务:

// build.gradle.kts

task("greeting") {
    doLast { println("Hello, World!") }
}

请注意,上述临时方法在 Groovy 和 Kotlin DSL 中同时配置创建的任务。

也可以在任务容器上注册或创建任务,分别使用 register(...)create(...) 函数,如下所示:

使用配置避免 API 和 DSL

// build.gradle.kts

tasks.register("greeting") {
    doLast { println("Hello, World!") }
}

使用非配置避免 (eager) API 和 DSL

// build.gradle.kts

tasks.create("greeting") {
    doLast { println("Hello, World!") }
}

上面的示例创建了未命名的临时任务(untyped, ad-hoc tasks),但您通常会更希望创建具有特定类型的任务。这也可以使用相同的 register()create() 方法来完成。以下是创建一个 Zip 类型任务的示例:

使用配置避免 API 和 DSL

// build.gradle.kts
tasks.register<Zip>("docZip") { zip:Zip->
    archiveFileName.set("doc.zip")
    from("doc")
}

使用非配置避免 (eager) API 和 DSL

// build.gradle.kts

tasks.create<Zip>("docZip") {
    archiveFileName.set("doc.zip")
    from("doc")
}

相互操作

在构建逻辑中混合语言时,您可能需要跨语言边界交互。一个极端的例子是使用 Java、Groovy 和 Kotlin 实现的任务和插件,同时使用 Kotlin DSL 和 Groovy DSL 构建脚本。

引用 Kotlin 参考文档的内容:

Kotlin 是考虑到与 Java 的互操作性而设计的。现有的 Java 代码可以以自然的方式从 Kotlin 中调用,而 Kotlin 代码也可以在 Java 中很顺畅地使用。

在 Kotlin 参考文档中,对从 Kotlin 调用 Java 和从 Java 调用 Kotlin 进行了广泛的介绍,同样适用于与 Groovy 代码的交互操作。此外,Kotlin DSL 提供了多种方法来选择使用 Groovy 语义。

关于 Gradle Kotlin DSL 和互操作性的详细文档,请参阅 Gradle Kotlin DSL Primer 中的互操作性部分

kts迁移实操

准备工作

字符串转换

groovy 的基础直接利用正则替换所有单引号''定义的字符串为"", 操作:

  • Ctrl + Shift + R
  • 匹配框输入正则表达式'(.*?[^\])', 替换框中输入"$1"
  • 过滤文件后缀输入*.gradle
  • 漏斗选择中 Excepts String literals and Comments, 表示仅匹配代码部分
  • 确保输入框后的.*正则匹配处于高亮状态
  • 自信的可以直接Replace All, 否则逐个核对再Replace
  • 完成后, Sync 项目保证无报错

函数调用转换

groovy的函数调用添加().

maven
maven {
    allowInsecureProtocol = true
    url "http://ip:port/repository/maven-releases/"
    credentials {
        username = "xxx"
        password = "xxx"
    }
}

修改为:

maven {
    allowInsecureProtocol = true
    url("http://ip:port/repository/maven-releases/")
    credentials {
        username = "xxx"
        password = "xxx"
    }
}
dependencies
dependencies {
    
    classpath "com.android.tools.build:gradle:7.3.1"
    
    implementation fileTree(include: ["*.jar", "*.aar"], dir: "libs")
    
    implementation "com.google.android.material:material:$material_version"
    
    testImplementation "junit:junit:$junit_version"
    androidTestImplementation "androidx.test:core:1.4.0"
}

修改为:

dependencies {

    classpath("com.android.tools.build:gradle:7.3.1")
    
    implementation(fileTree(include: ["*.jar", "*.aar"], dir: "libs"))
    
    implementation("com.google.android.material:material:$material_version")
    
    testImplementation("junit:junit:$junit_version")
    androidTestImplementation("androidx.test:core:1.4.0")
}
resolutionStrategy
allprojects {
    configurations.all {
        resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
        resolutionStrategy.cacheChangingModulesFor 0, "seconds"
    }
}

修改为:

allprojects {
    configurations.all {
        resolutionStrategy.cacheDynamicVersionsFor(0, "seconds")
        resolutionStrategy.cacheChangingModulesFor(0, "seconds")
    }
}
clean
task clean(type: Delete) {
    delete rootProject.buildDir
}

修改为:

task clean(type: Delete) {
    delete(rootProject.buildDir)
}
include
include ":app"

修改为:

include(":app")
plugins
plugins {
    id "com.android.application"
    id "kotlin-android"
    id "kotlin-kapt"
    id "kotlin-parcelize"
}

修改为:

plugins {
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-kapt")
    id("kotlin-parcelize")
}
buildTypes
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
    }
}

修改为:

buildTypes {
    release {
        minifyEnabled false
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
    }
}
productFlavors
productFlavors {
    pad {
        dimension "platform"
        buildConfigField "boolean", "USE_EXCAR", "false"
    }
    car {
        dimension "platform"
        buildConfigField "boolean", "USE_EXCAR", "true"
    }
}

修改为:

productFlavors {
    pad {
        dimension "platform"
        buildConfigField("boolean", "USE_EXCAR", "false")
    }
    car {
        dimension "platform"
        buildConfigField("boolean", "USE_EXCAR", "true")
    }
}
packagingOptions
packagingOptions {
    jniLibs {
        useLegacyPackaging true
    }
    exclude "MANIFEST.MF"
    exclude "META-INF/LICENSE.md"
    exclude "META-INF/LICENSE-notice.md"
}

修改为:

packagingOptions {
    jniLibs {
        useLegacyPackaging true
    }
    exclude("MANIFEST.MF")
    exclude("META-INF/LICENSE.md")
    exclude("META-INF/LICENSE-notice.md")
}

属性赋值转换

groovy的属性赋值添加=.

android
android {
    namespace "com.max.net"
    compileSdk rootProject.ext.compileSdkVersion
}

修改为:

android {
    namespace = "com.max.net"
    compileSdk = rootProject.ext.compileSdkVersion
}
defaultConfig
defaultConfig {
    applicationId "com.max.example"
    minSdk rootProject.ext.minSdkVersion
    targetSdk rootProject.ext.targetSdkVersion
    versionCode 23021119
    versionName "01.1.01.070"
    multiDexEnabled true
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

修改为:

defaultConfig {
    applicationId = "com.max.example"
    minSdk = rootProject.ext.minSdkVersion
    targetSdk = rootProject.ext.targetSdkVersion
    versionCode = 23021119
    versionName = "01.1.01.070"
    multiDexEnabled = true
    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs
signingConfigs {
    release {
        keyAlias keystore.keyAlias
        keyPassword keystore.keyPassword
        storeFile file(keystore.storeFile)
        storePassword keystore.storePassword
        v1SigningEnabled true
        v2SigningEnabled false
    }
}

修改为:

signingConfigs {
    release {
        keyAlias = keystore.keyAlias
        keyPassword = keystore.keyPassword
        storeFile = file(keystore.storeFile)
        storePassword = keystore.storePassword
        v1SigningEnabled = true
        v2SigningEnabled = false
    }
}
buildTypes
buildTypes {
    release {
        signingConfig signingConfigs.release
        minifyEnabled false
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        ndk {
            abiFilters "arm64-v8a", "x86"
        }
    }
}

修改为:

release {
    signingConfig = signingConfigs.release
    minifyEnabled = false
    proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
    ndk {
        abiFilters = ["arm64-v8a", "x86"]
    }
}
compileOptions & kotlinOptions & buildFeatures
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
    jvmTarget "1.8"
}

buildFeatures {
    viewBinding true
    dataBinding true
}

修改为:

compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

kotlinOptions {
    jvmTarget = "1.8"
}

buildFeatures {
    viewBinding = true
    dataBinding = true
}
lintOptions
lintOptions {
    checkReleaseBuilds false
    abortOnError false
    disable "MissingTranslation"
}

修改为:

lintOptions {
    checkReleaseBuilds = false
    abortOnError = false
    disable += ["MissingTranslation"]
}
productFlavors
flavorDimensions "platform"
productFlavors {
    pad {
        dimension "platform"
        buildConfigField("boolean", "USE_EXCAR", "false")
    }
}

修改为:

flavorDimensions = ["platform"]
productFlavors {
    pad {
        dimension = "platform"
        buildConfigField("boolean", "USE_EXCAR", "false")
    }
}
packagingOptions
packagingOptions {
    jniLibs {
        useLegacyPackaging true
    }
    exclude("MANIFEST.MF")
    exclude("META-INF/LICENSE.md")
    exclude("META-INF/LICENSE-notice.md")
}

修改为:

packagingOptions {
    jniLibs {
        useLegacyPackaging = true
    }
    exclude("MANIFEST.MF")
    exclude("META-INF/LICENSE.md")
    exclude("META-INF/LICENSE-notice.md")
}

开始迁移

完成以上准备工作后, 请尝试重新编译项目, 并验证功能正常, 即可以正式开始Kotlin DSL的迁移.

  1. 首先重命名项目中所有.gradle文件后缀为.gradle.kts

  2. 替换repositoriesmaven{}使用方式

    maven {
    allowInsecureProtocol = true
    url("http://10.167.50.6:8081/repository/maven-releases/")
    credentials {
    username = "xxx"
    password = "xxx"
    }
    }

修改为:

maven {
    // 为布尔值属性添加 is 前缀
    isAllowInsecureProtocol = true
    setUrl("http://10.167.50.6:8081/repository/maven-releases/")
    credentials {
        username = "xxx"
        password = "xxx"
    }
}
// 或者
maven("http://10.167.50.6:8081/repository/maven-releases/") {
    // 为布尔值属性添加 is 前缀
    isAllowInsecureProtocol = true
    credentials {
        username = "xxx"
        password = "xxx"
    }
}
  1. clean任务迁移

    task clean(type: Delete) {
    delete(rootProject.buildDir)
    }

修改为:

tasks.register<Delete>("clean") {
    delete(rootProject.buildDir)
}
  1. sourceSets表达式迁移

    sourceSets {
    main {
    jniLibs.srcDirs = ["libs"]
    }
    }

修改为:

sourceSets {
    getByName("main") {
        jniLibs {
            srcDirs("libs")
        }
    }
}
  1. productFlavors迁移

    flavorDimensions = ["platform"]
    productFlavors {
    pad {
    dimension = "platform"
    buildConfigField("boolean", "USE_EXCAR", "false")
    }
    }

修改为:

// Groovy 和 Kotlin 中的列表和映射使用不同的语法进行定义。
// Groovy 使用 [],而 Kotlin 使用 listOf 或 mapOf 显式调用集合创建方法。
// 迁移时,请务必将 [] 替换为 listOf 或 mapOf。
flavorDimensions += listOf("platform")
productFlavors {
    create("pad") {
        dimension = "platform"
        buildConfigField("boolean", "USE_EXCAR", "false")
    }
}
  1. lintOptions迁移

    lintOptions {
    checkReleaseBuilds = false
    abortOnError = false
    disable += ["MissingTranslation"]
    }

修改为:

lint {
    checkReleaseBuilds = false
    abortOnError = false
    disable += listOf("MissingTranslation")
}
  1. signingConfigs迁移

    signingConfigs {
    releaseConfig {
    keyAlias = "android"
    keyPassword = "android"
    storeFile = file("../app/lambda_platform.jks")
    storePassword = "xxx"
    v1SigningEnabled = true
    v2SigningEnabled = false
    }
    }

修改为:

signingConfigs {
    create("releaseConfig") {
        keyAlias = "android"
        keyPassword = "android"
        storeFile = file("../app/lambda_platform.jks")
        storePassword = "xxx"
        enableV1Signing = true
        enableV2Signing = false
    }
}
  1. buildTypes迁移

    buildTypes {
    release {
    signingConfig = signingConfigs.releaseConfig
    minifyEnabled = false
    proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
    }
    }

修改为:

buildTypes {
    release {
        signingConfig = signingConfigs.getByName("releaseConfig")
        isMinifyEnabled = false
        proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
    }
}
  1. 依赖管理方式迁移, 如果项目中使用了ext全局变量去管理依赖版本, 请移除相关代码, 因为Kotlin DSL不再支持该形式. 建议迁移至VersionCatelog管理

项目内新增libs.versions.toml文件本地维护依赖:

依赖迁移:

// ../app/build.gradle.

android {
    compileSdk = rootProject.ext.compileSdkVersion
    defaultConfig {
        applicationId = "com.max.example"
        minSdk = rootProject.ext.minSdkVersion
        targetSdk = rootProject.ext.targetSdkVersion
        versionCode = 23021119
        versionName = "01.1.01.070"
        multiDexEnabled = true
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
    ...
}

dependencies {
    // 部分依赖
    implementation("com.google.android.material:material:$material_version")
    implementation("androidx.core:core-ktx:$coreKtx_version")
    implementation("androidx.appcompat:appcompat:$appcompat_version")
    implementation("androidx.constraintlayout:constraintlayout:$constraintlayout_version")
    implementation("androidx.fragment:fragment-ktx:$fragment_version")
    implementation("androidx.viewpager2:viewpager2:1.0.0")
}

修改为:

// ../app/build.gradle.kts

android {
    compileSdk = libs.versions.sdk.compile.get().toInt()
    defaultConfig {
        applicationId = "com.max.example"
        minSdk = libs.versions.sdk.min.get().toInt()
        targetSdk = libs.versions.sdk.target.get().toInt()
        versionCode = 23021119
        versionName = "01.1.01.070"
        multiDexEnabled = true
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
    ...
}

dependencies {
    // 部分依赖
    implementation(libs.google.material)
    implementation(libs.core.ktx)
    implementation(libs.appcompat)
    implementation(libs.constraintlayout)
    implementation(libs.fragment.ktx)
    implementation(libs.viewpager2)
}

迁移记录

暂时无法在路特斯桌面文档外展示此内容

zip项目文件中, 有Q音项目迁移实操的修改记录供大家参考

需要注意的是, app 模块中只迁移了部分依赖, 所以项目是无法编译的.
参考文档:

Migrating build logic from Groovy to Kotlin

将 build 配置从 Groovy 迁移到 KTS

Gradle迁移指南:从Groovy到KTS

Android gradle 插件升级和 kts 迁移踩坑指南

相关推荐
梦想平凡3 分钟前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
十年一梦实验室7 分钟前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
isolusion9 分钟前
Springboot的创建方式
java·spring boot·后端
最爱番茄味15 分钟前
Python实例之函数基础打卡篇
开发语言·python
元争栈道28 分钟前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
zjw_rp38 分钟前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob1 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder1 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
engchina1 小时前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎