如何确保注释处理器始终应用于所有带注释的元素?
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
你问错了问题。以下是导致您提出错误问题的思考过程:
- 嗯,我的 AP 在编译 运行 时只看到了所有源代码的一小部分,这很奇怪!这会导致错误,我想修复这些错误。
- 哦,等等,我明白了,这是因为增量编译。
- 我知道!我将禁用增量编译!
- 嗯,那我该怎么做呢?我最好问一下。
我先给出一个直接的答案,但你不会喜欢的:你基本上不会。每次系统要编译时都重新编译整个代码库,效率极低;没有人喜欢源文件中的一个简单更改导致必须等待 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
,因此映射仅映射 Foo
到 FooImpl1
,当需要将文件转出磁盘时,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.FooImpl1
和 com.company.FooImpl2
。然后您可以检查这些类型(仍然)是否存在:
elements.getTypeElement("com.company.FooImpl1")
如果 returns null
,它不再存在,您可以将其从您的服务文件中删除。如果是这样,请保留它 - 除非你在巡视时点击了那个文件,结果发现它不再被注释了。关键是:如果你在你的轮次中根本没有击中那个文件,那意味着它被排除在外,因为增量编译过程没有考虑它改变,因此,最后一个已知状态(即 FooImpl1
实现 Foo
并用 @Provides(Foo.class)
注释,因此它在已经存在的服务文件中的原因)仍然是正确的,因此,采取相应的行动。
如果注释处理器的 output/effect 不包含任何可能用于在以后的增量编译中找出它的内容 运行,那么 制作这样一个文件:创建一个文件,'tracks' 你需要知道的状态。
我编写了自定义注释处理器,它收集所有带注释的 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
你问错了问题。以下是导致您提出错误问题的思考过程:
- 嗯,我的 AP 在编译 运行 时只看到了所有源代码的一小部分,这很奇怪!这会导致错误,我想修复这些错误。
- 哦,等等,我明白了,这是因为增量编译。
- 我知道!我将禁用增量编译!
- 嗯,那我该怎么做呢?我最好问一下。
我先给出一个直接的答案,但你不会喜欢的:你基本上不会。每次系统要编译时都重新编译整个代码库,效率极低;没有人喜欢源文件中的一个简单更改导致必须等待 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
,因此映射仅映射 Foo
到 FooImpl1
,当需要将文件转出磁盘时,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.FooImpl1
和 com.company.FooImpl2
。然后您可以检查这些类型(仍然)是否存在:
elements.getTypeElement("com.company.FooImpl1")
如果 returns null
,它不再存在,您可以将其从您的服务文件中删除。如果是这样,请保留它 - 除非你在巡视时点击了那个文件,结果发现它不再被注释了。关键是:如果你在你的轮次中根本没有击中那个文件,那意味着它被排除在外,因为增量编译过程没有考虑它改变,因此,最后一个已知状态(即 FooImpl1
实现 Foo
并用 @Provides(Foo.class)
注释,因此它在已经存在的服务文件中的原因)仍然是正确的,因此,采取相应的行动。
如果注释处理器的 output/effect 不包含任何可能用于在以后的增量编译中找出它的内容 运行,那么 制作这样一个文件:创建一个文件,'tracks' 你需要知道的状态。