KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > events > BuildManager


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.core.internal.events;
12
13 import java.util.*;
14 import org.eclipse.core.internal.dtree.DeltaDataTree;
15 import org.eclipse.core.internal.resources.*;
16 import org.eclipse.core.internal.utils.Messages;
17 import org.eclipse.core.internal.utils.Policy;
18 import org.eclipse.core.internal.watson.ElementTree;
19 import org.eclipse.core.resources.*;
20 import org.eclipse.core.runtime.*;
21 import org.eclipse.core.runtime.jobs.ILock;
22 import org.eclipse.osgi.util.NLS;
23 import org.osgi.framework.Bundle;
24
25 public class BuildManager implements ICoreConstants, IManager, ILifecycleListener {
26
27     /**
28      * Cache used to optimize the common case of an autobuild against
29      * a workspace where only a single project has changed (and hence
30      * only a single delta is interesting).
31      */

32     class DeltaCache {
33         private Object JavaDoc delta;
34         private ElementTree newTree;
35         private ElementTree oldTree;
36         private IPath projectPath;
37
38         public void cache(IPath project, ElementTree anOldTree, ElementTree aNewTree, Object JavaDoc aDelta) {
39             this.projectPath = project;
40             this.oldTree = anOldTree;
41             this.newTree = aNewTree;
42             this.delta = aDelta;
43         }
44
45         public void flush() {
46             this.projectPath = null;
47             this.oldTree = null;
48             this.newTree = null;
49             this.delta = null;
50         }
51
52         /**
53          * Returns the cached resource delta for the given project and trees, or
54          * null if there is no matching delta in the cache.
55          */

56         public Object JavaDoc getDelta(IPath project, ElementTree anOldTree, ElementTree aNewTree) {
57             if (delta == null)
58                 return null;
59             boolean pathsEqual = projectPath == null ? project == null : projectPath.equals(project);
60             if (pathsEqual && this.oldTree == anOldTree && this.newTree == aNewTree)
61                 return delta;
62             return null;
63         }
64     }
65
66     /**
67      * These builders are added to build tables in place of builders that couldn't be instantiated
68      */

69     class MissingBuilder extends IncrementalProjectBuilder {
70         private boolean hasBeenBuilt = false;
71         private String JavaDoc name;
72
73         MissingBuilder(String JavaDoc name) {
74             this.name = name;
75         }
76
77         /**
78          * Log an exception on the first build, and silently do nothing on subsequent builds.
79          */

80         protected IProject[] build(int kind, Map args, IProgressMonitor monitor) {
81             if (!hasBeenBuilt) {
82                 hasBeenBuilt = true;
83                 String JavaDoc msg = NLS.bind(Messages.events_skippingBuilder, name, getProject().getName());
84                 Policy.log(IStatus.WARNING, msg, null);
85             }
86             return null;
87         }
88     }
89
90     private static final int TOTAL_BUILD_WORK = Policy.totalWork * 1000;
91
92     //the job for performing background autobuild
93
final AutoBuildJob autoBuildJob;
94     private boolean building = false;
95     private final ArrayList builtProjects = new ArrayList();
96
97     //the following four fields only apply for the lifetime of a single builder invocation.
98
protected InternalBuilder currentBuilder;
99     private DeltaDataTree currentDelta;
100     private ElementTree currentLastBuiltTree;
101     private ElementTree currentTree;
102
103     /**
104      * Caches the IResourceDelta for a pair of trees
105      */

106     final private DeltaCache deltaCache = new DeltaCache();
107     /**
108      * Caches the DeltaDataTree used to determine if a build is necessary
109      */

110     final private DeltaCache deltaTreeCache = new DeltaCache();
111
112     private ILock lock;
113
114     //used for the build cycle looping mechanism
115
private boolean rebuildRequested = false;
116
117     private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
118

119     //used for debug/trace timing
120
private long timeStamp = -1;
121     private Workspace workspace;
122
123     public BuildManager(Workspace workspace, ILock workspaceLock) {
124         this.workspace = workspace;
125         this.autoBuildJob = new AutoBuildJob(workspace);
126         this.lock = workspaceLock;
127         InternalBuilder.buildManager = this;
128     }
129
130     private void basicBuild(int trigger, IncrementalProjectBuilder builder, Map args, MultiStatus status, IProgressMonitor monitor) {
131         try {
132             currentBuilder = builder;
133             //clear any old requests to forget built state
134
currentBuilder.clearForgetLastBuiltState();
135             // Figure out want kind of build is needed
136
boolean clean = trigger == IncrementalProjectBuilder.CLEAN_BUILD;
137             currentLastBuiltTree = currentBuilder.getLastBuiltTree();
138             // If no tree is available we have to do a full build
139
if (!clean && currentLastBuiltTree == null)
140                 trigger = IncrementalProjectBuilder.FULL_BUILD;
141             //don't build if this builder doesn't respond to the given trigger
142
if (!builder.getCommand().isBuilding(trigger))
143                 return;
144             // For incremental builds, grab a pointer to the current state before computing the delta
145
currentTree = ((trigger == IncrementalProjectBuilder.FULL_BUILD) || clean) ? null : workspace.getElementTree();
146             int depth = -1;
147             try {
148                 //short-circuit if none of the projects this builder cares about have changed.
149
if (!needsBuild(currentBuilder, trigger)) {
150                     //use up the progress allocated for this builder
151
monitor.beginTask("", 1); //$NON-NLS-1$
152
monitor.done();
153                     return;
154                 }
155                 String JavaDoc name = currentBuilder.getLabel();
156                 String JavaDoc message;
157                 if (name != null)
158                     message = NLS.bind(Messages.events_invoking_2, name, builder.getProject().getFullPath());
159                 else
160                     message = NLS.bind(Messages.events_invoking_1, builder.getProject().getFullPath());
161                 monitor.subTask(message);
162                 hookStartBuild(builder, trigger);
163                 //release workspace lock while calling builders
164
depth = getWorkManager().beginUnprotected();
165                 //do the build
166
SafeRunner.run(getSafeRunnable(trigger, args, status, monitor));
167             } finally {
168                 if (depth >= 0)
169                     getWorkManager().endUnprotected(depth);
170                 // Be sure to clean up after ourselves.
171
if (clean || currentBuilder.wasForgetStateRequested()) {
172                     currentBuilder.setLastBuiltTree(null);
173                 } else {
174                     // remember the current state as the last built state.
175
ElementTree lastTree = workspace.getElementTree();
176                     lastTree.immutable();
177                     currentBuilder.setLastBuiltTree(lastTree);
178                 }
179                 hookEndBuild(builder);
180             }
181         } finally {
182             currentBuilder = null;
183             currentTree = null;
184             currentLastBuiltTree = null;
185             currentDelta = null;
186         }
187     }
188
189     protected void basicBuild(IProject project, int trigger, ICommand[] commands, MultiStatus status, IProgressMonitor monitor) {
190         try {
191             for (int i = 0; i < commands.length; i++) {
192                 checkCanceled(trigger, monitor);
193                 BuildCommand command = (BuildCommand) commands[i];
194                 IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
195                 IncrementalProjectBuilder builder = getBuilder(project, command, i, status);
196                 if (builder != null)
197                     basicBuild(trigger, builder, command.getArguments(false), status, sub);
198             }
199         } catch (CoreException e) {
200             status.add(e.getStatus());
201         }
202     }
203
204     /**
205      * Runs all builders on the given project.
206      * @return A status indicating if the build succeeded or failed
207      */

208     private IStatus basicBuild(IProject project, int trigger, IProgressMonitor monitor) {
209         if (!canRun(trigger))
210             return Status.OK_STATUS;
211         try {
212             hookStartBuild(trigger);
213             MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
214             basicBuild(project, trigger, status, monitor);
215             return status;
216         } finally {
217             hookEndBuild(trigger);
218         }
219     }
220
221     private void basicBuild(final IProject project, final int trigger, final MultiStatus status, final IProgressMonitor monitor) {
222         try {
223             final ICommand[] commands;
224             if (project.isAccessible())
225                 commands = ((Project) project).internalGetDescription().getBuildSpec(false);
226             else
227                 commands = null;
228             int work = commands == null ? 0 : commands.length;
229             monitor.beginTask(NLS.bind(Messages.events_building_1, project.getFullPath()), work);
230             if (work == 0)
231                 return;
232             ISafeRunnable code = new ISafeRunnable() {
233                 public void handleException(Throwable JavaDoc e) {
234                     if (e instanceof OperationCanceledException)
235                         throw (OperationCanceledException) e;
236                     // don't log the exception....it is already being logged in Workspace#run
237
// should never get here because the lower-level build code wrappers
238
// builder exceptions in core exceptions if required.
239
String JavaDoc errorText = e.getMessage();
240                     if (errorText == null)
241                         errorText = NLS.bind(Messages.events_unknown, e.getClass().getName(), project.getName());
242                     status.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, errorText, e));
243                 }
244
245                 public void run() throws Exception JavaDoc {
246                     basicBuild(project, trigger, commands, status, monitor);
247                 }
248             };
249             SafeRunner.run(code);
250         } finally {
251             monitor.done();
252         }
253     }
254
255     /**
256      * Runs the builder with the given name on the given project.
257      * @return A status indicating if the build succeeded or failed
258      */

259     private IStatus basicBuild(IProject project, int trigger, String JavaDoc builderName, Map args, IProgressMonitor monitor) {
260         monitor = Policy.monitorFor(monitor);
261         try {
262             String JavaDoc message = NLS.bind(Messages.events_building_1, project.getFullPath());
263             monitor.beginTask(message, 1);
264             if (!canRun(trigger))
265                 return Status.OK_STATUS;
266             try {
267                 hookStartBuild(trigger);
268                 MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.events_errors, null);
269                 ICommand command = getCommand(project, builderName, args);
270                 try {
271                     IncrementalProjectBuilder builder = getBuilder(project, command, -1, status);
272                     if (builder != null)
273                         basicBuild(trigger, builder, args, status, Policy.subMonitorFor(monitor, 1));
274                 } catch (CoreException e) {
275                     status.add(e.getStatus());
276                 }
277                 return status;
278             } finally {
279                 hookEndBuild(trigger);
280             }
281         } finally {
282             monitor.done();
283         }
284     }
285
286     /**
287      * Loop the workspace build until no more builders request a rebuild.
288      */

289     private void basicBuildLoop(IProject[] ordered, IProject[] unordered, int trigger, MultiStatus status, IProgressMonitor monitor) {
290         int projectWork = ordered.length + unordered.length;
291         if (projectWork > 0)
292             projectWork = TOTAL_BUILD_WORK / projectWork;
293         int maxIterations = workspace.getDescription().getMaxBuildIterations();
294         if (maxIterations <= 0)
295             maxIterations = 1;
296         rebuildRequested = true;
297         for (int iter = 0; rebuildRequested && iter < maxIterations; iter++) {
298             rebuildRequested = false;
299             builtProjects.clear();
300             for (int i = 0; i < ordered.length; i++) {
301                 if (ordered[i].isAccessible()) {
302                     basicBuild(ordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
303                     builtProjects.add(ordered[i]);
304                 }
305             }
306             for (int i = 0; i < unordered.length; i++) {
307                 if (unordered[i].isAccessible()) {
308                     basicBuild(unordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
309                     builtProjects.add(unordered[i]);
310                 }
311             }
312             //subsequent builds should always be incremental
313
trigger = IncrementalProjectBuilder.INCREMENTAL_BUILD;
314         }
315     }
316
317     /**
318      * Runs all builders on all projects.
319      * @return A status indicating if the build succeeded or failed
320      */

321     public IStatus build(int trigger, IProgressMonitor monitor) {
322         monitor = Policy.monitorFor(monitor);
323         try {
324             monitor.beginTask(Messages.events_building_0, TOTAL_BUILD_WORK);
325             if (!canRun(trigger))
326                 return Status.OK_STATUS;
327             try {
328                 hookStartBuild(trigger);
329                 IProject[] ordered = workspace.getBuildOrder();
330                 HashSet leftover = new HashSet(Arrays.asList(workspace.getRoot().getProjects()));
331                 leftover.removeAll(Arrays.asList(ordered));
332                 IProject[] unordered = (IProject[]) leftover.toArray(new IProject[leftover.size()]);
333                 MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.BUILD_FAILED, Messages.events_errors, null);
334                 basicBuildLoop(ordered, unordered, trigger, status, monitor);
335                 return status;
336             } finally {
337                 hookEndBuild(trigger);
338             }
339         } finally {
340             monitor.done();
341             if (trigger == IncrementalProjectBuilder.INCREMENTAL_BUILD || trigger == IncrementalProjectBuilder.FULL_BUILD)
342                 autoBuildJob.avoidBuild();
343         }
344     }
345
346     /**
347      * Runs the builder with the given name on the given project.
348      * @return A status indicating if the build succeeded or failed
349      */

350     public IStatus build(IProject project, int trigger, String JavaDoc builderName, Map args, IProgressMonitor monitor) {
351         monitor = Policy.monitorFor(monitor);
352         if (builderName == null)
353             return basicBuild(project, trigger, monitor);
354         return basicBuild(project, trigger, builderName, args, monitor);
355     }
356
357     private boolean canRun(int trigger) {
358         return !building;
359     }
360
361     /**
362      * Cancel the build if the user has canceled or if an auto-build has been interrupted.
363      */

364     private void checkCanceled(int trigger, IProgressMonitor monitor) {
365         //if the system is shutting down, don't build
366
if (systemBundle.getState() == Bundle.STOPPING)
367             throw new OperationCanceledException();
368         Policy.checkCanceled(monitor);
369         //check for auto-cancel only if we are auto-building
370
if (trigger != IncrementalProjectBuilder.AUTO_BUILD)
371             return;
372         //check for request to interrupt the auto-build
373
if (autoBuildJob.isInterrupted())
374             throw new OperationCanceledException();
375     }
376
377     /**
378      * Creates and returns an ArrayList of BuilderPersistentInfo.
379      * The list includes entries for all builders that are
380      * in the builder spec, and that have a last built state, even if they
381      * have not been instantiated this session.
382      */

383     public ArrayList createBuildersPersistentInfo(IProject project) throws CoreException {
384         /* get the old builders (those not yet instantiated) */
385         ArrayList oldInfos = getBuildersPersistentInfo(project);
386
387         ICommand[] commands = ((Project) project).internalGetDescription().getBuildSpec(false);
388         if (commands.length == 0)
389             return null;
390
391         /* build the new list */
392         ArrayList newInfos = new ArrayList(commands.length);
393         for (int i = 0; i < commands.length; i++) {
394             String JavaDoc builderName = commands[i].getBuilderName();
395             BuilderPersistentInfo info = null;
396             IncrementalProjectBuilder builder = ((BuildCommand) commands[i]).getBuilder();
397             if (builder == null) {
398                 // if the builder was not instantiated, use the old info if any.
399
if (oldInfos != null)
400                     info = getBuilderInfo(oldInfos, builderName, i);
401             } else if (!(builder instanceof MissingBuilder)) {
402                 ElementTree oldTree = ((InternalBuilder) builder).getLastBuiltTree();
403                 //don't persist build state for builders that have no last built state
404
if (oldTree != null) {
405                     // if the builder was instantiated, construct a memento with the important info
406
info = new BuilderPersistentInfo(project.getName(), builderName, i);
407                     info.setLastBuildTree(oldTree);
408                     info.setInterestingProjects(((InternalBuilder) builder).getInterestingProjects());
409                 }
410             }
411             if (info != null)
412                 newInfos.add(info);
413         }
414         return newInfos;
415     }
416
417     private String JavaDoc debugBuilder() {
418         return currentBuilder == null ? "<no builder>" : currentBuilder.getClass().getName(); //$NON-NLS-1$
419
}
420
421     private String JavaDoc debugProject() {
422         if (currentBuilder == null)
423             return "<no project>"; //$NON-NLS-1$
424
return currentBuilder.getProject().getFullPath().toString();
425     }
426
427     /**
428      * Returns a string representation of a build trigger for debugging purposes.
429      * @param trigger The trigger to compute a representation of
430      * @return A string describing the trigger.
431      */

432     private String JavaDoc debugTrigger(int trigger) {
433         switch (trigger) {
434             case IncrementalProjectBuilder.FULL_BUILD :
435                 return "FULL_BUILD"; //$NON-NLS-1$
436
case IncrementalProjectBuilder.CLEAN_BUILD :
437                 return "CLEAN_BUILD"; //$NON-NLS-1$
438
case IncrementalProjectBuilder.AUTO_BUILD :
439             case IncrementalProjectBuilder.INCREMENTAL_BUILD :
440             default :
441                 return "INCREMENTAL_BUILD"; //$NON-NLS-1$
442
}
443     }
444
445     /**
446      * The outermost workspace operation has finished. Do an autobuild if necessary.
447      */

448     public void endTopLevel(boolean needsBuild) {
449         autoBuildJob.build(needsBuild);
450     }
451
452     /**
453      * Returns the value of the boolean configuration element attribute with the
454      * given name, or <code>false</code> if the attribute is missing.
455      */

456     private boolean getBooleanAttribute(IConfigurationElement element, String JavaDoc name) {
457         String JavaDoc valueString = element.getAttribute(name);
458         return valueString != null && valueString.equalsIgnoreCase(Boolean.TRUE.toString());
459     }
460
461     /**
462      * Returns the builder instance corresponding to the given command, or
463      * <code>null</code> if the builder was not valid.
464      * @param project The project this builder corresponds to
465      * @param command The build command
466      * @param buildSpecIndex The index of this builder in the build spec, or -1 if
467      * the index is unknown
468      * @param status MultiStatus for collecting errors
469      */

470     private IncrementalProjectBuilder getBuilder(IProject project, ICommand command, int buildSpecIndex, MultiStatus status) throws CoreException {
471         InternalBuilder result = ((BuildCommand) command).getBuilder();
472         if (result == null) {
473             result = initializeBuilder(command.getBuilderName(), project, buildSpecIndex, status);
474             ((BuildCommand) command).setBuilder((IncrementalProjectBuilder) result);
475             result.setCommand(command);
476             result.setProject(project);
477             result.startupOnInitialize();
478         }
479         if (!validateNature(result, command.getBuilderName())) {
480             //skip this builder and null its last built tree because it is invalid
481
//if the nature gets added or re-enabled a full build will be triggered
482
result.setLastBuiltTree(null);
483             return null;
484         }
485         return (IncrementalProjectBuilder) result;
486     }
487
488     /**
489      * Removes the builder persistent info from the map corresponding to the
490      * given builder name and build spec index, or <code>null</code> if not found
491      *
492      * @param buildSpecIndex The index in the build spec, or -1 if unknown
493      */

494     private BuilderPersistentInfo getBuilderInfo(ArrayList infos, String JavaDoc builderName, int buildSpecIndex) {
495         //try to match on builder index, but if not match is found, use the name alone
496
//this is because older workspace versions did not store builder infos in build spec order
497
BuilderPersistentInfo nameMatch = null;
498         for (Iterator it = infos.iterator(); it.hasNext();) {
499             BuilderPersistentInfo info = (BuilderPersistentInfo) it.next();
500             //match on name and build spec index if known
501
if (info.getBuilderName().equals(builderName)) {
502                 //we have found a match on name alone
503
if (nameMatch == null)
504                     nameMatch = info;
505                 //see if the index matches
506
if (buildSpecIndex == -1 || info.getBuildSpecIndex() == -1 || buildSpecIndex == info.getBuildSpecIndex())
507                     return info;
508             }
509         }
510         //no exact index match, so return name match, if any
511
return nameMatch;
512     }
513
514     /**
515      * Returns a list of BuilderPersistentInfo.
516      * The list includes entries for all builders that are in the builder spec,
517      * and that have a last built state but have not been instantiated this session.
518      */

519     public ArrayList getBuildersPersistentInfo(IProject project) throws CoreException {
520         return (ArrayList) project.getSessionProperty(K_BUILD_LIST);
521     }
522
523     /**
524      * Returns a build command for the given builder name and project.
525      * First looks for matching command in the project's build spec. If none
526      * is found, a new command is created and returned. This is necessary
527      * because IProject.build allows a builder to be executed that is not in the
528      * build spec.
529      */

530     private ICommand getCommand(IProject project, String JavaDoc builderName, Map args) {
531         ICommand[] buildSpec = ((Project) project).internalGetDescription().getBuildSpec(false);
532         for (int i = 0; i < buildSpec.length; i++)
533             if (buildSpec[i].getBuilderName().equals(builderName))
534                 return buildSpec[i];
535         //none found, so create a new command
536
BuildCommand result = new BuildCommand();
537         result.setBuilderName(builderName);
538         result.setArguments(args);
539         return result;
540     }
541
542     IResourceDelta getDelta(IProject project) {
543         try {
544             lock.acquire();
545             if (currentTree == null) {
546                 if (Policy.DEBUG_BUILD_FAILURE)
547                     Policy.debug("Build: no tree for delta " + debugBuilder() + " [" + debugProject() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
548
return null;
549             }
550             //check if this builder has indicated it cares about this project
551
if (!isInterestingProject(project)) {
552                 if (Policy.DEBUG_BUILD_FAILURE)
553                     Policy.debug("Build: project not interesting for this builder " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
554
return null;
555             }
556             //check if this project has changed
557
if (currentDelta != null && currentDelta.findNodeAt(project.getFullPath()) == null) {
558                 //if the project never existed (not in delta and not in current tree), return null
559
if (!project.exists())
560                     return null;
561                 //just return an empty delta rooted at this project
562
return ResourceDeltaFactory.newEmptyDelta(project);
563             }
564             //now check against the cache
565
IResourceDelta result = (IResourceDelta) deltaCache.getDelta(project.getFullPath(), currentLastBuiltTree, currentTree);
566             if (result != null)
567                 return result;
568
569             long startTime = 0L;
570             if (Policy.DEBUG_BUILD_DELTA) {
571                 startTime = System.currentTimeMillis();
572                 Policy.debug("Computing delta for project: " + project.getName()); //$NON-NLS-1$
573
}
574             result = ResourceDeltaFactory.computeDelta(workspace, currentLastBuiltTree, currentTree, project.getFullPath(), -1);
575             deltaCache.cache(project.getFullPath(), currentLastBuiltTree, currentTree, result);
576             if (Policy.DEBUG_BUILD_FAILURE && result == null)
577                 Policy.debug("Build: no delta " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
578
if (Policy.DEBUG_BUILD_DELTA)
579                 Policy.debug("Finished computing delta, time: " + (System.currentTimeMillis() - startTime) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
580
return result;
581         } finally {
582             lock.release();
583         }
584     }
585
586     /**
587      * Returns the safe runnable instance for invoking a builder
588      */

589     private ISafeRunnable getSafeRunnable(final int trigger, final Map args, final MultiStatus status, final IProgressMonitor monitor) {
590         return new ISafeRunnable() {
591             public void handleException(Throwable JavaDoc e) {
592                 if (e instanceof OperationCanceledException) {
593                     //just discard built state when a builder cancels, to ensure
594
//that it is called again on the very next build.
595
currentBuilder.forgetLastBuiltState();
596                     throw (OperationCanceledException) e;
597                 }
598                 //ResourceStats.buildException(e);
599
// don't log the exception....it is already being logged in SafeRunner#run
600

601                 //add a generic message to the MultiStatus
602
String JavaDoc builderName = currentBuilder.getLabel();
603                 if (builderName == null || builderName.length() == 0)
604                     builderName = currentBuilder.getClass().getName();
605                 String JavaDoc pluginId = currentBuilder.getPluginId();
606                 String JavaDoc message = NLS.bind(Messages.events_builderError, builderName, currentBuilder.getProject().getName());
607                 status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, null));
608
609                 //add the exception status to the MultiStatus
610
if (e instanceof CoreException)
611                     status.add(((CoreException) e).getStatus());
612                 else {
613                     message = e.getMessage();
614                     if (message == null)
615                         message = NLS.bind(Messages.events_unknown, e.getClass().getName(), builderName);
616                     status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, e));
617                 }
618             }
619
620             public void run() throws Exception JavaDoc {
621                 IProject[] prereqs = null;
622                 //invoke the appropriate build method depending on the trigger
623
if (trigger != IncrementalProjectBuilder.CLEAN_BUILD)
624                     prereqs = currentBuilder.build(trigger, args, monitor);
625                 else
626                     currentBuilder.clean(monitor);
627                 if (prereqs == null)
628                     prereqs = new IProject[0];
629                 currentBuilder.setInterestingProjects((IProject[]) prereqs.clone());
630             }
631         };
632     }
633
634     /**
635      * We know the work manager is always available in the middle of
636      * a build.
637      */

638     private WorkManager getWorkManager() {
639         try {
640             return workspace.getWorkManager();
641         } catch (CoreException e) {
642             //cannot happen
643
}
644         //avoid compile error
645
return null;
646     }
647
648     public void handleEvent(LifecycleEvent event) {
649         IProject project = null;
650         switch (event.kind) {
651             case LifecycleEvent.PRE_PROJECT_DELETE :
652             case LifecycleEvent.PRE_PROJECT_MOVE :
653                 project = (IProject) event.resource;
654                 //make sure the builder persistent info is deleted for the project move case
655
if (project.isAccessible())
656                     setBuildersPersistentInfo(project, null);
657         }
658     }
659
660     /**
661      * Returns true if the given project has been built during this build cycle, and
662      * false otherwise.
663      */

664     boolean hasBeenBuilt(IProject project) {
665         return builtProjects.contains(project);
666     }
667
668     /**
669      * Hook for adding trace options and debug information at the end of a build.
670      * This hook is called after each builder instance is called.
671      */

672     private void hookEndBuild(IncrementalProjectBuilder builder) {
673         if (ResourceStats.TRACE_BUILDERS)
674             ResourceStats.endBuild();
675         if (!Policy.DEBUG_BUILD_INVOKING || timeStamp == -1)
676             return; //builder wasn't called or we are not debugging
677
Policy.debug("Builder finished: " + toString(builder) + " time: " + (System.currentTimeMillis() - timeStamp) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
678
timeStamp = -1;
679     }
680
681     /**
682      * Hook for adding trace options and debug information at the end of a build.
683      * This hook is called at the end of a build cycle invoked by calling a
684      * build API method.
685      */

686     private void hookEndBuild(int trigger) {
687         building = false;
688         builtProjects.clear();
689         deltaCache.flush();
690         deltaTreeCache.flush();
691         //ensure autobuild runs after a clean
692
if (trigger == IncrementalProjectBuilder.CLEAN_BUILD)
693             autoBuildJob.forceBuild();
694     }
695
696     /**
697      * Hook for adding trace options and debug information at the start of a build.
698      * This hook is called before each builder instance is called.
699      */

700     private void hookStartBuild(IncrementalProjectBuilder builder, int trigger) {
701         if (ResourceStats.TRACE_BUILDERS)
702             ResourceStats.startBuild(builder);
703         if (Policy.DEBUG_BUILD_INVOKING) {
704             timeStamp = System.currentTimeMillis();
705             Policy.debug("Invoking (" + debugTrigger(trigger) + ") on builder: " + toString(builder)); //$NON-NLS-1$ //$NON-NLS-2$
706
}
707     }
708
709     /**
710      * Hook for adding trace options and debug information at the start of a build.
711      * This hook is called when a build API method is called, before any builders
712      * start running.
713      */

714     private void hookStartBuild(int trigger) {
715         building = true;
716         if (Policy.DEBUG_BUILD_STACK) {
717             IStatus info = new Status(IStatus.INFO, ResourcesPlugin.PI_RESOURCES, 1, "Starting build: " + debugTrigger(trigger), new RuntimeException JavaDoc().fillInStackTrace()); //$NON-NLS-1$
718
Policy.log(info);
719         }
720     }
721
722     /**
723      * Instantiates the builder with the given name. If the builder, its plugin, or its nature
724      * is missing, create a placeholder builder to takes its place. This is needed to generate
725      * appropriate exceptions when somebody tries to invoke the builder, and to
726      * prevent trying to instantiate it every time a build is run.
727      * This method NEVER returns null.
728      */

729     private IncrementalProjectBuilder initializeBuilder(String JavaDoc builderName, IProject project, int buildSpecIndex, MultiStatus status) throws CoreException {
730         IncrementalProjectBuilder builder = null;
731         try {
732             builder = instantiateBuilder(builderName);
733         } catch (CoreException e) {
734             status.add(new ResourceStatus(IResourceStatus.BUILD_FAILED, project.getFullPath(), NLS.bind(Messages.events_instantiate_1, builderName), e));
735             status.add(e.getStatus());
736         }
737         if (builder == null) {
738             //unable to create the builder, so create a placeholder to fill in for it
739
builder = new MissingBuilder(builderName);
740         }
741         // get the map of builders to get the last built tree
742
ArrayList infos = getBuildersPersistentInfo(project);
743         if (infos != null) {
744             BuilderPersistentInfo info = getBuilderInfo(infos, builderName, buildSpecIndex);
745             if (info != null) {
746                 infos.remove(info);
747                 ElementTree tree = info.getLastBuiltTree();
748                 if (tree != null)
749                     ((InternalBuilder) builder).setLastBuiltTree(tree);
750                 ((InternalBuilder) builder).setInterestingProjects(info.getInterestingProjects());
751             }
752             // delete the build map if it's now empty
753
if (infos.size() == 0)
754                 setBuildersPersistentInfo(project, null);
755         }
756         return builder;
757     }
758
759     /**
760      * Instantiates and returns the builder with the given name. If the builder, its plugin, or its nature
761      * is missing, returns null.
762      */

763     private IncrementalProjectBuilder instantiateBuilder(String JavaDoc builderName) throws CoreException {
764         IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, builderName);
765         if (extension == null)
766             return null;
767         IConfigurationElement[] configs = extension.getConfigurationElements();
768         if (configs.length == 0)
769             return null;
770         String JavaDoc natureId = null;
771         if (getBooleanAttribute(configs[0], "hasNature")) { //$NON-NLS-1$
772
//find the nature that owns this builder
773
String JavaDoc builderId = extension.getUniqueIdentifier();
774             natureId = workspace.getNatureManager().findNatureForBuilder(builderId);
775             if (natureId == null)
776                 return null;
777         }
778         //The nature exists, or this builder doesn't specify a nature
779
InternalBuilder builder = (InternalBuilder) configs[0].createExecutableExtension("run"); //$NON-NLS-1$
780
builder.setPluginId(extension.getContributor().getName());
781         builder.setLabel(extension.getLabel());
782         builder.setNatureId(natureId);
783         builder.setCallOnEmptyDelta(getBooleanAttribute(configs[0], "callOnEmptyDelta")); //$NON-NLS-1$
784
return (IncrementalProjectBuilder) builder;
785     }
786
787     /**
788      * Another thread is attempting to modify the workspace. Cancel the
789      * autobuild and wait until it completes.
790      */

791     public void interrupt() {
792         autoBuildJob.interrupt();
793     }
794
795     /**
796      * Returns true if the current builder is interested in changes
797      * to the given project, and false otherwise.
798      */

799     private boolean isInterestingProject(IProject project) {
800         if (project.equals(currentBuilder.getProject()))
801             return true;
802         IProject[] interestingProjects = currentBuilder.getInterestingProjects();
803         for (int i = 0; i < interestingProjects.length; i++) {
804             if (interestingProjects[i].equals(project)) {
805                 return true;
806             }
807         }
808         return false;
809     }
810
811     /**
812      * Returns true if the given builder needs to be invoked, and false
813      * otherwise.
814      *
815      * The algorithm is to compute the intersection of the set of projects that
816      * have changed since the last build, and the set of projects this builder
817      * cares about. This is an optimization, under the assumption that computing
818      * the forward delta once (not the resource delta) is more efficient than
819      * computing project deltas and invoking builders for projects that haven't
820      * changed.
821      */

822     private boolean needsBuild(InternalBuilder builder, int trigger) {
823         //on some triggers we build regardless of the delta
824
switch (trigger) {
825             case IncrementalProjectBuilder.CLEAN_BUILD :
826                 return true;
827             case IncrementalProjectBuilder.FULL_BUILD :
828                 return true;
829             case IncrementalProjectBuilder.INCREMENTAL_BUILD :
830                 if (currentBuilder.callOnEmptyDelta())
831                     return true;
832                 //fall through and check if there is a delta
833
}
834
835         //compute the delta since the last built state
836
ElementTree oldTree = builder.getLastBuiltTree();
837         ElementTree newTree = workspace.getElementTree();
838         long start = System.currentTimeMillis();
839         currentDelta = (DeltaDataTree) deltaTreeCache.getDelta(null, oldTree, newTree);
840         if (currentDelta == null) {
841             if (Policy.DEBUG_BUILD_NEEDED) {
842                 String JavaDoc message = "Checking if need to build. Starting delta computation between: " + oldTree.toString() + " and " + newTree.toString(); //$NON-NLS-1$ //$NON-NLS-2$
843
Policy.debug(message);
844             }
845             currentDelta = newTree.getDataTree().forwardDeltaWith(oldTree.getDataTree(), ResourceComparator.getBuildComparator());
846             if (Policy.DEBUG_BUILD_NEEDED)
847                 Policy.debug("End delta computation. (" + (System.currentTimeMillis() - start) + "ms)."); //$NON-NLS-1$ //$NON-NLS-2$
848
deltaTreeCache.cache(null, oldTree, newTree, currentDelta);
849         }
850
851         //search for the builder's project
852
if (currentDelta.findNodeAt(builder.getProject().getFullPath()) != null) {
853             if (Policy.DEBUG_BUILD_NEEDED)
854                 Policy.debug(toString(builder) + " needs building because of changes in: " + builder.getProject().getName()); //$NON-NLS-1$
855
return true;
856         }
857
858         //search for builder's interesting projects
859
IProject[] projects = builder.getInterestingProjects();
860         for (int i = 0; i < projects.length; i++) {
861             if (currentDelta.findNodeAt(projects[i].getFullPath()) != null) {
862                 if (Policy.DEBUG_BUILD_NEEDED)
863                     Policy.debug(toString(builder) + " needs building because of changes in: " + projects[i].getName()); //$NON-NLS-1$
864
return true;
865             }
866         }
867         return false;
868     }
869
870     /**
871      * Removes all builders with the given ID from the build spec.
872      * Does nothing if there were no such builders in the spec
873      */

874     private void removeBuilders(IProject project, String JavaDoc builderId) throws CoreException {
875         IProjectDescription desc = project.getDescription();
876         ICommand[] oldSpec = desc.getBuildSpec();
877         int oldLength = oldSpec.length;
878         if (oldLength == 0)
879             return;
880         int remaining = 0;
881         //null out all commands that match the builder to remove
882
for (int i = 0; i < oldSpec.length; i++) {
883             if (oldSpec[i].getBuilderName().equals(builderId))
884                 oldSpec[i] = null;
885             else
886                 remaining++;
887         }
888         //check if any were actually removed
889
if (remaining == oldSpec.length)
890             return;
891         ICommand[] newSpec = new ICommand[remaining];
892         for (int i = 0, newIndex = 0; i < oldLength; i++) {
893             if (oldSpec[i] != null)
894                 newSpec[newIndex++] = oldSpec[i];
895         }
896         desc.setBuildSpec(newSpec);
897         project.setDescription(desc, IResource.NONE, null);
898     }
899
900     /**
901      * Hook for builders to request a rebuild.
902      */

903     void requestRebuild() {
904         rebuildRequested = true;
905     }
906
907     /**
908      * Sets the builder infos for the given project. The builder infos are
909      * an ArrayList of BuilderPersistentInfo.
910      * The list includes entries for all builders that are
911      * in the builder spec, and that have a last built state, even if they
912      * have not been instantiated this session.
913      */

914     public void setBuildersPersistentInfo(IProject project, ArrayList list) {
915         try {
916             project.setSessionProperty(K_BUILD_LIST, list);
917         } catch (CoreException e) {
918             //project is missing -- build state will be lost
919
//can't throw an exception because this happens on startup
920
Policy.log(new ResourceStatus(IStatus.ERROR, 1, project.getFullPath(), "Project missing in setBuildersPersistentInfo", null)); //$NON-NLS-1$
921
}
922     }
923
924     public void shutdown(IProgressMonitor monitor) {
925         autoBuildJob.cancel();
926     }
927
928     public void startup(IProgressMonitor monitor) {
929         workspace.addLifecycleListener(this);
930     }
931
932     /**
933      * Returns a string representation of the given builder.
934      * For debugging purposes only.
935      */

936     private String JavaDoc toString(InternalBuilder builder) {
937         String JavaDoc name = builder.getClass().getName();
938         name = name.substring(name.lastIndexOf('.') + 1);
939         return name + "(" + builder.getProject().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
940
}
941
942     /**
943      * Returns true if the nature membership rules are satisfied for the given
944      * builder extension on the given project, and false otherwise. A builder that
945      * does not specify that it belongs to a nature is always valid. A builder
946      * extension that belongs to a nature can be invalid for the following reasons:
947      * <ul>
948      * <li>The nature that owns the builder does not exist on the given project</li>
949      * <li>The nature that owns the builder is disabled on the given project</li>
950      * </ul>
951      * Furthermore, if the nature that owns the builder does not exist on the project,
952      * that builder will be removed from the build spec.
953      *
954      * Note: This method only validates nature constraints that can vary at runtime.
955      * Additional checks are done in the instantiateBuilder method for constraints
956      * that cannot vary once the plugin registry is initialized.
957      */

958     private boolean validateNature(InternalBuilder builder, String JavaDoc builderId) throws CoreException {
959         String JavaDoc nature = builder.getNatureId();
960         if (nature == null)
961             return true;
962         IProject project = builder.getProject();
963         if (!project.hasNature(nature)) {
964             //remove this builder from the build spec
965
removeBuilders(project, builderId);
966             return false;
967         }
968         return project.isNatureEnabled(nature);
969     }
970 }
971
Popular Tags