引言

目标受众:Gradle开发者曾经遇到过’神奇的’或意外的构建行为.

在我们创建插件的历程中`site-baker`, 我们遵循了严格的TDD方法。每个功能都进行了测试、验证,并且我们充满信心地前进。然后有一天,意外到来。构建开始表现得反复无常。插件逻辑或配置文件的修改似乎被忽略,而我们之前可靠的功能测试莫名其妙地失败了。

这种问题可能会令人极度沮丧。它质疑了工具的可靠性和我们代码的有效性。经过一次激烈的调试会话后,罪魁祸首被确定为:Gradle 配置缓存.

�症状:幽灵构建

问题以多种方式表现出来:

  1. 我在一项任务中修改字符串`println`, 但旧的字符串在运行时仍然显示。

  2. 我当时正在更改我的文件中的一个值`managed-jbake-context.yml`, 但插件的行为就像文件没有被修改一样。

  3. 功能测试会即时创建测试项目,但它们失败了,因为插件似乎没有检测到新创建的配置文件。

一切都好像 Gradle 在执行我们构建的一个“幻影版本”,忽略了我们最近的更改。

调查:配置缓存是什么?

配置缓存是 Gradle 相对现代且功能极其强大的特性,在新版本中默认启用。其目标是让构建更快。

原则很简单:
  1. 在第一次执行时,Gradle 执行阶段配置( 读取 的)build.gradle.kts, 创建任务,解析依赖) 并构建任务图。

  2. 在此阶段结束时,Gradle序列化此任务图并将其缓存。

  3. 在随后的执行中,如果没有任何更改(构建脚本`gradle.properties`, etc.), Gradle完全跳过配置阶段并重新使用缓存的任务图。

在大型项目中,时间节约效果惊人。然而,这种性能有代价:它对插件的编写方式提出了严格的要求。

config cache lifecycle
Figure 1. 配置缓存的生命周期

问题的原因:不合规的插件

我们的插件`site-baker`不知不觉地违反了配置缓存的若干规则。 为了使任务图可序列化,任务不得包含对复杂对象(如对象)的引用`Project`或以任意方式读取文件,在执行阶段。

我们的主要错误是在任务执行逻辑内直接读取 YAML 文件的内容,使用我们扩展中存储的路径引用。这种方法与缓存不兼容,因为如果这种读取未被建模为一个,Gradle 无法知道文件内容是否已更改。任务条目(任务输入).

临时解决方案:禁用缓存

为了使我们摆脱困境并恢复可预测的构建行为,最快的解决方案是禁用配置缓存。只需在文件中添加以下行即可。gradle.properties`使用该插件的项目(或者在我们的情况下,是测试项目)`site-baker).

# site-baker/gradle.properties
org.gradle.configuration-cache=false

瞬间,构建恢复了正常行为。每次执行都重新启动了配置阶段,我们的更改被考虑在内。

然而,这是一个变通的解决方案,而不是一个持久的解决方案。它牺牲了性能,也没有解决我们插件的根本问题。

真正的解决方案:使插件兼容

为了让插件成为现代 Gradle 生态系统的良好公民,它必须与配置缓存兼容。这意味着我们需要重新思考数据如何流向我们的任务。

关键是使用提供方 APIGradle的。 而不是直接传递值(如一个)String ou un File) 对于我们的任务,我们必须花费`Property<T>`或一些`Provider<T>`。

以下是重构计划:
  1. 声明任务条目:解析 YAML 文件的任务必须将该文件声明为输入。为此,我们使用该注解。@InputFile。

[source,kotlin] ---- @get:InputFile abstract val configFile: RegularFileProperty ----


相关文章