javagradleannotation-processing

gradle use project as its own annotation processor


I'm making a library that includes an annotation processor

.
├── lib
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── demo
│       │   │       ├── MyAnnotation.java
│       │   │       └── MyAnnotationProcessor.java
│       │   └── resources
│       └── test
│           ├── java
│           │   └── demo
│           │       └── MyAnnotationProcessorTest.java
│           └── resources
└── settings.gradle

I would like to use @MyAnnotation in MyAnnotationProcessorTest.java

But it doesn't look like gradle is processing that annotation at all when I build nor test this project.

For the sake of debugging, the annotation files just look like

package demo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.MODULE, ElementType.PACKAGE, ElementType.TYPE_USE, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE, ElementType.TYPE_PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.RECORD_COMPONENT})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation { }
package demo;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;

import com.google.auto.service.AutoService;

@SupportedAnnotationTypes("demo.MyAnnotation")
@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ">>>>> process called <<<<<");
        return true;
    }
}

and the test file just sprays a lot of @MyAnnotation everywhere.

My build.gradle is

plugins {
    id 'java-library'
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'

    implementation 'com.google.auto.service:auto-service:1.1.1'
    annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
}

// Apply a specific Java toolchain to ease working on different environments.
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

But when I gradle build or gradle test, there is no output from my annotation processor.

I made a second project that specifies dependencies { annotationProcessor 'my-annotation-processor-project' } to test that the annotation processor works, and when I build that project, the annotation processor's messages are logged. I would like them to also be logged when the annotation is used in the annotation processor's project itself.

How can I test and use a compile-time annotation processor within the same project that defines it?


Solution

  • I've found a solution that works for me. It feels a bit janky, not sure if this would be recommended, so I'm also looking forward to better answers.

    I made a second gradle "module" that does the testing

    .
    ├── lib
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           └── java
    │               └── demo
    │                   ├── MyAnnotation.java
    │                   └── MyAnnotationProcessor.java
    ├── test
    │   ├── build.gradle
    │   └── src
    │       └── test
    │           └── java
    │               └── demo
    │                   └── MyAnnotationProcessorTest.java
    └── settings.gradle
    

    settings.gradle has to have include('test') added (under where you probably already have include('lib')

    Then lib/build.gradle can have all the testing removed:

    lib/build.gradle

    plugins {
        id 'java-library'
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation 'com.google.auto.service:auto-service:1.1.1'
        annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
    }
    
    // Apply a specific Java toolchain to ease working on different environments.
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    

    and test/build.gradle should be set up for testing

    test/build.gradle

    plugins {
        id 'java'
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
    
        testImplementation project(':lib')
        testAnnotationProcessor project(':lib')
    }
    
    // Apply a specific Java toolchain to ease working on different environments.
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    
    tasks.named('test') {
        // Use JUnit Platform for unit tests.
        useJUnitPlatform()
    }
    

    I don't think any of the ./test module is going to get included in the ./builds/libs/demo.jar build result, but I'm not sure.