从 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 仅支持 create(…)
API。create(…)
API 在调用时会急切地创建和配置任务,应避免使用。
仅使用 register(…) 可能不足以完全避免所有任务配置。您可能需要更改其他通过名称或类型配置任务的代码,请参见下文。
|
延迟任务配置
像 DomainObjectCollection.all(org.gradle.api.Action) 和 DomainObjectCollection.withType(java.lang.Class, org.gradle.api.Action) 这样的急切 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()。这将导致任务被创建和配置,但所有操作都应与使用急切 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
用于延迟配置Project
,因此将延迟配置与新 API 混合可能会导致难以诊断的错误,因为使用新 API 注册的任务并不总是被配置,但始终预期afterEvaluate
块执行。
迁移步骤
迁移过程的第一步是遍历代码,手动将急切任务创建和配置迁移到使用配置避免 API。
-
迁移影响所有任务(
tasks.all {}
)或按类型划分的子集(tasks.withType(…) {}
)的任务配置。
这将导致您的构建急切地创建更少的由插件注册的任务。 -
迁移按名称配置的任务。
这将导致您的构建急切地创建更少的由插件注册的任务。例如,使用TaskContainer#getByName(String, Closure)
的逻辑应该转换为TaskContainer#named(String, Action)
。这也包括 通过 DSL 块进行的任务配置。 -
迁移任务创建到
register(…)
。
此时,您应该将任何任务创建(使用create(…)
或类似方法)更改为使用 register 代替。
完成这些更改后,您应该看到配置时急切创建的任务数量有所改善。
迁移故障排除
-
哪些任务正在被实现? 使用 构建扫描 通过以下步骤进行故障排除
-
使用
--scan
标志执行 Gradle 命令。 -
导航到配置性能选项卡
-
将显示所有必要的信息
-
创建或未创建每个任务时存在的总任务数。
-
立即创建
代表使用急切任务 API 创建的任务。 -
配置期间创建
代表使用配置避免 API 创建的任务,但已明确实现(通过TaskProvider#get()
)或隐式使用急切任务查询 API。 -
立即创建
和配置期间创建
这两个数字都被认为是“不良”数字,应该尽可能地减少。 -
任务执行期间创建
代表在创建任务图之后创建的任务。在此阶段创建的任何任务都不会作为图的一部分执行。理想情况下,此数字应为零。 -
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
API | 注意 |
---|---|
返回 |
|
返回 |
|
可以使用。如果链接 |
|
返回 |
要避免的急切 API
API | 注意 |
---|---|
|
不要使用简写符号。改用 |
改用 |
|
不要使用。 |
|
不要使用。 |
|
避免调用此方法。行为将来可能会改变。 |
|
改用 |
|
改用 |
|
改用 |
|
如果您是根据名称匹配,请改用 |
|
改用 |
|
请使用 |
|
请使用 |
|
请使用 |
|
避免调用此方法。在大多数情况下, |
|
请勿使用。 |
|
|
避免这样做,因为它需要创建和配置所有任务。 |
|
避免调用此方法。行为将来可能会改变。 |