Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Gradle annotation processing #2319

Merged
merged 4 commits into from
Nov 25, 2022
Merged

Conversation

jdneo
Copy link
Contributor

@jdneo jdneo commented Nov 11, 2022

  • Add a new preference 'java.import.gradle.annotationProcessing.enabled' to specify whether the Gradle annotation processing will be enabled or not.
  • Create a init script to get the annotation processing configuration from Gradle. And delegate the processing to JDT APT.
  • Move the init script related utilities from GradleProjectImporter to GradleUtils.

Requires: redhat-developer/vscode-java#2793

fix redhat-developer/vscode-java#1660
fix redhat-developer/vscode-java#1039

Sample project for verification: https://github.com/mapstruct/mapstruct-examples/tree/main/mapstruct-on-gradle

Signed-off-by: Sheng Chen sheche@microsoft.com

- Add a new preference 'java.import.gradle.annotationProcessing.enabled'
  to specify whether the Gradle annotation processing will be enabled or
  not.
- Create a init script tp get the annotation processing configuration from
  Gradle. And delegate the processing to JDT APT.
- Move the init script related utilities from GradleProjectImporter to
  GradleUtils.

Signed-off-by: Sheng Chen <sheche@microsoft.com>
@jdneo
Copy link
Contributor Author

jdneo commented Nov 11, 2022

@donat This is the PR to support annotation processing for Gradle. It uses the init script to get the annotation processing configurations and sync those configs to JDT APT. I'm open to move this into Buildship. Meanwhile, there are some limitations for the current implementation:

  1. Since the Gradle Custom Model API buildAll() can only pass the root project, that means I cannot synchronize on a single sub project. I guess this could be improved if Gradle can provide a dedicated model(i.e. a model representing the compile options) for this purpose, and then I can get that model from a GradleBuild which is specific to a sub project?
  2. Looks like JDT cannot specify different options for main and test. The current implementation will ignore the argument for the test source set.
  3. Cannot work together with Buildship's sync.auto feature. Now I need to explicitly sync ap configurations when user wants to reload project.

Signed-off-by: Sheng Chen <sheche@microsoft.com>
@jdneo jdneo marked this pull request as ready for review November 11, 2022 07:43
@jdneo
Copy link
Contributor Author

jdneo commented Nov 11, 2022

@snjeza
Copy link
Contributor

snjeza commented Nov 11, 2022

test this please

org.eclipse.jdt.ls.core/gradle/apt/init.gradle Outdated Show resolved Hide resolved
Signed-off-by: Sheng Chen <sheche@microsoft.com>
@rgrunber
Copy link
Contributor

Change looks good to me, but maybe we can wait for Graeme to confirm against Micronaut.

@jdneo
Copy link
Contributor Author

jdneo commented Nov 17, 2022

I tried micronaut with this sample project: https://github.com/micronaut-projects/micronaut-examples/tree/master/hello-world-java, and roughly compared the results with m2e-apt and command line build tool.

Before import the project, some modifications are needed.

  1. remove the plugin id "net.ltgt.apt-eclipse" version "0.21" in build.gradle. The current implementation will do nothing if users project already configured this plugin to support eclipse apt.
  2. Update micronautVersion=1.2.0 in gradle.properties. This is to make sure the processor version is the same as that defined in pom.xml. Because I found the compiled classes are different between different versions.

Result

The compiled class files are same between gradle(with this PR) and maven(with m2e) - at least for the file names and numbers.

This PR:
image

M2E
image

But the compiled test output is different from build tools:
image

There is an error observed in the log, using m2e-apt and this PR are the same

!ENTRY org.eclipse.jdt.apt.pluggable.core 4 1 2022-11-17 10:46:48.472
!MESSAGE Exception thrown by Java annotation processor io.micronaut.annotation.processing.BeanDefinitionInjectProcessor@3f1c7bcf
!STACK 0
java.lang.Exception: java.lang.NullPointerException: Cannot invoke "org.eclipse.jdt.internal.compiler.util.SimpleLookupTable.get(Object)" because "this.directoryCache" is null
	at org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher.handleProcessor(RoundDispatcher.java:172)
	at org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher.round(RoundDispatcher.java:124)
	at org.eclipse.jdt.internal.compiler.apt.dispatch.BaseAnnotationProcessorManager.processAnnotations(BaseAnnotationProcessorManager.java:172)
	at org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeAnnotationProcessorManager.processAnnotations(IdeAnnotationProcessorManager.java:138)
	at org.eclipse.jdt.internal.compiler.Compiler.processAnnotations(Compiler.java:953)
	at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:450)
	at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:426)
	at org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.compile(AbstractImageBuilder.java:379)
	at org.eclipse.jdt.internal.core.builder.BatchImageBuilder.compile(BatchImageBuilder.java:214)
	at org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.compile(AbstractImageBuilder.java:311)
	at org.eclipse.jdt.internal.core.builder.BatchImageBuilder.build(BatchImageBuilder.java:79)
	at org.eclipse.jdt.internal.core.builder.JavaBuilder.buildAll(JavaBuilder.java:276)
	at org.eclipse.jdt.internal.core.builder.JavaBuilder.build(JavaBuilder.java:188)
	at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:1020)
	at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
	at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:247)
	at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:303)
	at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:392)
	at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
	at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:395)
	at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:506)
	at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:454)
	at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:536)
	at org.eclipse.core.internal.resources.Workspace.buildInternal(Workspace.java:524)
	at org.eclipse.core.internal.resources.Workspace.build(Workspace.java:413)
	at org.eclipse.jdt.ls.core.internal.handlers.BuildWorkspaceHandler.buildWorkspace(BuildWorkspaceHandler.java:65)
	at org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer.lambda$27(JDTLanguageServer.java:902)
	at org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer.lambda$55(JDTLanguageServer.java:1094)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(Unknown Source)
	at java.base/java.util.concurrent.CompletableFuture$Completion.exec(Unknown Source)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
	at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
Caused by: java.lang.NullPointerException: Cannot invoke "org.eclipse.jdt.internal.compiler.util.SimpleLookupTable.get(Object)" because "this.directoryCache" is null
	at org.eclipse.jdt.internal.core.builder.ClasspathMultiDirectory.directoryList(ClasspathMultiDirectory.java:78)
	at org.eclipse.jdt.internal.core.builder.ClasspathDirectory.doesFileExist(ClasspathDirectory.java:132)
	at org.eclipse.jdt.internal.core.builder.ClasspathDirectory.findClass(ClasspathDirectory.java:157)
	at org.eclipse.jdt.internal.core.builder.ClasspathLocation.findClass(ClasspathLocation.java:74)
	at org.eclipse.jdt.internal.core.builder.NameEnvironment.findClass(NameEnvironment.java:569)
	at org.eclipse.jdt.internal.core.builder.NameEnvironment.findType(NameEnvironment.java:591)
	at org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment.findType(IModuleAwareNameEnvironment.java:97)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:242)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getType(LookupEnvironment.java:1735)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getResolvedType(LookupEnvironment.java:1665)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getResolvedJavaBaseType(LookupEnvironment.java:1677)
	at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.buildTargetAnnotation(AnnotationBinding.java:142)
	at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.addStandardAnnotations(AnnotationBinding.java:83)
	at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.retrieveAnnotations(BinaryTypeBinding.java:1942)
	at org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding.getAnnotations(ReferenceBinding.java:1069)
	at org.eclipse.jdt.internal.compiler.apt.model.TypeElementImpl.getAnnotationBindings(TypeElementImpl.java:149)
	at org.eclipse.jdt.internal.compiler.apt.model.ElementImpl.getPackedAnnotationBindings(ElementImpl.java:66)
	at org.eclipse.jdt.internal.compiler.apt.model.ElementImpl.getAnnotationMirrors(ElementImpl.java:81)
	at io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder.getRepeatableNameForType(JavaAnnotationMetadataBuilder.java:102)
	at io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder.getRepeatableName(JavaAnnotationMetadataBuilder.java:96)
	at io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder.getRepeatableName(JavaAnnotationMetadataBuilder.java:48)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.buildInternal(AbstractAnnotationMetadataBuilder.java:765)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.buildDeclared(AbstractAnnotationMetadataBuilder.java:111)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.validateAnnotationValue(AbstractAnnotationMetadataBuilder.java:336)
	at io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder.readAnnotationRawValues(JavaAnnotationMetadataBuilder.java:247)
	at io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder.readAnnotationRawValues(JavaAnnotationMetadataBuilder.java:48)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.processAnnotationDefaults(AbstractAnnotationMetadataBuilder.java:658)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.processAnnotationDefaults(AbstractAnnotationMetadataBuilder.java:647)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.populateAnnotationData(AbstractAnnotationMetadataBuilder.java:508)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.buildInternal(AbstractAnnotationMetadataBuilder.java:763)
	at io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder.build(AbstractAnnotationMetadataBuilder.java:154)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor$AnnBeanElementVisitor.populateParameterData(BeanDefinitionInjectProcessor.java:1992)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor$AnnBeanElementVisitor.access$200(BeanDefinitionInjectProcessor.java:304)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor$AnnBeanElementVisitor$1.accept(BeanDefinitionInjectProcessor.java:546)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor$AnnBeanElementVisitor$1.accept(BeanDefinitionInjectProcessor.java:534)
	at io.micronaut.annotation.processing.SuperclassAwareTypeVisitor.visitDeclared(SuperclassAwareTypeVisitor.java:69)
	at org.eclipse.jdt.internal.compiler.apt.model.DeclaredTypeImpl.accept(DeclaredTypeImpl.java:117)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor$AnnBeanElementVisitor.visitIntroductionAdviceInterface(BeanDefinitionInjectProcessor.java:534)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor$AnnBeanElementVisitor.visitType(BeanDefinitionInjectProcessor.java:387)
	at org.eclipse.jdt.internal.compiler.apt.model.TypeElementImpl.accept(TypeElementImpl.java:143)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor.lambda$process$7(BeanDefinitionInjectProcessor.java:184)
	at java.base/java.lang.Iterable.forEach(Unknown Source)
	at io.micronaut.annotation.processing.BeanDefinitionInjectProcessor.process(BeanDefinitionInjectProcessor.java:179)
	at org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher.handleProcessor(RoundDispatcher.java:142)
	... 34 more

Most likely this is related with the implementation of ecj.

@jdneo
Copy link
Contributor Author

jdneo commented Nov 17, 2022

Update: The problem mentioned above could be solved once the NPE issue is fixed: eclipse-jdt/eclipse.jdt.core#545

@graemerocher
Copy link

@jdneo could you try with this example https://guides.micronaut.io/latest/creating-your-first-micronaut-app-gradle-java.zip

The referenced example is very old

@jdneo
Copy link
Contributor Author

jdneo commented Nov 22, 2022

There is one small gap with the new example. The current implementation will skip fetching the apt configurations if the user's Gradle project contains eclipseJdtApt task:

https://github.com/eclipse/eclipse.jdt.ls/blob/0287c41b69d336c7dec2a805ad1bfda7b2ce3c8b/org.eclipse.jdt.ls.core/gradle/apt/init.gradle#L61-L63

My original assumption is that if there is such plugin configured in the project, that's a hint that user will do the code generation by himself. When I first opened the sample project, it did not generate the .class files because the task is available in the sample project. (I guess it's brought by io.micronaut.application plugin)

After I remove the check, it then worked as expected:

WeChat Screenshot_20221122101147
WeChat Screenshot_20221122101226

Now I think it's ok to remove those checks to bring out-of-box gradle annotation processor support. Since we have a setting for this, user can turn it off if he really wants to do it manually.

Signed-off-by: Sheng Chen <sheche@microsoft.com>
@jdneo
Copy link
Contributor Author

jdneo commented Nov 23, 2022

test this please

@jdneo
Copy link
Contributor Author

jdneo commented Nov 23, 2022

@rgrunber @fbricon @testforstephen I think we can now merge this one.(if the name of preference key looks good). For other ecj related issues, we can address them separately. Let me know if there are any concerns :)

@testforstephen
Copy link
Contributor

For me, it was good to merge.

Copy link

@graemerocher graemerocher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I asked @melix to take a look from a Gradle perspective and he picked up on a point that annotation processors on the test annotation processor classpath are not handled correctly.

List<String> compilerArgs = new LinkedList<>()
// Compiler args for test are ignored.
// Due to JDT does not differentiate the args between main and test.
collectApConfiguration(project, "compileJava", processors, compilerArgs)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be different processors present on compileJava vs compileTestJava, how does this PR address that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason that test annotation processors are ignored is that the APT support on JDT side cannot differentiate the processors from different source sets. My approach here is to ignore test annotation processors.

Another approach we can take is to mix all the processors from main and test together, but personally I think this is not ideal neither.

This is the case that we cannot handle gracefully now until the JDT can support that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed an issue to track the testAnnotationProcessor support: #2345, which we can address separately from this pull request.

@jdneo jdneo merged commit 681c5f7 into eclipse-jdtls:master Nov 25, 2022
@jdneo
Copy link
Contributor Author

jdneo commented Nov 25, 2022

Hi all, I merged the PR, and we can address the issue about test annotation processor separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants