演进 Gradle API 以减少配置时间
简介
本文介绍了用于在构建脚本和插件中声明和配置 Gradle 任务 的新 API。我们的目标是让这个新 API 最终取代现有的 API,因为它允许 Gradle 避免配置不必要的构建逻辑。新 API 的使用将很快成为默认建议,但现有 API 将在几个主要版本中经历我们通常的弃用过程。
我们正在邀请早期采用者试用新的 Gradle 任务 API,以解决任何问题并收集反馈。我们创建了一个新的用户手册章节,以快速介绍该功能,并解释了一些将构建迁移到使用新 API 的指南。
我们欢迎您对该 API 的任何反馈。您可以在此 Gradle issue 上留下反馈。
Gradle 4.9 中新的 Gradle 任务 API 预览 #
现有 Gradle 任务 API 和新 Gradle 任务 API 之间的主要区别之一是 Gradle 是否花费时间来创建 Task
实例并运行配置代码。新的 API 允许 Gradle 延迟或完全避免配置在构建中永远不会执行的任务。例如,在编译代码时,Gradle 不需要配置运行测试的任务。
花费在创建和配置永远不会使用的任务上的时间是导致 Gradle 总体配置时间增加的因素之一。配置时间会影响每个构建,因此使其更快对每个人都有好处。
现有的 API 也会尽快急切地创建 Task
实例。这会使插件的顺序更加脆弱。新的 API 旨在通过传递 Provider
给 Task
来更明确地表达 Task
与需要它的事物之间的关系。
我们还在一些构建中看到,配置特定任务非常耗时,因为它们会访问 Web 服务,运行 git status
或需要解析某些内容。如果这些任务很少使用,则每个构建都在为配置该任务付出代价。新的 API 允许插件或构建作者声明耗时的任务,以便仅在必要时才配置它们。
一旦新的 API 成为默认建议,后续的 Gradle 版本将提供更多关于新 API 的详细信息和示例。
Gradle 构建的实测影响 #
在过去的几个月中,我们一直在 gradle/gradle 构建、Gradle Enterprise 和一个典型的 大型项目 中试用新的 Gradle 任务 API。
在那段时间里,我们进行了一些更改以减少 Gradle 构建中的配置时间,在我们的 Linux 性能代理上,配置时间 从 1.7 秒降至 1.4 秒。这些改进部分来自新的 API,Gradle 构建仍然不必要地配置了数百个任务,因此我们相信我们可以进一步减少这个数字。
下面,我们绘制了直接与新 API 相关的改进。我们在一台 2014 年中款 MacBook Pro(2.6 GHz Intel Core i5,16GB RAM)上针对几个不同的项目进行了测量。
perf-enterprise-large 项目是我们性能测试中使用的生成项目,它有近 350 个 Java 模块。当使用现有 API 配置此构建时,Gradle 创建并配置了超过 10000 个任务。使用新的 API,Gradle 仅配置 349 个(避免了 97% 的任务)。未来版本的 Gradle 中的更改将使此数字变为 1 个任务。从图中我们可以看到,平均配置时间从 936 毫秒降至 703 毫秒(-233 毫秒)。
第二组图是针对 gradle/gradle 构建的。在没有新 API 的情况下,Gradle 将创建并配置超过 6300 个任务。我们还有更多工作要做,但我们避免了创建和配置 90% 的任务。我们的平均配置时间从 1325 毫秒降至 1117 毫秒(-208 毫秒)。
对于我们的闭源 Gradle Enterprise 项目,我们才刚刚开始将其转换为新的 API。我们只避免了大约 64% 的任务。我们的平均配置时间从 1636 毫秒降至 1467 毫秒(-169 毫秒)。
我们预计,当避免许多不需要的任务时,其他构建也会看到类似的减少。如果某些构建更大(数百个子项目)或配置非常耗时的任务,则它们可能会看到更大的影响。我们正在与著名的插件作者合作以使用新的 API,因此大多数构建都将看到任务避免带来的好处。
为什么需要新的 API? #
不幸的是,现有的 API 无法适应我们的新要求
Task
实例不应立即创建。许多现有的 API 返回Task
,我们无法破坏二进制兼容性。- 除非需要,否则不应创建或配置任务。许多现有的 API 可以 适应延迟创建或配置,但这会在难以诊断的方式中悄悄地破坏许多构建。
新的 API 旨在与现有的 API 并行工作,因此使用现有 API 创建的任务对新的 API 可见,反之亦然。构建可以逐步迁移到新的 API,但混合使用现有 API 会否定新 API 的某些好处。任何需要 Task
实例的现有 API 都将强制使用新 API 创建的任务像通过现有 API 创建的任务一样被创建。
新的 API 也旨在与现有 API 足够相似,以便于迁移。
从现有的 Gradle 任务 API 迁移到新的 API #
我们的 用户手册章节 提供了将现有 API 映射到新 API 的参考。
作为一个快速而粗略的参考
tasks.create(...)
变为tasks.register(...)
tasks.withType(SomeType) { }
变为tasks.withType(SomeType).configureEach { }
tasks.all { }
变为tasks.configureEach { }
tasks.getByName(...)
变为tasks.named(...)
- 对于 Groovy Gradle DSL,没有使用新 API 的
task foo(...) { }
的替代方案。
请记住,与新 API 一起使用的配置块不能保证始终执行。
在现有的 API 中,如果您要使用 tasks.withType(...)
plugins {
id "java"
}
tasks.withType(JavaCompile) { javaCompile ->
println "Hello, " + javaCompile.name
}
运行 gradle help
或 gradle build
或 gradle compileJava
将记录 “Hello, compileJava” 和 “Hello, compileTestJava”。配置的任务不会根据需要执行的内容而改变。
如果我们改为使用新的 API tasks.withType(...).configureEach
plugins {
id "java"
}
tasks.withType(JavaCompile).configureEach { javaCompile ->
println "Hello, " + javaCompile.name
}
我们看到行为非常不同。Gradle 根据执行所需的内容配置或多或少的构建。
- 运行
gradle help
将不会记录任何 “Hello” 消息。Gradle 能够避免配置JavaCompile
任务,因为它们永远不会被执行。 - 运行
gradle build
将 记录 “Hello, compileJava” 和 “Hello, compileTestJava”。Gradle 必须配置JavaCompile
任务,因为它们都被执行。 - 运行
gradle compileJava
将仅 记录 “Hello, compileJava”。Gradle 只需要配置compileJava
任务,因为它被执行了。compileTestJava
任务未执行,因此它不会被创建或配置。
期待您的反馈 #
在 Gradle Enterprise 2018.3 中包含,您可以在构建扫描中看到 使用新的任务 API 的进度。对于许多内置的 Gradle 插件,我们已切换为使用新的 API。对于使用 Java 的构建,您可能会看到大量从未配置的任务。
这个 Gradle 任务 API 仍在开发中,但我们相信它已经足够完整,可以在所有场景中取代现有的 API。我们计划将任何反馈纳入下一个版本,其中将包含更多关于使用新 API 的文档和示例。我们的目标是尽快使该 API 投入生产使用。
请 在您的插件和构建中试用此 API,并告诉我们您的想法。我们在用户手册中包含了一个 章节,用于收集现有 API 和新 API 之间性能比较的数据。我们很乐意看到您的结果,并听取您认为哪些方面做得好、哪些方面令人困惑以及缺少哪些会阻止您使用这个新 API 而不是现有 API 的内容。您可以在此 Gradle issue 上留下反馈。