Bug 84188 - Compiler does not find Dependencies
Summary: Compiler does not find Dependencies
Status: RESOLVED WORKSFORME
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.1   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.1 M5   Edit
Assignee: Kent Johnson CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-02-01 17:19 EST by Adam Brod CLA
Modified: 2005-02-04 10:18 EST (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Adam Brod CLA 2005-02-01 17:19:51 EST
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.
Comment 1 Adam Brod CLA 2005-02-01 18:06:23 EST
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
Comment 2 Adam Brod CLA 2005-02-01 18:23:23 EST
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.
Comment 3 Philipe Mulet CLA 2005-02-02 07:00:39 EST
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.
Comment 4 Kent Johnson CLA 2005-02-02 10:28:53 EST
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.
Comment 5 Adam Brod CLA 2005-02-02 11:45:32 EST
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
Comment 6 Adam Brod CLA 2005-02-02 12:15:47 EST
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

Comment 7 Kent Johnson CLA 2005-02-02 12:32:30 EST
.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.
Comment 8 Adam Brod CLA 2005-02-02 13:57:06 EST
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
Comment 9 Kent Johnson CLA 2005-02-02 14:11:21 EST
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.
Comment 10 Adam Brod CLA 2005-02-02 15:04:17 EST
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?
Comment 11 Kent Johnson CLA 2005-02-02 15:29:53 EST
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.
Comment 12 Kent Johnson CLA 2005-02-04 10:18:47 EST
Reopen if either setup options does not work.