The Java Plugin (original) (raw)
Starting with Gradle 4.7, the incremental compiler also supports incremental annotation processing. All annotation processors need to opt in to this feature, otherwise they will trigger a full recompilation.
As a user you can see which annotation processors are triggering full recompilations in the --info
log. Incremental annotation processing will be deactivated if a custom executable
or javaHome
is configured on the compile task.
Making an annotation processor incremental
Please first have a look at incremental Java compilation, as incremental annotation processing builds on top of it.
Gradle supports incremental compilation for two common categories of annotation processors: "isolating" and "aggregating". Please consult the information below to decide which category fits your processor.
You can then register your processor for incremental compilation using a file in the processor’s META-INF directory. The format is one line per processor, with the fully qualified name of the processor class and its case-insensitive category separated by a comma.
Example: Registering incremental annotation processors
processor/src/main/resources/META-INF/gradle/incremental.annotation.processors
org.gradle.EntityProcessor,isolating org.gradle.ServiceRegistryProcessor,dynamic
If your processor can only decide at runtime whether it is incremental or not, you can declare it as "dynamic" in the META-INF descriptor and return its true type at runtime using the Processor#getSupportedOptions() method.
Example: Registering incremental annotation processors dynamically
processor/src/main/java/org/gradle/ServiceRegistryProcessor.java
@Override
public Set<String> getSupportedOptions() {
return Collections.singleton("org.gradle.annotation.processing.aggregating");
}
Both categories have the following limitations:
- They can only read
CLASS
orRUNTIME
retention annotations. - They can only read parameter names if the user passes the
-parameters
compiler argument. - They must generate their files using the Filer API. Writing files any other way will result in silent failures later on, as these files won’t be cleaned up correctly. If your processor does this, it cannot be incremental.
- They must not depend on compiler-specific APIs like
com.sun.source.util.Trees
. Gradle wraps the processing APIs, so attempts to cast to compiler-specific types will fail. If your processor does this, it cannot be incremental, unless you have some fallback mechanism. - If they use Filer#createResource, the
location
argument must be one of these values from StandardLocation:CLASS_OUTPUT
,SOURCE_OUTPUT
, orNATIVE_HEADER_OUTPUT
. Any other argument will disable incremental processing.
"Isolating" annotation processors
The fastest category, these look at each annotated element in isolation, creating generated files or validation messages for it. For instance an EntityProcessor
could create a <TypeName>Repository
for each type annotated with @Entity
.
Example: An isolated annotation processor
processor/src/main/java/org/gradle/EntityProcessor.java
Set<? extends Element> entities = roundEnv.getElementsAnnotatedWith(entityAnnotation);
for (Element entity : entities) {
createRepository((TypeElement) entity);
}
"Isolating" processors have the following additional limitations:
- They must make all decisions (code generation, validation messages) for an annotated type based on information reachable from its AST. This means you can analyze the types' super-class, method return types, annotations etc., even transitively. But you cannot make decisions based on unrelated elements in the RoundEnvironment. Doing so will result in silent failures because too few files will be recompiled later. If your processor needs to make decisions based on a combination of otherwise unrelated elements, mark it as "aggregating" instead.
- They must provide exactly one originating element for each file generated with the
Filer
API. If zero or many originating elements are provided, Gradle will recompile all source files.
When a source file is recompiled, Gradle will recompile all files generated from it. When a source file is deleted, the files generated from it are deleted.
"Aggregating" annotation processors
These can aggregate several source files into one or more output files or validation messages. For instance, a ServiceRegistryProcessor
could create a single ServiceRegistry
with one method for each type annotated with @Service
.
Example: An aggregating annotation processor
processor/src/main/java/org/gradle/ServiceRegistryProcessor.java
JavaFileObject serviceRegistry = filer.createSourceFile("ServiceRegistry");
Writer writer = serviceRegistry.openWriter();
writer.write("public class ServiceRegistry {");
for (Element service : roundEnv.getElementsAnnotatedWith(serviceAnnotation)) {
addServiceCreationMethod(writer, (TypeElement) service);
}
writer.write("}");
writer.close();
Gradle will always reprocess (but not recompile) all annotated files that the processor was registered for. Gradle will always recompile any files the processor generates.