Community
Participate
Working Groups
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.
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; }
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.
The ICompilationParticipant API resolved the problem. Marking the bug as fixed.
Closing bug as ICompilationParticipant API is moving into 3.2.
Theodora : this bug should be assigned to someone real, and have a proper milestone when closed as fixed.
Re-opening as per Philippe's request.
Kent - this should be resolveable with the work you did on the CompilationParticipant API.
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 } }
Verified for 3.2 M5 using build I20060214-0010