Bug 280542 - Annotation processors in project classpath not discovered
Summary: Annotation processors in project classpath not discovered
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: APT (show other bugs)
Version: 3.5   Edit
Hardware: PC Linux
: P3 enhancement with 13 votes (vote)
Target Milestone: ---   Edit
Assignee: Generic inbox for the JDT-APT component CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 286074 339957 (view as bug list)
Depends on:
Blocks:
 
Reported: 2009-06-16 19:50 EDT by Jesse Glick CLA
Modified: 2016-05-23 17:37 EDT (History)
18 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jesse Glick CLA 2009-06-16 19:50:01 EDT
When a classpath entry for an Eclipse project - whether a JAR or another project - contains META-INF/services/javax.annotation.processing.Processor entries listing annotation processors, these are ignored by default, even if the project is configured to perform annotation processing.

The user is forced to explicitly list every JAR which might contain annotation processors. Since you apparently need to explicitly enable processing _and_ list all processor JARs for _every_ project in the workspace (there is no Annotation Processing default preferences page in workspace settings), this could be quite a setup and maintenance burden for large multiproject systems. Reference page:

http://help.eclipse.org/ganymede/index.jsp?topic=/org.eclipse.jdt.doc.isv/guide/jdt_apt_getting_started.htm

This is related to bug #259230, though the symptom there is a bit different: this is about automatic discovery of processors when no processorpath is explicitly set, whereas that bug seems to discuss visibility of the classpath from the processorpath.


For an example of the pain, look at

http://code.google.com/p/spi/wiki/GettingStarted

where the instructions for Eclipse users are detailed, whereas users of javac-based tools need do nothing special.

As another example, people using Eclipse to develop Hudson plugins find that @Extension registrations (based on sezpoz.dev.java.net) work fine when you build the plugin using Maven, but the plugin does not work when Eclipse's builder is used - the extension is silently ignored.

There are other examples of libraries which ship both an annotation and a matching processor which expect the processor to be invoked automatically. Without the processor, the annotation is often useless. In other cases, the processor adds critical compile-time error checking.

Also, it is apparently impossible to list other projects in the workspace in the processorpath - you would have to build JARs from them and include those JARs, meaning longer round-trip time if you are testing a processor and some example clients (have to remember to regenerate the processor JARs).

The suggestion in #259230 that a "configuration wizard" add processors is not very satisfactory. There is hardly likely to be an Eclipse plugin specially designed to handle every library you might add to your classpath.


JSR 269 does not seem to require that the processorpath default to the classpath, though something like this is implied by the phrase "the normal discovery mechanism" in

http://java.sun.com/javase/6/docs/api/javax/tools/JavaCompiler.CompilationTask.html#setProcessors(java.lang.Iterable)

The javac tool indeed discovers processors automatically in the classpath if -processorpath is not specified. This means that Ant or Maven based builds work out of the box, including those run from NetBeans and probably also from IDEA.

I was not able to get 'java -jar ecj.jar' to do so (using libecj-java 3.4.2-0ubuntu1), or even to print any information when using -XprintProcessorInfo, although the source code of BatchAnnotationProcessorManager makes it look like it would work.


The decision to not do discovery in Eclipse was justified in #259230 with

"Because of limitations in the Java ClassLoader architecture, it is not deterministically possible to unlock files once a class has been loaded from them."

which is not really true. A particular ClassLoader, java.net.URLClassLoader up to JDK 6, has this behavior. As was disclaimed a bit later, one workaround is to load from a copy of the classpath. Better is to use a different ClassLoader implementation which can be forcibly closed at the end of the compilation process (this is not at all hard to do); in fact, URLCL can do this in JDK 7:

http://java.sun.com/javase/7/docs/technotes/guides/net/ClassLoader.html


Bug #168246 is somewhat similar, but refers to tools.jar which should not be expected to be in the classpath of a project unless you asked to put it there. Interestingly, the comment at the end of that issue says

"In Eclipse 3.3 we will certainly be implementing fallback to the compile classpath for Java 6 processor discovery if no processor path is specified"

which is what apparently did not happen.

Bug #159534 would be superseded for some use cases by a fix for this bug. Bug #202554 would also be affected.


A fix would probably involve changes to:

* FactoryContainer - needs new discovery type (enum constant & subclass)
* JarFactoryContainer - needs to permit also directories (e.g. ${projdir}/bin)
* AnnotationProcessorFactoryLoader - calls JarFactoryContainer.getJarFile() to create class loader
* FactoryPluginManager - would need to inspect project CP

I investigated whether a fix would be possible from a plugin, but it seems this would not work. A plugin can only register a static list of processors; it cannot register a callback to provide processors on demand for a project. (Modifying .factorypath in response to changes in .classpath might be possible, but it seems clumsy and would irritate users who keep such files in version control. This would also not work well if it were necessary to create temporary JARs to load processors from.) Better for a fix to be done as part of jdt.apt.core.
Comment 1 Walter Harley CLA 2009-08-12 11:57:17 EDT
(In reply to comment #0)

> The decision to not do discovery in Eclipse was justified in #259230 with
> 
> "Because of limitations in the Java ClassLoader architecture, it is not
> deterministically possible to unlock files once a class has been loaded from
> them."
> 
> which is not really true. A particular ClassLoader, java.net.URLClassLoader up
> to JDK 6, has this behavior. As was disclaimed a bit later, one workaround is
> to load from a copy of the classpath. Better is to use a different ClassLoader
> implementation which can be forcibly closed at the end of the compilation
> process (this is not at all hard to do); in fact, URLCL can do this in JDK 7

Some time was put into this in the Eclipse 3.2 timeframe, and we found that it was, in fact, "hard to do."  IIRC the issue was keeping track of open streams returned by the GetResourceAsStream() method, since processors written with command-line mode in mind do not always reliably close their streams before exiting, but I might be mis-remembering.  If you would care to contribute a patch, this discussion might be a bit less abstract.

Note that if processors are wrapped within Eclipse plug-ins (something that can be done even for third-party processors, depending on licensing terms), then all that is needed is to enable processing for the project; it is not necessary to individually add all the processors.  Another alternative (preferred by the company that initially funded the APT work) is to write project creation wizards that handle all the project configuration.

However, also note that because of the processor selection algorithm defined by the APT spec, it is quite easy for a processor to "claim" all annotations, thereby short-circuiting any processing by processors that happen to be later on the path.  Since one such ill-behaved processor can break all other projects, and debugging a problem like that is somewhat hard, we opted against making it the default.
Comment 2 Walter Harley CLA 2009-08-12 12:01:53 EDT
*** Bug 286074 has been marked as a duplicate of this bug. ***
Comment 3 Daniel Sperry CLA 2010-08-24 08:51:42 EDT
This bug is marked as "Platform: PC Linux" but is also a problem in windows and mac os, and, I suspect, in all platforms.
Comment 4 Walter Harley CLA 2010-08-24 12:06:17 EDT
(In reply to comment #3)
> This bug is marked as "Platform: PC Linux" but is also a problem in windows and
> mac os, and, I suspect, in all platforms.

Yes; the current Eclipse behavior is by design and is consistent across platforms.
Comment 5 Thomas Hallgren CLA 2011-03-15 03:02:23 EDT
*** Bug 339957 has been marked as a duplicate of this bug. ***
Comment 6 Michael Schnell CLA 2014-03-20 04:12:21 EDT
I vote for a possibility to add processors form the projects classpath.

The problem with the current approach is that users that have their processors on the classpath currently need to keep two files in sync: .classpath and .factorypath. 

When the user updates the .classpath to use a newer version of the JAR that contains the annotation and its processor then he must remember to update the .factorypath as well. Otherwise the compiler might use a processor that is incompatible to the annotation. 
If the annotation changed its layout, this might lead to NoSuchMethodException and similar errors that are logged in Eclipse error log without any further notice. All the user might notice at first is that the processor did not run (e.g. files are not generated).
If the annotation layout stayed the same but the processors logic changed, this error might be even harder to notice.

Therefore it would be great to add entries to the factory path that pick the processor form classpath. Either to add explicitly the whole classpath (as javac does per default) or provide a mechanism to add some JARs from classpath or even pick the processors manually, e.g. by providing the fully qualified name of the processor class.
Comment 7 Walter Harley CLA 2014-03-24 00:52:51 EDT
I understand your point about having to maintain the factory path and classpath separately.  However, I would ask that you carefully read the rest of this thread to understand the reasons why we do NOT want to support annotation processors living on the build classpath, in an incremental compiler.

Also, with all respect, given current funding levels, votes are unfortunately not likely to be counted.

What would be great is if we can use this bug to discuss a solution that resolves your problem without introducing the classpath problems, and if you can then submit it as a patch.

The key concern that needs to be addressed is that once an annotation processor class has been loaded, it is hard to deterministically unload.  The writers of javac were able to ignore this issue because the entire javac process shuts down after every compile; but that is not true for Eclipse.
Comment 8 Jesse Glick CLA 2014-03-24 13:59:24 EDT
(In reply to Walter Harley from comment #7)
> once an annotation processor class has been loaded,
> it is hard to deterministically unload.

What is the specific issue here, beyond calling .close() on the ClassLoader used to load the processor and then discarding it, as suggested in comment #0?

Certainly there are robustness issues with any Java framework loading any third-party classes, usually revolving around instances of those classes improperly adding themselves to global static state when it can be reached (such as in System.getProperties), or blocking without a sensible timeout on some lock. But that just seems like a bug fix for the (processor) class in question, not fundamentally different from a bug in a plugin (including one which registers a processor) other than where the bug report initially gets sent.
Comment 9 Walter Harley CLA 2014-03-26 01:46:44 EDT
Have you tried that?  What platforms have you tried it on?

When we did, we found that it was not reliable.  I would much rather disable functionality altogether than have it work some of the time unpredictably.

Perhaps the platforms have improved since when we tried it.  If you are willing to do the platform testing effort then this is something that could be revisited.  Keep in mind that Eclipse works hard to maintain support for fairly old platforms.
Comment 10 Philipp Wendler CLA 2014-04-12 14:47:37 EDT
If it is not desired to scan the full classpath, would it be possible to mark single JARs from the classpath such that they should be used for annotation processing?

Or, as a least resort, could you please provide a possibility to put project-relative paths in .factorypath? Then this file could at least get checked into revision control, which would be a major simplification for distributed projects.

Currently it is not possible to check in .factorypath because it either contains the project name (forcing every developer to have the same project name), or it requires manual setup of classpath variables, which are not project-specific and thus break when using multiple projects.
Comment 11 Philipp Wendler CLA 2014-08-26 06:26:55 EDT
I would like to ask again to please provide a possibility to put project-relative paths in .factorypath?

I guess this would be relatively easy to implement, does not have the problems of auto-discovery, and would be fully sufficient for us (and I guess most other users) because we could simply check in this file.

Please consider this, it is currently the only problem that keeps us from using some projects like Google AutoValue that rely on annotation processors.
Comment 12 Jay Arthanareeswaran CLA 2014-08-26 09:26:50 EDT
I haven't given much thought, but if someone is interested in providing a patch, we can look into this.
Comment 13 Curtis Rueden CLA 2014-08-26 09:39:29 EDT
@Philipp You can use VARJAR with a variable. It works especially well with the Maven M2E plugin, which defines the M2_REPO variable for you to point at ~/.m2/repository. E.g.:

<factorypath>
    <factorypathentry kind="VARJAR" id="M2_REPO/net/java/sezpoz/sezpoz/1.9/sezpoz-1.9.jar" enabled="true" runInBatchMode="true"/>
</factorypath>

Of course, even if you check in such a .factorypath, as well as a .settings/org.eclipse.jdt.apt.core.prefs file, you still have to remember to clean the project to rerun the annotation processors.

These days, my group works around this bug by using our own Maven plugin with an "eclipse-helper" goal configured to run on incremental builds:
* https://github.com/scijava/scijava-maven-plugin/blob/scijava-maven-plugin-0.2.0/src/main/java/org/scijava/maven/plugin/EclipseHelperMojo.java#L50
* https://github.com/scijava/scijava-maven-plugin/blob/scijava-maven-plugin-0.2.0/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
* https://github.com/scijava/pom-scijava/commit/e68cb0fab473352320fa34a53a539874716c5a00

This approach makes the relevant annotation processing work out of the box for all projects extending our pom-scijava parent POM. The license is BSD-2 so anyone who needs it is welcome to generalize this plugin to run _all_ annotation processors present on the classpath, the same as javac does.
Comment 14 Kris De Volder CLA 2015-02-06 13:42:52 EST
> I would much rather disable functionality altogether than have it work
> some of the time unpredictably.

Unfortunately, this means the reality is that it then works 'unreliably' all of the time. 

I'd rather have something that works 'out of the box' sometimes and needs some manual intervention sometimes, rather than something that never works out of the box.

Enough examples have been given already how this creates a siutation where people come out and say 'If you do this with X, it just works'. If you do this with Eclipse please follow this long list of instructions and keep your fingers crossed that you did it right'.

X = IntelliJ, javac, maven, gradle, ...

The conclusion people invitably will draw from this is 'Eclipse is unreliable'.

Try explaining them that this is because Eclipse is better than these other things because it has fabulous incremental compiler (I tried explaining this to an IntelliJ user, and beleave me, you just feel dumb making arguments like that, and they really don't care). 

Also.. if JTD / APT doesn't configure the processors, someone/something else will (have to). Because, if a user's code depends on some annotation processing, its going to have to be turned-on somehow (or it won't work). So people will either do it themselves (and create a mess) or will want their build tools (IveDE, M2E, Gradle tooling) to do what JDT/APT refuses to do [*]. In the end you have not solved anything but end up in exactly the same situation (all the classpath jars will be copied into the factory path by something like M2E-APT, for example).

My point is, you can't stop people from using annotation processors by making them hard to configure in Eclipse. And if indeed a project depends on an annotation processor and it doesn't get executed by default... that doesn't exactly make Eclipse look 'reliable'.

[*] = https://issuetracker.springsource.com/browse/STS-3925
Comment 15 Curtis Rueden CLA 2015-02-06 14:20:52 EST
I agree with many here that Eclipse should be improved to run annotation processors out of the box by default. Even M2E-APT does not turn this on by default. For my project, we need dabbler-programmer types to be able to painlessly write e.g. "@Plugin" on a class have have the annotation processing just work, _especially_ in Eclipse which is what nearly every newbie uses.

That said, I also agree with the Eclipse committers: where is the patch? Has anyone bothered to actually try getting this working? I have seen comments elsewhere (e.g., https://issuetracker.springsource.com/browse/STS-3925) strongly implying that the Eclipse developers are somehow unwilling to address this issue, or that they do not consider it to be a bug. But I don't read Walter Harley's comments that way. He asked for a discussion and a patch, as did Jay Arthanareeswaran. And the status of this issue is still NEW, not WONTFIX.

Hopefully that isn't too ranty -- this discussion just pushes my buttons a little. Eclipse is a truly amazing tool, and free. If this issue truly impacts your project so much, maybe it's worth the time to really explore the technical details of a fix.
Comment 16 Kris De Volder CLA 2015-02-06 15:22:33 EST
Thanks Curtis, maybe I went overboard a little in echoing the sentiment I got from some of our (STS) users that Eclipse developers are unwilling to address the isue.

You are indeed correct that the thread here doesn't really imply that at all.
So thanks for pointing that out. 

But, Bug #259230 *was* actually closed as won't fix. And both issues are really asking for the same kind of thing: for APT to configure/use processors from the projects classpath. So reasons for 'won't' fix given in #259230 also would apply here.

My point was, before closing this one also as 'Won't fix' because 'it is hard'. 

Consider that not fixing it doesn't make the problem go away. People will still configure their projects somehow to take processors from the classpath and all you do is make that harder, you don't actually stop that practice.
Comment 17 Michael Vorburger CLA 2016-05-23 17:37:33 EDT
FYI: I find that using https://github.com/jbosstools/m2e-apt alleviates this problem.. it doesn't solve exactly what is being for here, for native JDT APT support, but is more than good enough to get APT working out-of-the-box in any project using Maven.