从 Gradle 5.1 开始,我们建议在创建任务时使用配置避免 API。
任务配置回避API
如果任务不用于构建,配置避免 API 会避免配置任务,这可能会显着影响总配置时间。
例如,当运行一个compile
任务(java
应用了插件)时,其他不相关的任务(如clean
、test
、javadocs
)将不会被执行。
为了避免创建和配置构建不需要的任务,我们可以注册该任务。
当任务被注册时,构建就知道它。它可以被配置,并且可以传递对它的引用,但是任务对象本身尚未创建,并且其操作尚未执行。已注册的任务将保持此状态,直到构建中的某些内容需要实例化的任务对象。如果永远不需要任务对象,则任务将保持注册状态,并且将避免创建和配置任务的成本。
在 Gradle 中,您可以使用TaskContainer.register(java.lang.String)注册任务。该方法不返回任务实例,而是register(…)
返回一个TaskProvider,它是对任务的引用,可以在许多可能使用普通任务对象的地方(即,创建任务依赖项时)使用该任务。
指南
推迟任务创建
有效的任务配置避免需要构建作者将TaskContainer.create(java.lang.String)的实例更改为TaskContainer.register(java.lang.String)。
旧版本的 Gradle 仅支持 API create(…)
。该create(…)
API 在调用时会急切地创建和配置任务,因此应避免这样做。
单独使用register(…) 可能不足以完全避免所有任务配置。您可能需要更改按名称或按类型配置任务的其他代码,请参见下文。
|
推迟任务配置
像DomainObjectCollection.all(org.gradle.api.Action)和DomainObjectCollection.withType(java.lang.Class, org.gradle.api.Action)这样的 Eager API将立即创建和配置任何已注册的任务。要推迟任务配置,您必须迁移到等效的配置避免 API。请参阅下表以确定最佳替代方案。
引用已注册的任务
您可以通过TaskProvider对象处理已注册的任务,而不是引用任务对象。可以通过多种方式获取 TaskProvider,包括TaskContainer.register ( java.lang.String)和TaskCollection.named(java.lang.String)方法。
调用Provider.get()或使用TaskCollection.getByName(java.lang.String)按名称查找任务将导致创建和配置任务。
像Task.dependsOn(java.lang.Object...)和ConfigurableFileCollection.builtBy(java.lang.Object...)这样的方法与TaskProvider 的工作方式与Task相同,因此您无需打开Provider
显式依赖项即可继续工作。
您必须使用与按名称配置任务等效的配置避免。请参阅下表以确定最佳替代方案。
引用任务实例
如果您需要访问 Task 实例,可以使用TaskCollection.named(java.lang.String)和Provider.get()。这将导致任务被创建和配置,但一切都应该像使用 eager API 一样工作。
避免配置的任务排序
调用排序方法本身不会导致任务创建。所有这些方法所做的就是声明关系。
这些关系的存在可能会间接导致构建过程后期阶段的任务创建。 |
当需要建立任务关系时(即,,,,,dependsOn
),可以区分软关系和强关系。它们对配置阶段任务创建的影响不同:finalizedBy
mustRunAfter
shouldRunAfter
-
Task.mustRunAfter(…)和Task.shouldRunAfter(…)表示软关系,只能更改现有任务的顺序,但不能触发其创建。
-
Task.dependsOn(…)和Task.finalizedBy(…)表示强关系,这会强制执行引用的任务,即使它们没有以其他方式创建。
-
如果任务未执行,无论它是使用Task.register(…)还是Task.create(…)创建的,定义的关系都不会在配置时触发任务创建。
-
如果执行任务,则必须在配置时创建和配置所有强关联任务,因为它们可能具有其他
dependsOn
关系finalizedBy
。这将传递地发生,直到任务图包含所有强关系。
迁移指南
迁移指南
-
在迁移过程中使用
help
任务作为基准。
该help
任务是对迁移过程进行基准测试的完美候选者。在仅使用配置避免 API 的构建中,构建扫描显示在配置期间没有创建任何任务,并且仅创建执行的任务。 -
仅在配置操作中改变当前任务。
由于任务配置操作现在可以立即运行、稍后运行或从不运行,因此改变当前任务以外的任何内容都可能会导致构建中的不确定行为。考虑以下代码:val check by tasks.registering tasks.register("verificationTask") { // Configure verificationTask // Run verificationTask when someone runs check check.get().dependsOn(this) }
def check = tasks.register("check") tasks.register("verificationTask") { verificationTask -> // Configure verificationTask // Run verificationTask when someone runs check check.get().dependsOn verificationTask }
执行
gradle check
任务应该执行verificationTask
,但在这个例子中,它不会。这是因为verificationTask
和之间的依赖关系只有在实现check
时才会发生。verificationTask
为了避免此类问题,您必须仅修改与配置操作关联的任务。其他任务应在其自己的配置操作中进行修改:val check by tasks.registering val verificationTask by tasks.registering { // Configure verificationTask } check { dependsOn(verificationTask) }
def check = tasks.register("check") def verificationTask = tasks.register("verificationTask") { // Configure verificationTask } check.configure { dependsOn verificationTask }
将来,Gradle 会将这种反模式视为错误并产生异常。
-
更喜欢小的增量变化。
较小的更改更容易进行健全性检查。如果您破坏了构建逻辑,则分析自上次成功验证以来的变更日志会更容易。 -
确保制定良好的计划来验证构建逻辑。
通常,一个简单的build
任务调用就可以验证您的构建逻辑。但是,某些构建可能需要额外的验证 - 了解构建的行为并确保您有一个良好的验证计划。 -
避免通过名称引用任务。
通常,通过名称引用任务是一种脆弱的模式,应该避免。尽管任务名称在 上可用TaskProvider
,但应尽量使用强类型模型中的引用。 -
尽可能使用新的任务API。
急切地实现某些任务可能会导致其他任务的级联实现。使用TaskProvider
有助于创建一种间接的方式来防止传递实现。 -
如果您尝试从新 API 的配置块访问某些 API,它们可能会被禁止。
例如,Project.afterEvaluate()
在配置使用新 API 注册的任务时无法调用。由于afterEvaluate
用于延迟配置 aProject
,因此将延迟配置与新 API 混合可能会导致难以诊断的错误,因为使用新 API 注册的任务并不总是被配置,但afterEvaluate
可能总是期望执行一个块。
迁移步骤
迁移过程的第一部分是检查代码并手动迁移急切任务创建和配置以使用配置避免 API。
-
迁移影响所有任务 (
tasks.all {}
) 或按类型 (tasks.withType(…) {}
) 划分的子集的任务配置。
这将导致您的构建急切地创建更少的由插件注册的任务。 -
迁移按名称配置的任务。
这将导致您的构建急切地创建更少的由插件注册的任务。例如,使用的逻辑TaskContainer#getByName(String, Closure)
应转换为TaskContainer#named(String, Action)
.这还包括通过 DSL 块进行任务配置。 -
将任务创建迁移到
register(…)
.
此时,您应该将任何任务创建(使用create(…)
或类似)更改为使用寄存器。
进行这些更改后,您应该会看到在配置时急切创建的任务数量有所增加。
迁移故障排除
-
正在实现哪些任务?按照以下步骤使用构建扫描进行故障排除:
-
使用该标志执行 Gradle 命令
--scan
。 -
导航到配置性能选项卡:
-
将提供所需的所有信息:
-
每个任务创建或未创建时存在的总任务数。
-
Created immediately
表示使用 eager task API 创建的任务。 -
Created during configuration
TaskProvider#get()
表示使用配置避免 API 创建的任务,但显式(通过)或隐式使用 eager 任务查询 API实现。 -
Created immediately
和数字都Created during configuration
被认为是“坏”数字,应尽可能减少。 -
Created during task execution
表示创建任务图后创建的任务。此时创建的任何任务都不会作为图表的一部分执行。理想情况下,这个数字应该为零。 -
Created during task graph calculation
表示构建执行任务图时创建的任务。理想情况下,该数字等于执行的任务数。 -
Not created
表示在此构建会话中避免的任务。理想情况下,这个数字尽可能大。
-
-
下一节将帮助回答任务在哪里实现的问题。对于每个脚本、插件或生命周期回调,最后一列表示立即或在配置期间创建的任务。理想情况下,此列应为空。
-
重点关注脚本、插件或生命周期回调将显示已创建任务的详细信息。
-
-
迁移陷阱
-
当心隐藏的急切任务实现。 可以通过多种方式快速配置任务。
例如,使用任务名称和 DSL 块配置任务将导致立即创建任务(当使用 Groovy DSL 时):// Given a task lazily created with tasks.register("someTask") // Some time later, the task is configured using a DSL block someTask { // This causes the task to be created and this configuration to be executed immediately }
相反,使用该
named()
方法来获取对任务的引用并对其进行配置:tasks.named("someTask") { // ... // Beware of the pitfalls here }
同样,Gradle 具有语法糖,允许通过名称引用任务,而无需显式查询方法。这也可能导致立即创建任务:
tasks.register("someTask") // Sometime later, an eager task is configured like task anEagerTask { // The following will cause "someTask" to be looked up and immediately created dependsOn someTask }
有几种方法可以避免这种过早创建:
-
使用
TaskProvider
变量。 当在同一构建脚本中多次引用任务时很有用。val someTask by tasks.registering task("anEagerTask") { dependsOn(someTask) }
def someTask = tasks.register("someTask") task anEagerTask { dependsOn someTask }
-
将消费者任务迁移到新的 API。
tasks.register("someTask") tasks.register("anEagerTask") { dependsOn someTask }
-
懒洋洋地查找任务。 当任务不是由同一插件创建时很有用。
tasks.register("someTask") task("anEagerTask") { dependsOn(tasks.named("someTask")) }
tasks.register("someTask") task anEagerTask { dependsOn tasks.named("someTask") }
-
使用惰性 API
应用程序编程接口 | 笔记 |
---|---|
返回 a |
|
返回 a |
|
可以使用了。如果是链接的 |
|
返回 |
需要避免的急切 API
应用程序编程接口 | 笔记 |
---|---|
|
不要使用速记符号。代替使用 |
代替使用 |
|
不使用。 |
|
不使用。 |
|
避免这样称呼。该行为将来可能会改变。 |
|
代替使用 |
|
代替使用 |
|
代替使用 |
|
如果您根据名称进行匹配,请使用 |
|
代替使用 |
|
代替使用 |
|
代替使用 |
|
代替使用 |
|
避免调用此方法。 |
|
不使用。 |
|
|
避免这样做,因为它需要创建和配置所有任务。 |
|
避免这样称呼。该行为将来可能会改变。 |