Bug 87718 - Listener on build process on a per file basis.
Summary: Listener on build process on a per file basis.
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.1   Edit
Hardware: PC Windows XP
: P3 enhancement (vote)
Target Milestone: 3.2 M5   Edit
Assignee: Kent Johnson CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-03-10 17:25 EST by Theodora Yeung CLA
Modified: 2006-02-14 06:35 EST (History)
1 user (show)

See Also:


Attachments
zip file of the first patches for the ICompilationParticipant interfaces (8.11 KB, application/octet-stream)
2005-03-28 19:55 EST, Mike Kaufman CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Theodora Yeung CLA 2005-03-10 17:25:13 EST
Requesting a listener interface for getting build notification on a per file 
basis. 

The main goal is to implement APT (Annotation Processor Tool) as a plug-in 
with JDT. 

Requirements:
1) APT needs to be run whenever a file have been compiled and the notification 
can occur anytime after a full resolve (it doesn't matter whether is occur 
after check or after code-gen). 
2) Since APT processing could generate files, the generation of files (always 
java source file) need to be communicated back to the builder and be built.


To better demonstrate the process, below is an API suggestion. 
(Just a suggestion. Any recommendations on how to accomplish the goal is very 
much appreciated.)

/**
 * Listener on the build process on a per file basis. 
 * The listener is notified whenever a java file has been compiled.
 */
public interface BuildFileListener{

    /**
     * Called by the builder after a file has been compiled. 
     * @param unit the compilation unit that has been compiled and contains
     *             binding information.
     * @param shapeChanged <code>true</code>indicate whether the public shape 
     *                     of the type has been changed. 
     *		         False otherwise or no such information is available 
because of clean build.
     *        
     * @return the set of source files that needs to be(re)compiled because of 
     *         the APT processing. This would include newly generated files 
     *         or generated files that has been changed. 
     *         (see comments below for dependency handling).
     */
    Set notify(CompilationUnit unit, boolean shapeChanged);
}

How this all ties together
==========================
Example: 
In an incremental build, two java source files in the project need to be 
compiled, 
A.java and B.java, which both have some annotations on them.

batch #0: Files to build are [A.java, B.java]
At the end of building A.java, the BuildFileListener is notified and APT 
processor(s) would be dispatched.
During APT processing, file AGenerated.java is created. In order to 
communicate the presence of a generated file, the 
returned value of BuildFileListener.notify() would include AGenerated.java. 
Similar process occured to B.java and BGenerated.java is created and returned.

Assume that both A.java and B.java, has imported foo.* and AGenerated and 
BGenerated are types in to package foo.

batch #1: Files to build are [AGenerated.java, BGenerated.java]
Assuming I am correct on the dependency handling+ in eclipse, type AGenerated 
and BGenerated would cause the recompilation of A.java and B.java
since A.java and B.java has a package dependency on package foo and package 
foo has just changed. 

batch #2: Files to build are [A.java, B.java] 
Scenario 1: the public shape of type A and type B did not change and the 
notifiy call for the two files were called with the 
		second boolean parameter being "false". 
Since there is no structural change, APT will not need to be dispatched and 
the return value would be the empty set in both notify call. 
The build is complete. Note: structure change here has to include annotation 
information as well. 

Scenario 2: the public shape of type A and type B did change. APT is 
dispatched and re-generated AGenerated.java and BGenerated.java. 
The APT plug-in noticed that the check sum of AGenerated and BGenerated did 
not change (something that the plug-in has to maintain) 
and they will not be returned from the notify call. 
The build is complete.

Scenario 3: the public shape of type A and type B did change and the check sum 
of AGenerated and BGenerated did change also. 
Then repeat the process as in batch #1. Since the content of A.java and B.java 
did not change during this time frame. We would hit scenario #1 or #2 no later 
than the second time AGenerated.java and BGenerated.java are compiled.

+ Here the assumption is that JDT is handling the regular java dependency for 
the APT plug-in. The set of files returned from the notify call does not   
include those needed to be recompiled based on dependency information. If this 
is not true, then, the plug-in has to have read access to the dependency   
information and the returned set of source files will include files that need 
to be recompiled based on those information.

Since a clean build does not have a queue or notation of a batch, this 
scenario only works for incremental build and something else has be worked out 
the handle the clean build scenario.
Comment 1 Mike Kaufman CLA 2005-03-23 21:13:43 EST
So, I've managed to get some of the builder hooks we've been discussing to 
work on my machine.  This is really just a proto-type at this point, but I 
think it proves that it will work.   I'd like some feedback on the code. If 
everyone likes this, I'll clean it up with comments and submit a patch.  In 
particular, I'd like to know if the following three things are OK:

1.  the naming of the "IBuildListener" interface.  It seems there is some 
conflict with the current idea of a BuildNotifier, and I'd like to choose as 
appropritae a name as possible.  

2.  I'd like to know if the regstration of IBuildListeners is OK.  In 
particular, is keeping a static list of IBuildListeners on the 
AbstractImageBuilder OK?

3.  I'd like to know if the code looks OK.


Here is the basic infrastructure to do pre-build notification that I've added 
to the AbstractImageBuilder class:

    private static java.util.List _buildListeners = new java.util.ArrayList();

    public static interface IBuildListener {

        /** 
          * returns a Set<IFile> of new files to be added to the compilatoin 
          */
	public java.util.Set beginToCompile( ICompilationUnit[] sourceUnits, 
IProject p,  org.eclipse.jdt.internal.core.JavaProject jp );
    }

    public static void registerBuildListener( IBuildListener l) {
	_buildListeners.add( l );
    }

   
    protected Set notifyBuildListeners( ICompilationUnit[] sourceUnits ) {
	java.util.Iterator it = _buildListeners.iterator();
	Set newFiles = new HashSet();
	while ( it.hasNext() )
	{
            IBuildListener l = (IBuildListener) it.next();
            Set f = l.beginToCompile( sourceUnits ,  
javaBuilder.currentProject, javaBuilder.javaProject );
            newFiles.addAll( f );
	}
	return newFiles;
    }	




Here is where we plug into the compile method in AbstractImageBuilder to do 
the pre-build notificaiton.  The try-block is complete. 


void compile(SourceFile[] units, SourceFile[] additionalUnits) {

        ...

	try {
		inCompiler = true;
		
		Set newFiles = notifyBuildListeners( units );
		SourceFile[] newUnits = new SourceFile[ units.length + 
newFiles.size() ];
		System.arraycopy( units, 0, newUnits, 0, units.length );
		Iterator it = newFiles.iterator();
		int idx = units.length;
		while ( it.hasNext() )
		{
			IFile f = (IFile) it.next();
			newUnits[idx++] = new SourceFile( f, 
getSourceLocationForFile( f ) );
		}
		units = newUnits;

		
		compiler.compile(units);
	} catch (AbortCompilation ignored) {

	...
}


And here is a utility function.  The code was lifted from  
IncrementalImageBuilder, so when I do a patch, I'll update 
IncrementalImageBuilder to use this new function.


    protected ClasspathMultiDirectory getSourceLocationForFile( IFile file ) {
	ClasspathMultiDirectory md =  null;
	if (file.exists()) {
            md = sourceLocations[0];
            if (sourceLocations.length > 1) {
                IPath sourceFileFullPath = file.getFullPath();
                for (int j = 0, m = sourceLocations.length; j < m; j++) {
                    if (sourceLocations[j].sourceFolder.getFullPath
().isPrefixOf(sourceFileFullPath)) {
                        md = sourceLocations[j];
                        if (md.exclusionPatterns == null && 
md.inclusionPatterns == null)
                            break;
                        if (!Util.isExcluded(file, md.inclusionPatterns, 
md.exclusionPatterns))
                            break;
                    }
                }
            }
	}
	return md;
    }


Comment 2 Mike Kaufman CLA 2005-03-28 19:55:33 EST
Created attachment 19257 [details]
zip file of the first patches for the ICompilationParticipant interfaces


I've attached a zip file which contains interfaces for the
ICompilationParticipant interfaces we discussed.  These patches contain the
hookups for pre-build notification & post-reconcile notification.
Comment 3 Theodora Yeung CLA 2005-07-11 19:56:42 EDT
The ICompilationParticipant API resolved the problem. Marking the bug as 
fixed. 
Comment 4 Theodora Yeung CLA 2005-10-20 18:21:17 EDT
Closing bug as ICompilationParticipant API is moving into 3.2.
Comment 5 Philipe Mulet CLA 2005-12-21 16:34:04 EST
Theodora : this bug should be assigned to someone real, and have a proper milestone when closed as fixed.
Comment 6 Jess Garms CLA 2005-12-21 16:45:39 EST
Re-opening as per Philippe's request.
Comment 7 Jess Garms CLA 2005-12-21 16:48:22 EST
Kent - this should be resolveable with the work you did on the CompilationParticipant API.
Comment 8 Kent Johnson CLA 2006-01-16 14:56:19 EST
Solved with CompilationParticipant, API follows... tests were added to ParticipantBuildTests.


/**
 * A compilation participant is notified of events occuring during the compilation process.
 * The compilation process not only involves generating .class files (i.e. building), it also involve
 * cleaning the output directory, reconciling a working copy, etc.
 * So the notified events are the result of a build action, a clean action, a reconcile operation 
 * (for a working copy), etc.
 * <p>
 * Clients wishing to participate in the compilation process must suclass this class, and implement
 * {@link #isActive(IJavaProject)}, {@link #aboutToBuild(IJavaProject)}, 
 * {@link #reconcile(ReconcileContext)}, etc.
* </p><p>
 * This class is intended to be subclassed by clients.
 * </p>
 * @since 3.2
 */
public abstract class CompilationParticipant {

public static int READY_FOR_BUILD = 1;
public static int NEEDS_FULL_BUILD = 2;

/**
 * Notifies this participant that a build is about to start and provides it the opportunity to
 * create missing source folders for generated source files.
 * Only sent to participants interested in the project.
 * <p>
 * Default is to return <code>READY_FOR_BUILD</code>.
 * </p>
 * @param project the project about to build
 * @return READY_FOR_BUILD or NEEDS_FULL_BUILD
 */
public int aboutToBuild(IJavaProject project) {
	return READY_FOR_BUILD;
}

/**
 * Notifies this participant that a compile operation is about to start and provides it the opportunity to
 * generate source files based on the source files about to be compiled.
 * When isBatchBuild is true, then files contains all source files in the project.
 * Only sent to participants interested in the current build project.
 *
 * @param files is an array of CompilationParticipantResult
 * @param isBatch identifies when the build is a batch build
  */
public void buildStarting(ICompilationParticipantResult[] files, boolean isBatch) {
	// do nothing by default
}

/**
 * Notifies this participant that a clean is about to start and provides it the opportunity to
 * delete generated source files.
 * Only sent to participants interested in the project.
 * @param project the project about to be cleaned
 */
public void cleanStarting(IJavaProject project) {
	// do nothing by default
}

/**
 * Returns whether this participant is active for a given project.
 * <p>
 * Default is to return <code>false</code>.
 * </p><p>
 * For efficiency, participants that are not interested in the 
 * given project should return <code>false</code> for that project.
 * </p>
 * @param project the project to participate in
 * @return whether this participant is active for a given project
 */
public boolean isActive(IJavaProject project) {
	return false;
}

/**
 * Returns whether this participant is interested in only Annotations.
 * <p>
 * Default is to return <code>false</code>.
 * </p>
 * @return whether this participant is interested in only Annotations.
 */
public boolean isAnnotationProcessor() {
	return false;
}

/**
 * Notifies this participant that a compile operation has found source files using Annotations.
 * Only sent to participants interested in the current build project that answer true to isAnnotationProcessor().
 * Each CompilationParticipantResult was informed whether its source file currently hasAnnotations().
 *
 * @param files is an array of CompilationParticipantResult
  */
public void processAnnotations(ICompilationParticipantResult[] files) {
	// do nothing by default
}
}
Comment 9 David Audel CLA 2006-02-14 06:35:09 EST
Verified for 3.2 M5 using build I20060214-0010