如何确保注释处理器始终应用于所有带注释的元素?

How do sure that the annotation processor is always applied to all annotated elements?

我编写了自定义注释处理器,它收集所有带注释的 classes,按字典顺序组织它们并为每个带注释的 class 生成一个新的 class。

在 Intellij Idea 中,当项目被增量构建时,并非所有 项目中注释的 classes 都传递到我的注释处理器,但是只有那些 modified/added。 这违反了排序逻辑。

如何确保注释处理器始终应用于每个构建中的所有注释元素?

我也找到了这篇文章,但似乎它只适用于 Gradle:https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing

是否可以让注释处理器为任何构建工具聚合增量?

是否可以让这样的注解处理器隔离增量?

我的注释处理器的源代码:https://github.com/ElegantNetworking/ElegantNetworkingAnnotationProcessor

你问错了问题。以下是导致您提出错误问题的思考过程:

  1. 嗯,我的 AP 在编译 运行 时只看到了所有源代码的一小部分,这很奇怪!这会导致错误,我想修复这些错误。
  2. 哦,等等,我明白了,这是因为增量编译。
  3. 我知道!我将禁用增量编译!
  4. 嗯,那我该怎么做呢?我最好问一下。

我先给出一个直接的答案,但你不会喜欢的:你基本上不会。每次系统要编译时都重新编译整个代码库,效率极低;没有人喜欢源文件中的一个简单更改导致必须等待 250 秒才能看到它的效果。您很快就会责怪这些工具(无论是 gradle 还是 intellij)对您的工作时间充满敌意。这些工具知道这一点,并且不会(轻易地)允许这种无辜的行为(例如包括一些注释处理器)使工具边界无法使用。

你也不想知道如何 'fix' 这个,因为,好吧,我只是说了 'borderline unusable'。您肯定不希望更改的周转时间从半秒变为 5 分钟。

虽然 一个很好的解决方案 - 但前提是您后退几步。

关于增量编译的事情是:没有被编译的东西(因为它们没有改变/不需要)?他们 WERE 编译较早。您需要做的就是按照以下步骤操作:就像编译源文件会产生 'persistent' 的结果一样,这意味着您不需要重做,直到出现表明您需要重新应用该过程的某些情况,你需要对你的 AP 做同样的事情:如果你的 AP 处理了一些源文件,那需要留下持久的影响;这种效果对于所有未来 运行s 没有 拥有原始源代码树的好处需要足够,至少在所述源代码树被更改之前是这样。

这比看起来容易,因为您有文件管理器。

我将以注释处理器为例进行描述:

此处理器将扫描所有用 @Provides(com.pkg.Foo.class) 注释的类型,检查如此注释的类型是否实现或扩展 Foo,然后创建一个文件 META-INF/services/com.pkg.Foo,列出该类型在那里。这准确地描述了 SPI 处理器的工作原理:例如,google's auto-service processor 就是这样做的(周围有很多这样的项目)。

这个过程对于完整编译来说是微不足道的 运行:AP 可以制作一个 Map<String, List<String>> 来映射,例如"com.pkg.Foo"["com.company.FooImpl1", "com.company.FooImpl2"],在轮次发生和访问源文件时填充它,然后在结束轮次期间,以服务文件的形式转储这些地图。 AP 就像 2 页的代码,几乎微不足道,但非常有用。

问题是,当增量编译发生时,该模型实际上并没有工作:在增量编译 运行 中,仅发现 FooImpl1,因此映射仅映射 FooFooImpl1,当需要将文件转出磁盘时,FooImpl2 就从您的服务文件中消失了,即使 FooImpl2 class 仍然存在 - 它只是不在增量编译 运行 因为它没有改变。

虽然解决方案很简单:您有一个文件管理器!

您需要先阅读 服务文件,而不是将这些构建的地图中的每一个都转储到服务文件中并收工。如果不存在,很简单,只需返回 'dump the list out' 代码即可。但是,如果 ,请阅读其中的每个条目,向文件管理器询问这些 class。如果文件管理器找不到其中之一,则从服务文件中删除该行。可以的话就留着吧

好的,所以现在我们的 AP 从大约 2 页变成了 3 页,但是现在完全可以跟随增量编译。它可以区分某人删除 FooImpl2 并进行完全重新编译(这将导致服务文件仅包含 FooImpl1),以及某人首先进行完整 运行(导致两者1 和 2 在服务文件中),然后仅更改 FooImpl1.java 并进行增量编译 运行:

class MyProcessor extends javax.annotation.processing.AbstractProcessor {
  @Override public void init(ProcessingEnvironment env) {
    // you need these:
    Filer filer = env.getFiler();
    Elements elementUtils = processingEnv.getElementUtils();
  }
}

使用文件管理器,您可以:

  FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT,
  "", pathToServicesFile);

然后你可以从那里读取那个文件(如果它在那里),检查哪个 classes 已经在那个服务文件中:在你的增量编译 运行 中,这会给你com.company.FooImpl1com.company.FooImpl2。然后您可以检查这些类型(仍然)是否存在:

elements.getTypeElement("com.company.FooImpl1")

如果 returns null,它不再存在,您可以将其从您的服务文件中删除。如果是这样,请保留它 - 除非你在巡视时点击了那个文件,结果发现它不再被注释了。关键是:如果你在你的轮次中根本没有击中那个文件,那意味着它被排除在外,因为增量编译过程没有考虑它改变,因此,最后一个已知状态(即 FooImpl1实现 Foo 并用 @Provides(Foo.class) 注释,因此它在已经存在的服务文件中的原因)仍然是正确的,因此,采取相应的行动。

如果注释处理器的 output/effect 不包含任何可能用于在以后的增量编译中找出它的内容 运行,那么 制作这样一个文件:创建一个文件,'tracks' 你需要知道的状态。