自 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 的构建中,构建扫描显示在配置阶段没有任务被创建,只有被执行的任务才会被创建。 -
仅在配置 action 内部修改当前任务。
由于任务配置 action 现在可以立即运行、稍后运行或从不运行,修改当前任务之外的任何内容都可能导致构建中出现不确定行为。请考虑以下代码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
被实例化时才会发生。为了避免这类问题,您必须仅修改与配置 action 关联的任务。其他任务应在其自己的配置 action 中修改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,可能会被禁止。
例如,在使用新 API 注册的任务配置时,不能调用Project.afterEvaluate()
。由于afterEvaluate
用于延迟配置Project
,将延迟配置与新 API 混合使用可能会导致难以诊断的错误,因为使用新 API 注册的任务并非总是被配置,而afterEvaluate
块可能总是期望执行。
迁移步骤
迁移过程的第一步是通读代码,手动将急切的任务创建和配置迁移为使用配置回避 API。
-
迁移影响所有任务 (
tasks.all {}
) 或按类型影响任务子集 (tasks.withType(…) {}
) 的任务配置。
这将使得您的构建急切创建的由插件注册的任务数量减少。 -
迁移按名称配置的任务。
这将使得您的构建急切创建的由插件注册的任务数量减少。例如,使用TaskContainer#getByName(String, Closure)
的逻辑应转换为使用TaskContainer#named(String, Action)
。这也包括通过 DSL 块配置任务。 -
将任务创建迁移到使用
register(…)
。
此时,您应该将任何任务创建(使用create(…)
或类似方法)更改为使用 register。
完成这些更改后,您应该会看到在配置阶段急切创建的任务数量有所改善。
迁移故障排除
-
哪些任务正在被实例化? 使用 Build Scan 按照以下步骤进行故障排除
-
使用
--scan
标志执行 Gradle 命令。 -
导航到配置性能标签页
-
将呈现所有必需的信息
-
创建或未创建每个任务时存在的总任务数。
-
Created immediately
表示使用急切任务 API 创建的任务。 -
Created during configuration
表示使用配置回避 API 创建的任务,但通过显式(通过TaskProvider#get()
)或隐式(使用急切任务查询 API)方式被实例化。 -
Created immediately
和Created during configuration
这两个数字都被视为应尽可能最小化的“不良”数字。 -
Created during task execution
表示在任务图创建之后创建的任务。此时创建的任何任务都不会作为图的一部分执行。理想情况下,此数字应为零。 -
Created during task graph calculation
表示在构建执行任务图时创建的任务。理想情况下,此数字应等于已执行任务的数量。 -
Not created
表示在此构建会话中回避的任务。理想情况下,此数字应尽可能大。
-
-
下一节有助于回答任务是在哪里被实例化的问题。对于每个脚本、插件或生命周期回调,最后一列表示立即创建或在配置期间创建的任务。理想情况下,此列应为空。
-
重点关注某个脚本、插件或生命周期回调,将显示被创建任务的详细 breakdown。
-
-
迁移陷阱
-
警惕隐藏的急切任务实例化。 有许多方式可以急切地配置任务。
例如,使用任务名称和 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 }
-
将 consumer 任务迁移到新的 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 | 注意 |
---|---|
|
不要使用简写符号。改用 |
改用 |
|
不要使用。 |
|
不要使用。 |
|
避免调用此方法。未来行为可能发生变化。 |
|
改用 |
|
改用 |
|
改用 |
|
如果您基于名称进行匹配,请改用延迟执行的 |
|
改用 |
|
改用 |
|
改用 |
|
改用 |
|
避免调用此方法。在大多数情况下, |
|
不要使用。 |
|
|
避免这样做,因为它需要创建和配置所有任务。 |
|
避免调用此方法。未来行为可能发生变化。 |