使用 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 的最新小版本
使用 plugins
块应用插件
您应该始终使用 plugins
块在构建脚本中应用插件。
说明
plugins
块是 Gradle 中应用插件的首选方式。插件 API 允许 Gradle 更好地管理插件的加载,它比在 buildscript 的 classpath 中显式添加依赖项来使用 apply
方法更简洁且更不易出错。
它允许 Gradle 优化插件类的加载和复用,并有助于工具了解插件将添加到构建脚本中的扩展可能拥有的属性和值。它被限制为幂等(每次执行产生相同结果)且无副作用(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)
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 的应用点没有版本信息。 |
改用这种方式
plugins {
id("java") (1)
id("com.google.protobuf").version("0.9.4") (2)
}
plugins {
id("java") (1)
id("com.google.protobuf").version("0.9.4") (2)
}
1 | 应用核心插件:使用这两种方法都非常相似。 |
2 | 应用第三方插件:您可以在 plugins 块本身中,使用方法链指定版本。 |
不要使用内部 API
不要使用包路径中包含 internal
部分的 API,或名称中以 Internal
或 Impl
为后缀的类型。
说明
使用内部 API 本质上是危险的,并且在升级过程中可能导致严重问题。Gradle 和许多插件(例如 Android Gradle Plugin 和 Kotlin Gradle Plugin)认为这些内部 API 在任何新的 Gradle 版本发布时都可能未经宣布地发生破坏性变更,甚至在小版本发布时也是如此。有许多案例表明,即使经验丰富的插件开发者也曾因使用此类 API 而受到影响,导致其用户遇到意外中断。
如果您需要缺失的特定功能,最好提交功能请求。作为临时解决方案,请考虑将必要的代码复制到您自己的代码库中,并使用复制的代码通过您自己的自定义实现来扩展 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))
}
val badMap = (attributes as AttributeContainerInternal).asMap() (1)
logger.warn("Bad map")
badMap.forEach { (key, value) ->
logger.warn("$key -> $value")
}
}
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。 |
改用这种方式
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")
}
}
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() 版本会稳健得多。 |