使用 Kotlin DSL

在编写新构建或在现有构建中创建新子项目时,优先使用 Kotlin DSL (build.gradle.kts),而不是 Groovy DSL (build.gradle)。

说明

Kotlin DSL 相比 Groovy DSL 有几个优势

  • 严格类型:IDE 对 Kotlin DSL 提供更好的自动补全和导航支持。

  • 提高可读性:用 Kotlin 编写的代码通常更容易理解和追踪。

  • 单一语言栈:对于生产和测试代码已使用 Kotlin 的项目,无需为了构建而引入 Groovy。

自 Gradle 8.0 起,对于使用 gradle init 创建的新构建,Kotlin DSL 是默认选项。Android Studio 也默认使用 Kotlin DSL

使用 Gradle 的最新小版本

始终使用当前 Gradle 主版本的最新小版本,并定期将插件更新到最新的兼容版本。

说明

Gradle 遵循相对可预测的、基于时间的发布节奏。只有当前主版本和前一个主版本的最新小版本才被积极支持。

我们推荐以下策略

  • 尝试直接升级到当前 Gradle 主版本的最新小版本。

  • 如果失败,则一次升级一个小版本,以隔离回归或兼容性问题。

每个新的小版本包括

  • 性能和稳定性改进。

  • 弃用警告,帮助您为下一个主版本做准备。

  • 已知 Bug 和安全漏洞的修复。

使用 wrapper 任务更新项目

./gradlew wrapper --gradle-version <version>

您还可以使用 SDKMAN!Homebrew 等工具轻松安装最新 Gradle 版本,具体取决于您的平台。

插件兼容性

始终使用每个插件的最新兼容版本

  • 先升级 Gradle,再升级插件。

  • 使用 shadow jobs 测试插件兼容性。

  • 更新时查阅变更日志。

保持了解

订阅 Gradle 时事通讯,及时了解 Gradle 的新版本、新功能和新插件。

标签

#plugins

使用 plugins 块应用插件

您应该始终使用 plugins 块在构建脚本中应用插件

说明

plugins 块是 Gradle 中应用插件的首选方式。插件 API 允许 Gradle 更好地管理插件的加载,它比在 buildscript 的 classpath 中显式添加依赖项来使用 apply 方法更简洁且更不易出错。

它允许 Gradle 优化插件类的加载和复用,并有助于工具了解插件将添加到构建脚本中的扩展可能拥有的属性和值。它被限制为幂等(每次执行产生相同结果)且无副作用(Gradle 在任何时候执行都是安全的)。

示例

不要这样做

build.gradle.kts
buildscript {
    repositories {
        gradlePluginPortal() (1)
    }

    dependencies {
        classpath("com.google.protobuf:com.google.protobuf.gradle.plugin:0.9.4") (2)
    }
}

apply(plugin = "java") (3)
apply(plugin = "com.google.protobuf") (4)
build.gradle
buildscript {
    repositories {
        gradlePluginPortal() (1)
    }

    dependencies {
        classpath("com.google.protobuf:com.google.protobuf.gradle.plugin:0.9.4") (2)
    }
}

apply plugin: "java" (3)
apply plugin: "com.google.protobuf" (4)
1 声明仓库:要使用旧版插件应用语法,您需要显式告诉 Gradle 在哪里找到插件。
2 声明插件依赖项:要将旧版插件应用语法与第三方插件一起使用,您需要显式告诉 Gradle 插件的完整坐标。
3 应用核心插件:使用这两种方法都非常相似。
4 应用第三方插件:语法与核心 Gradle 插件相同,但在 buildscript 的应用点没有版本信息。

改用这种方式

build.gradle.kts
plugins {
    id("java") (1)
    id("com.google.protobuf").version("0.9.4") (2)
}
build.gradle
plugins {
    id("java") (1)
    id("com.google.protobuf").version("0.9.4") (2)
}
1 应用核心插件:使用这两种方法都非常相似。
2 应用第三方插件:您可以在 plugins 块本身中,使用方法链指定版本。

不要使用内部 API

不要使用包路径中包含 internal 部分的 API,或名称中以 InternalImpl 为后缀的类型。

说明

使用内部 API 本质上是危险的,并且在升级过程中可能导致严重问题。Gradle 和许多插件(例如 Android Gradle Plugin 和 Kotlin Gradle Plugin)认为这些内部 API 在任何新的 Gradle 版本发布时都可能未经宣布地发生破坏性变更,甚至在小版本发布时也是如此。有许多案例表明,即使经验丰富的插件开发者也曾因使用此类 API 而受到影响,导致其用户遇到意外中断。

如果您需要缺失的特定功能,最好提交功能请求。作为临时解决方案,请考虑将必要的代码复制到您自己的代码库中,并使用复制的代码通过您自己的自定义实现来扩展 Gradle 公共类型。

示例

不要这样做

build.gradle.kts
import org.gradle.api.internal.attributes.AttributeContainerInternal

configurations.create("bad") {
    attributes {
        attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Usage.JAVA_RUNTIME))
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named<Category>(Category.LIBRARY))
    }
    val badMap = (attributes as AttributeContainerInternal).asMap() (1)
    logger.warn("Bad map")
    badMap.forEach { (key, value) ->
        logger.warn("$key -> $value")
    }
}
build.gradle
import org.gradle.api.internal.attributes.AttributeContainerInternal

configurations.create("bad") {
    attributes {
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
    }
    def badMap = (attributes as AttributeContainerInternal).asMap() (1)
    logger.warn("Bad map")
    badMap.each {
        logger.warn("${it.key} -> ${it.value}")
    }
}

configurations.create("good") {
    attributes {
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
    }
    def goodMap = attributes.keySet().collectEntries {
        [Attribute.of(it.name, it.type), attributes.getAttribute(it as Attribute<Object>)]
    }
    logger.warn("Good map")
    goodMap.each {
        logger.warn("$it.key -> $it.value")
    }
}
1 应避免强制转换为 AttributeContainerInternal 并使用 toMap(),因为它依赖于内部 API。

改用这种方式

build.gradle.kts
configurations.create("good") {
    attributes {
        attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Usage.JAVA_RUNTIME))
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named<Category>(Category.LIBRARY))
    }
    val goodMap = attributes.keySet().associate { (1)
        Attribute.of(it.name, it.type) to attributes.getAttribute(it)
    }
    logger.warn("Good map")
    goodMap.forEach { (key, value) ->
        logger.warn("$key -> $value")
    }
}
build.gradle
configurations.create("good") {
    attributes {
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
    }
    def goodMap = attributes.keySet().collectEntries {
        [Attribute.of(it.name, it.type), attributes.getAttribute(it as Attribute<Object>)]
    }
    logger.warn("Good map")
    goodMap.each {
        logger.warn("$it.key -> $it.value")
    }
}
1 实现一个只使用公共 API 的自己的 toMap() 版本会稳健得多。