Add buildship support for com.diffplug.eclipse.apt
I'm trying to make Eclipse run the annotation processors defined in my Gradle script. I ended up here, since it seems like the only maintained (although non-official) way to do this at the moment is to apply this plugin. In particular, I'm trying to apply the Hibernate JPA Model Generation annotation processor.
This is what I'm doing in my build.gradle (please note Spring Boot version is 2.3.1.RELEASE and that I'm using Gradle 6.5):
plugins {
id 'application'
id 'groovy'
id 'eclipse'
id 'com.diffplug.eclipse.apt' version '3.23.0'
id 'org.springframework.boot' version "$springBootVersion"
}
// I want my generated sources to be put into src/generated/java
ext.generatedOutputDir = 'src/generated/java'
// set the output for annotation processors in Java and Groovy compilation tasks
tasks.withType(JavaCompile) {
options.compilerArgs << '-implicit:class'
options.generatedSourceOutputDirectory = file(generatedOutputDir)
}
tasks.withType(GroovyCompile) {
options.compilerArgs << '-implicit:class'
groovyOptions.javaAnnotationProcessing = true
options.generatedSourceOutputDirectory = file(generatedOutputDir)
}
// add generated sources to to the final compilation set
sourceSets.main.java.srcDir generatedOutputDir
dependencies {
// add the annotation processor to create the metamodel classes
annotationProcessor platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")
annotationProcessor "org.hibernate:hibernate-jpamodelgen"
compileOnly "org.hibernate:hibernate-jpamodelgen" // is this needed? Probably no?
}
eclipse {
classpath {
file.whenMerged {
// disable warnings in Eclipse for the generated source folder
entries.find { it.path == generatedOutputDir }.entryAttributes['ignore_optional_problems'] = 'true'
}
}
}
With this in place, generated sources are correctly created by Gradle (when I run the jar task, for instance), but nothing happens at all within Eclipse. Doing a "Refresh Gradle project" doesn't do anything yet.
Once I manually run the eclipseJdtApt task, then the file .settings/org.eclipse.jdt.apt.core.prefs is created, with the following contents:
org.eclipse.jdt.apt.aptEnabled=true
org.eclipse.jdt.apt.genTestSrcDir=.apt_generated_tests
org.eclipse.jdt.apt.reconcileEnabled=true
org.eclipse.jdt.apt.genSrcDir=.apt_generated
eclipse.preferences.version=1
Also, an empty .apt_generated folder is created in my Eclipse project root.
Once I manually run eclipseFactorypath, the file .factorypath is created with the following contents, which seem to be correct:
<?xml version="1.0" encoding="UTF-8"?>
<factorypath>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/org.hibernate/hibernate-jpamodelgen/5.4.17.Final/7a06cd357c40d787564e9bf80e7e393b52e1a0b5/hibernate-jpamodelgen-5.4.17.Final.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/jaxb-runtime/2.3.3/c46b68a6e3a2d84ba4eb14c6a8a1a9a7be4048bc/jaxb-runtime-2.3.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/org.glassfish.jaxb/txw2/2.3.3/12f70b0ea4fc1ad45315e842f63f7c9a46f46530/txw2-2.3.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/com.sun.activation/jakarta.activation/1.2.2/74548703f9851017ce2f556066659438019e7eb5/jakarta.activation-1.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/jakarta.xml.bind/jakarta.xml.bind-api/2.3.3/48e3b9cfc10752fba3521d6511f4165bea951801/jakarta.xml.bind-api-2.3.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/javax.xml.bind/jaxb-api/2.3.1/8531ad5ac454cc2deb9d4d32c40c4d7451939b5d/jaxb-api-2.3.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/javax.activation/javax.activation-api/1.2.0/85262acf3ca9816f9537ca47d5adeabaead7cb16/javax.activation-api-1.2.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/org.jboss.logging/jboss-logging/3.4.1.Final/40fd4d696c55793e996d1ff3c475833f836c2498/jboss-logging-3.4.1.Final.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/home/mauro/.gradle/caches/modules-2/files-2.1/com.sun.istack/istack-commons-runtime/3.0.11/4293b5f4e4e89d598f62bb2ba73b32132e7c3a27/istack-commons-runtime-3.0.11.jar" enabled="true" runInBatchMode="false"/>
</factorypath>
So:
- nothing happens out of the box or at Gradle project synchronisation, I have to run those two tasks manually; not a big deal, I could make Buildship automatically run these tasks on project synchronisation, however I could not find any documentation saying I need to run them manually
- the directory where I want to put generated sources is ignored
- still no source is generated, even if I change a Java/Groovy source file or if I issue a "Clean Project" command
What am I missing? The documentation at https://github.com/diffplug/goomph drives to https://javadoc.io/static/com.diffplug.gradle/goomph/3.23.0/com/diffplug/gradle/eclipse/apt/AptEclipsePlugin.html, which simply says "no configuration is required".
Maybe a better phrase than "no configuration is required" would be "no configuration is available".
At DiffPlug, we use this plugin with just apply plugin: 'eclipse', we don't use buildship. We'd be happy to merge and maintain support for buildship, but it's not on our todo list, and I don't have any insight into why it isn't working already.
I can say that when we migrated from the original apt implementation, we didn't remove buildship support, we kept everything the same except we fixed deprecation warnings.
So, after many trials and errors and looking at the original plugin documentation at https://github.com/tbroyer/gradle-apt-plugin, I ended with:
- yes, running those two tasks manually is required; otherwise, with Buildship, the following can be added to the build script to automatically run them on project synchronisation:
eclipse {
// re-generate APT configuration on Gradle project synchronisation
synchronizationTasks eclipseJdtApt, eclipseFactorypath
}
- to set the directory where the generated sources are to be located, two plugin configuration settings are needed (so, it isn't totally true that "no configuration is available" - indeed there are quite a lot of options available as described at https://github.com/tbroyer/gradle-apt-plugin#eclipse):
eclipse {
jdt {
apt {
genSrcDir = file(generatedOutputDir)
genTestSrcDir = file('src/generatedTest/java')
}
}
- the sources started to be automatically generated after I manually unchecked and rechecked "Enable annotation processing" flag in my project properties: before I did this, I tried everything (cleaning the project, changing involved source files, closing and reopening the project, restarting the IDE multiple times) but nothing worked... now it seems to work even across restarts... this is really a mistery...
Perhaps to improve the first point you may investigate whether setting an appropriate dependency for eclipseJdtApt and eclipseFactorypath tasks could make them to be executed whenever one of the standard Eclipse tasks is also executed, in this way you'll get them run whenever Buildship project synchronisation occurs for free. Otherwise adding those tasks to the above synchronizationTasks would do the trick
To improve the second point I think the genSrcDir should be set by default to the value of compileJava.options.generatedSourceOutputDirectory. No idea of what genTestSrcDir should be by default, probably some other value automatically derived from the first one?
Happy to merge and release a PR, our contribution guide can show how to test it.
As a happy user of com.diffplug.eclipse.apt (thank you so much for taking up its maintenance), I have been working the last few days on sprucing up a build for a project that generates code and uses QueryDSL. After upgrading it to Gradle 6.8.3 and adopting the native querydsl annotation processors, I was stumped when importing the project into Eclipse (2020-12) using Buildship (3.1.5.v20210113-0929). Eclipse would not run the annotation processors (likely because it didn't see them). The notes in this ticket were quite useful. I believe I may have an explanation for the mystery alluded to in an earlier comment.
The reason that disabling annotation processing and re-enabling it (after allowing a full project build to occur) works is that these actions result in the creation of .settings/org.eclipse.jdt.core.prefs with org.eclipse.jdt.core.compiler.processAnnotations set to true.
This is confusing because the property page in Eclipse for Annotation Processing already indicates that annotation processing is enabled. I believe this is derived from org.eclipse.jdt.apt.aptEnabled=true in .settings/org.eclipse.jdt.apt.core.prefs (yes, two properties contributing to one configuration dialog and one configuration dialog producing content for two preference files).
I was curious to see what com.diffplug.eclipse.apt did and a quick look at src/main/java/com/diffplug/gradle/eclipse/apt/AptEclipsePlugin.java tells that I should have seen .settings/org.eclipse.jdt.core.prefs. I am not sure why the file got written only after what I did next.
I added eclipseJdt to the list of synchronizationTasks in the eclipse configuration closure in build.gradle. Things looked like this now (minus some other stuff that makes sense only to the project I was working with)
eclipse {
synchronizationTasks eclipseJdtApt, eclipseJdt, eclipseFactorypath
}
I then imported the project afresh into Eclipse and once Buildship had set it up for me, I initiated a clean build (I have Build Automatically turned off in my workspace). I had the Error Log view open and saw messages logged by org.eclipse.jdt.apt.pluggable.core (they also go to .metadata/.log in the workspace). I also saw classes generated by QueryDSL going to .apt_generated. The project built fine. No other trickery necessary. (.settings/org.eclipse.jdt.core.prefs also appeared now with org.eclipse.jdt.core.compiler.processAnnotations=true along with several other settings derived from workspace defaults and the project settings)
I think not assigning eclipse.jdt.apt.genSrcDir the default value of compileJava.options.generatedSourceOutputDirectory would be more in keeping with the intent of keeping Eclipse's output folders (like bin) separate from Gradle's output folders. There's an old post in the Gradle discussion forums that talks about a similar deliberate decision taken for Buildship.
You explained this very clearly, but it's a complex issue and I don't have time to dig in. I'm happy to merge and release a PR, and with or without a PR thanks for sharing these details.
Just an additional note: today I discovered that eclipseFactorypath only goes incrementally and does not rewrite the .factorypath file from scratch. So, in my case, a Spring Boot upgrade caused the old entries in .factorypath to remain there and produce error markers on my project.
So, the right approach to enable proper annotation processing synchronization with Buildship is to add this in build.gradle:
// re-generate annotation processor configuration on Gradle project synchronisation
synchronizationTasks eclipseJdtApt, cleanEclipseFactorypath, eclipseFactorypath
Please note the addition of cleanEclipseFactorypath before eclipseFactorypath.
@smoothreggae I read your interesting analysis, thank you. Could you just elaborate more on why you added the eclipseJdt task to synchronizationTasks? I'm not sure I've understood it correctly: did it fix the problem of having to disable and re-enable the "Enable annotation processing" flag in project properties for you?
Indeed, contrary to eclipseJdtApt task, which is actually contributed by this plugin, I believe eclipseJdt task is contributed by the official Gradle Eclipse plugin (in fact, I have this task in another project which does NOT use this plugin), so it sounds strange to me that Buildship does not take care of this out-of-the-box, if it's really needed.
@nedtwigg I understand your reluctance. However, please note that I think anybody using Gradle and Eclipse also uses Buildship (I can't think of any good reason not to do that, indeed...), so IMHO having proper Buildship support would be really important. Unfortunately my experience with Gradle plugin development is really really rusty (I just did something many years ago), so I'm not confident enough of providing the necessary plumbing, I hope this information will be helpful for you sooner or later.
I think these issues may be solved in EquoIDE, and the way that it integrates buildship. If you have any troubles, open an issue there and I bet we can figure it out. https://github.com/equodev/equo-ide/tree/main/plugin-gradle