Community
Participate
Working Groups
We use Eclipse to build/compile the project automatically. We use Ant to generate some source files (with XDoclet), compile the generated files, and then package the jars/wars/etc. In Ant, we use the JDTCompilerAdapter (<property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>). Ant compiles to the same output directory as Eclipse. The source files are generated to a directory that is registered as a source directory in the Eclipse project. We launch Ant from within the Eclipse Ant runner. The problem occurs when we modify the callers to the generated classes before we regenerate the callees. When we regenerate (and compile), the callers still show up having build errors, even though the errors were fixed with the newly generated file. Even if you refresh the project or close and reopen Eclipse, the caller shows compile errors. Example: Class Manager depends on generated class GenCommand. GenCommand currently has a method execute(String). We want to change GenCommand to have an interface execute(String, String). First, we edit Manager's call to GenCommand.execute(...). // inside Manager class, we modify the call for the new execute(String, String) signature this.getGenCommand().execute(String, String); At this point, Manager does not compile because GenCommand.execute(...) has only one String parameter. Next, we run Ant to regenerated GenCommand with the new execute(String, String) interface. Ant compiles GenCommand with the JDT compiler. When Ant completes, GenCommand.execute(...) has the signature (String, String), but Manager still shows a build error with the message: "The method execute(String) in the type GenCommand is not applicable for the arguments (String, String)" If I open the declaration of GenCommand, it shows that execute has (String, String) for the arguments. So I know the file generated ok and Eclipse is "seeing" the new version. This tells me that even though I am compiling the generated file with the JDT, Eclipse is not being notified and the caller (Manager) is "caching" the old execute() signature. Or else the Manager.class file is not being updated to indicate no compile error. I am using sun's JDK 1.4.2_04 with the Eclipse jdk compliance setting to "1.4". I get this error in both Eclipse 3.0.1 and 3.1M4.
Just to double-check, I decompiled the .class file of the caller that is showing the (incorrect) compile error. The class file showed: throw new Error("Unresolved compilation problem: \n\tThe method... for each of the methods that called to GenCommand.execute(...) So it really seems that the JDTCompilerAdapter is not finding the dependencies. Since Manager.class depends on GenCommand.class, it should recompile Manager. I hope this helps. --Adam
More research. Another way to reproduce this error without involving Ant: I have everything compiling. Open a class, let's call it MyPojo. MyPojo calls MyDAO.getInstance(id); Modify MyPojo so it calls MyDAO.getIstance(id, "some string"); Now MyPojo won't compile because the signature MyDAO.getInstance(...) is getInstance(long id). Now open MyDAO outside of Eclipse. Change the signature of MyDAO.getInstance(...) to be getInstance(long id, String str). Now go back to eclipse and refresh MyDAO. (Eclipse builds automatically.) Now MyPojo should be compiling because it calls the updated signature of MyDAO.getInstance(long, String). However, MyPojo still shows a compile error. This tells me that the Eclipse compiler just isn't identifying compilation dependencies if a file is modified outside Eclipse and then refreshed and compiled by Eclipse. Since this problem does not appear to relate to Ant, I have renamed the summary of this bug.
Just to set the correct expectations. The Ant compiler adapter is purely a command-line compiler, which doesn't deal with incremental recompilations. If you tell it to compile given files, it will compile these only, and also referenced sources available on the classpath. So dependents are not in the loop. Our contribution is not different from javac or other command-line compilers in that respect. When building inside Eclipse however, our Java builder maintains a dependency graph and is able to recompile affected dependents, based on discovered changes. Note that the Java builder can be run headless, on the Eclipse platform. Comment #2 seems however indicate you discovered a problem using Eclipse incremental builder though. We will investigate this one.
So just to double check, your testcase looks like: public class MyPojo { void test(long id) { MyDAO.getInstance(id); } // void test(long id) { MyDAO.getInstance(id, "some string"); } // step #2 } public class MyDAO { public static void getInstance(long id) {} // public static void getInstance(long id, String s) {} // step #3 } 1. save these 2 types in their respective files. 2. replace the first test() method - see compile error 3. replace the first getInstance() method - compile error goes away correct? When I try this, it works - regardless of how step 3 is performed. Does it work for you when you try it in Eclipse (no outside mods/refresh)? How are you 'refreshing' the file? Did you select refresh on the project or the specific file? Does it make a difference which one you choose? When you change the file, what happens if you have it open in a Java editor at the same time? Are you notified that eclipse has detected external changes when you reactivate your workspace? BTW: If your Ant generated source files are in a normal source folder on the build path, there is no reason to compile them with Ant. The Eclipse Java builder will compile them whenever they are changed.
Yes, that is the correct test scenario. If I do those steps in Eclipse, it definitely works. If I do step #3 outside Eclipse, I get the error. However, I was getting the error with some of our product code. I'm going to create these test classes to ensure I can reproduce exactly as you specified. I'm not sure if the error happens when Eclipse auto-refresh is on or not. About your Ant comment, we use Ant to compile because it isn't always obvious when we need to regenerate the files. It is nice for developers to run one Ant target that generates files, compiles as needed and deploys the code to the appserver. I assumed that since Ant used the JDT compiler from inside eclipse, it could also benefit from JDT's dependency checking and incremental compiling. Since it doesn't, we need to have developers run the code generation first, then refresh in Eclipse (to trigger a compile), and then run the deploy target... Adam
Hi Kent- I've been working on this for three hours today. When I updated the defect yesterday with the MyDAO/MyPojo example, it was a simplification of our actual product code. Having actually created MyPojo/MyDAO classes, I can't reproduce the issue. The actual scenario has a number of other factors that I guess we need to consider now. I will keep working on this to see if I can come up with a reproduceable test case for you. Meanwhile, here are some more details of our environment and the actual scenario. Perhaps this will give you some ideas. In our application, we have Servlets and Struts Actions calling to EJBs (session beans). We use xdoclet to generate Business Delegates for the EJBs, so the Servlets/Actions call to the Delegates, and the Delegates call to the EJB Remote interfaces. The Delegates are generated on the developer's local machine--they are not checked into CVS. If developer John makes a change to the interface of an EJB (CommentFacade), then he generates the Delegate and modifies the Servlet. So everything in John's environment compiles and runs. Then he checks in the Servlet and EJB, but not the Delegate. Then I do a CVS update. After the CVS update, my environment won't compile because the Delegate does not reflect the new interface of the CommentFacade. So I run Ant to generate the CommentFacadeDelegate. I tell Eclipse to refresh the src-generated directory where the Delegates get generated. Eclipse compiles the Delegates. This is where the error usually happens. Even though Eclipse now sees the new Delegates, the Servlet still shows a compile error. Sometimes when I do an update, there may be five EJBs with new interfaces that need to be generated. Almost everyone on the team has experienced this problem, so it isn't just a local issue. Could there be any race conditions in there? If a Servlet depends on two Delegates that need to be generated, the first Delegate gets refreshed, which recompiles the Servlet. Then the second Delegate gets refreshed, but it doesn't recompile the Servlet because the Servlet's class file is newer than the Servlet's source file? I will keep working on this. I realize you probably can't make any progress without being able to reproduce regularly. Do you have any suggestions for me? Thank you very much for your help. Adam
.class file timestamps do not matter. We are told by Core whenever a resource (a .java file) has changed. I would suggest you copy the generated files before you regen them. If the bug shows up then maybe you can deduce what happened from the changes in the old- >new gen'ed files & the compile errors that do not go away.
Saying that Core notifies you if a .java changes, does that mean that once Eclipse sees the changes to the Delegate .java files it should fix the dependent callers, even if the Delegates have already been compiled by Ant? (Into the same output directory as Eclipse.) I might be able to reproduce the problem easier if I compile things with Ant and then refresh. Thanks again, Adam
Actually you just hit the problem! We compare the .class files generated BEFORE we save them to see if the type has structural changes that need to be propagated. By putting your gen'ed classes into a source folder in the project, Ant compiles the classes BEFORE we get to compile & compare. So you have 2 choices: 1. let eclipse compile your gen'ed classes instead of Ant 2. or put your gen'ed classes in another folder (not a source folder on the build path) & then have Ant create the .class files into a .class folder that you add to the build path In option 2, we will see the .class file changes & recompile dependent types ALWAYS. We cannot tell when structual changes happened so we'll always have to recompile dependent types. Your best option is to let the Java Builder compile your gen'ed source files.
Would it work to configure the src-generated source directory to build to an output directory that is different from the default output directory? For example, the default source directory goes to build/classes but src-generated goes to build-gen/classes. That way Ant could compile the Delegates to build/classes. Then we would refresh Eclipse and Eclipse would look in the build-gen/classes dir to see that the Delegates have changed and changes need to be propagated. Or would that cause a problem because Ant puts the Delegate class files in build/classes and that directory is used by Eclipse for other output?
Why do you want to compile the same files twice? If you want Ant to compile them then have it put them in its own output folder which you add to your build path as a .class folder. But then the dependents will be recompiled everytime you regen your source. Its seems straight forward to have 2 Ant tasks. One to gen the source & another to deploy it on the target. Then you let eclipse compile the changes for you.
Reopen if either setup options does not work.