【问题标题】:How to configure Gradle's incremental build with annotation processor如何使用注释处理器配置 Gradle 的增量构建
【发布时间】:2023-09-01 08:59:01
【问题描述】:

我想在构建过程中使用 QueryDSL 注释处理器。我如何在每次更改任何类时摆脱不必要的注释处理器编译和运行?我希望 QueryDSL 仅在更改某些相关类时生成 Q-* 类。

这种始终运行的注解处理器对我们的构建过程时间有负面影响,如果必须运行注解处理器,增量构建似乎不起作用。

谢谢。

【问题讨论】:

  • 所以它与我的简单注释处理器一起工作,在一个硬编码目录中观看文件。每次运行加速约 2-3 倍!希望 Gradle 能够为所有注释处理器解决这个问题 :) 谢谢。
  • 很高兴它对你有用。但是我不认为 Gradle 可以普遍解决它,因为它永远无法知道 AP 需要哪些文件作为其输入。

标签: java gradle querydsl incremental-build annotation-processor


【解决方案1】:

Gradle 无法知道注释处理器使用哪些文件作为输入,因此它必须在每次监视目录(src)中的某些内容发生更改时触发完全重新编译。

但是,您可以轻松地告诉 Gradle 哪些文件应该只触发注释处理。更改其他文件不会触发注释处理器的使用,并且 gradle 可以使用其所有功能(例如增量构建)。

我还添加了“强制”任务 buildWithAP 调用注释处理器,无论提示(启发式)函数结果如何。

我的解决方案:

ext.isTask = { name -> return project.gradle.startParameter.taskNames.contains(name) }

/**
 * Heuristic function allowing to build process guess if annotation processor run is necessary
 * Annotation processors will not be called during build task if this function returns FALSE
 */
ext.isApInvalidated = { -> return hasAnyFileRelatedToApChanged() }

dependencies {
  if (isTask("buildWithAP") || isApInvalidated()) {
    println "Going to run annotation processors ..."
    apt "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
  ...
  } else {
    // just add generated classes to the classpath
    // must be in else branch or multiple AP calls will collide!
  sourceSets.main.java.srcDirs += projectDir.absolutePath + "/build/generated/apt"
  } 

}

task buildWithAP (dependsOn: build) {}

您可以使用任何您想要的注释处理器,例如您自己的,而不仅仅是 QueryDSL。

希望我的观点是明确的。

【讨论】:

  • 不幸的是,它比这更复杂,但 Gradle 很快就会发布一个解决方法。它已在今年的 Gradle 峰会上宣布。您的解决方案不是最理想的,因为它会在需要注释处理时立即触发完整构建。在这种情况下,有价值的是增量注释处理。此外,确定是否应该重新编译使用注释的文件的启发式方法过于简单,一些 AP 可能具有高级处理,并且可能很难识别这些文件。更不用说在大多数情况下您需要对所有文件进行 grep 注释..
  • 即使您能够解决所有这些问题,您仍然需要对依赖项进行完整的依赖分析,以便如果触发 AP 的文件的传递闭包内的文件之一被更改,它也会使文件本身无效(就像编译器一样)。
  • 当然这不是最终解决方案,但它也适用于我们的自定义 AP 和 QueryDSL 的解决方案。我的 hasAnyFileRelatedToApChanged() 只是基于文件更改时间戳比较。如果 AP 使用的文件确实发生了变化,则会触发带有 AP 运行的完整构建。但是它比每次都触发它要好得多;)我期待官方解决方案,但目前这是我想出的最好的。
  • 例如,我们遵循在实体文件上有后缀 *Entity 的常见做法。这些文件在大多数(或全部)情况下是您只需要注意更改的那些。如果发生变化,触发QueryDSL 的AP。如果没有,请不要理会它(=快)。所以对于querydsl,我们看:fileTree(dir: 'src', include: '**/*Entity.java')
  • 它可以工作,但这在很大程度上取决于您的 AP 的性质。如果您的 AP 不需要除包含注释的文件之外的任何其他文件来执行其工作(即生成代码),那么是的,您肯定处于事情更简单的情况下,您的解决方法可能会延长您的构建时间。
【解决方案2】:

AFAIK 这目前是不可能的:见blog post - 部分“使用注释处理器进行增量编译”

...使用注释处理器,Gradle 不知道它们将生成哪些文件。它也不知道在哪里以及基于什么条件。因此,如果注释处理器正在使用中,Grade 会禁用 Java 增量编译器

Gradle 相关问题:Make incremental compile efficient in the presence of Annotation Processors #1320

博客文章还提到了一种可能的解决方法:

但是,可以将其影响限制在真正使用注释处理器的类集。简而言之,您可以使用不同的编译任务声明一个不同的源集,该任务将使用注释处理器,而其他编译任务则无需任何类型的注释处理

但是,这似乎是一项相当大的工作,所以我还没有使用它。

【讨论】:

  • 谢谢,我也找到了那篇博文。遗憾的是,Gradle 没有通知开发人员跳过增量处理,因为注释处理器有效。但是我想出了如何解决它。看我的回答。
【解决方案3】:

问题似乎已经解决了! https://issuetracker.google.com/issues/37079915

【讨论】: