Source code: org/eclipse/core/internal/events/BuildManager.java
1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-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.Assert;
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.osgi.framework.Bundle;
23
24 public class BuildManager implements ICoreConstants, IManager, ILifecycleListener {
25
26 private static final int TOTAL_BUILD_WORK = Policy.totalWork * 1000;
27
28 /**
29 * Cache used to optimize the common case of an autobuild against
30 * a workspace where only a single project has changed (and hence
31 * only a single delta is interesting).
32 */
33 class DeltaCache {
34 private Object delta;
35 private ElementTree newTree;
36 private ElementTree oldTree;
37 private IPath projectPath;
38
39 public void cache(IPath project, ElementTree oldTree, ElementTree newTree, Object delta) {
40 this.projectPath = project;
41 this.oldTree = oldTree;
42 this.newTree = newTree;
43 this.delta = delta;
44 }
45
46 public void flush() {
47 this.projectPath = null;
48 this.oldTree = null;
49 this.newTree = null;
50 this.delta = null;
51 }
52
53 /**
54 * Returns the cached resource delta for the given project and trees, or
55 * null if there is no matching delta in the cache.
56 */
57 public Object getDelta(IPath project, ElementTree oldTree, ElementTree newTree) {
58 if (delta == null)
59 return null;
60 boolean pathsEqual = projectPath == null ? project == null : projectPath.equals(project);
61 if (pathsEqual && this.oldTree == oldTree && this.newTree == newTree)
62 return delta;
63 return null;
64 }
65 }
66
67 /**
68 * These builders are added to build tables in place of builders that couldn't be instantiated
69 */
70 class MissingBuilder extends IncrementalProjectBuilder {
71 private boolean hasBeenBuilt = false;
72 private String name;
73
74 MissingBuilder(String name) {
75 this.name = name;
76 }
77
78 /**
79 * Log an exception on the first build, and silently do nothing on subsequent builds.
80 */
81 protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
82 if (!hasBeenBuilt) {
83 hasBeenBuilt = true;
84 String msg = Policy.bind("events.skippingBuilder", new String[] {name, getProject().getName()}); //$NON-NLS-1$
85 IStatus status = new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, 1, msg, null);
86 ResourcesPlugin.getPlugin().getLog().log(status);
87 }
88 return null;
89 }
90 }
91
92 //the job for performing background autobuild
93 protected final AutoBuildJob autoBuildJob;
94 protected boolean building = false;
95 protected final ArrayList builtProjects = new ArrayList();
96 protected InternalBuilder currentBuilder;
97 protected DeltaDataTree currentDelta;
98
99 //the following four fields only apply for the lifetime of
100 //a single builder invocation.
101 protected ElementTree currentTree;
102 /**
103 * Caches the IResourceDelta for a pair of trees
104 */
105 final protected DeltaCache deltaCache = new DeltaCache();
106 /**
107 * Caches the DeltaDataTree used to determine if a build is necessary
108 */
109 final protected DeltaCache deltaTreeCache = new DeltaCache();
110
111 protected ElementTree lastBuiltTree;
112
113 //used for the build cycle looping mechanism
114 protected boolean rebuildRequested = false;
115
116 private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
117
118 //used for debug/trace timing
119 private long timeStamp = -1;
120 protected Workspace workspace;
121 private ILock lock;
122
123 public BuildManager(Workspace workspace, ILock workspaceLock) {
124 this.workspace = workspace;
125 this.autoBuildJob = new AutoBuildJob(workspace);
126 this.lock = workspaceLock;
127 }
128
129 protected void basicBuild(int trigger, IncrementalProjectBuilder builder, Map args, MultiStatus status, IProgressMonitor monitor) {
130 try {
131 currentBuilder = builder;
132 //clear any old requests to forget built state
133 currentBuilder.clearForgetLastBuiltState();
134 // Figure out want kind of build is needed
135 boolean clean = trigger == IncrementalProjectBuilder.CLEAN_BUILD;
136 lastBuiltTree = currentBuilder.getLastBuiltTree();
137 // If no tree is available we have to do a full build
138 if (!clean && lastBuiltTree == null)
139 trigger = IncrementalProjectBuilder.FULL_BUILD;
140 boolean fullBuild = trigger == IncrementalProjectBuilder.FULL_BUILD;
141 // Grab a pointer to the current state before computing the delta
142 currentTree = fullBuild ? null : workspace.getElementTree();
143 int depth = -1;
144 try {
145 //short-circuit if none of the projects this builder cares about have changed.
146 if (!clean && !fullBuild && !needsBuild(currentBuilder))
147 return;
148 String name = currentBuilder.getLabel();
149 String message;
150 if (name != null)
151 message = Policy.bind("events.invoking.2", name, builder.getProject().getFullPath().toString()); //$NON-NLS-1$
152 else
153 message = Policy.bind("events.invoking.1", builder.getProject().getFullPath().toString()); //$NON-NLS-1$
154 monitor.subTask(message);
155 hookStartBuild(builder, trigger);
156 //release workspace lock while calling builders
157 depth = getWorkManager().beginUnprotected();
158 //do the build
159 Platform.run(getSafeRunnable(trigger, args, status, monitor));
160 } finally {
161 if (depth >= 0)
162 getWorkManager().endUnprotected(depth);
163 // Be sure to clean up after ourselves.
164 if (clean || currentBuilder.wasForgetStateRequested()) {
165 currentBuilder.setLastBuiltTree(null);
166 } else {
167 // remember the current state as the last built state.
168 ElementTree lastTree = workspace.getElementTree();
169 lastTree.immutable();
170 currentBuilder.setLastBuiltTree(lastTree);
171 }
172 hookEndBuild(builder);
173 }
174 } finally {
175 currentBuilder = null;
176 currentTree = null;
177 lastBuiltTree = null;
178 currentDelta = null;
179 }
180 }
181 /**
182 * We know the work manager is always available in the middle of
183 * a build.
184 */
185 private WorkManager getWorkManager() {
186 try {
187 return workspace.getWorkManager();
188 } catch (CoreException e) {
189 //cannot happen
190 }
191 //avoid compile error
192 return null;
193 }
194
195 protected void basicBuild(IProject project, int trigger, ICommand[] commands, MultiStatus status, IProgressMonitor monitor) {
196 monitor = Policy.monitorFor(monitor);
197 try {
198 String message = Policy.bind("events.building.1", project.getFullPath().toString()); //$NON-NLS-1$
199 monitor.beginTask(message, Math.max(1, commands.length));
200 for (int i = 0; i < commands.length; i++) {
201 checkCanceled(trigger, monitor);
202 IProgressMonitor sub = Policy.subMonitorFor(monitor, 1);
203 BuildCommand command = (BuildCommand) commands[i];
204 basicBuild(project, trigger, command.getBuilderName(), command.getArguments(false), status, sub);
205 }
206 } finally {
207 monitor.done();
208 }
209 }
210
211 protected void basicBuild(final IProject project, final int trigger, final MultiStatus status, final IProgressMonitor monitor) {
212 if (!project.isAccessible())
213 return;
214 final ICommand[] commands = ((Project) project).internalGetDescription().getBuildSpec(false);
215 if (commands.length == 0)
216 return;
217 ISafeRunnable code = new ISafeRunnable() {
218 public void handleException(Throwable e) {
219 if (e instanceof OperationCanceledException)
220 throw (OperationCanceledException) e;
221 // don't log the exception....it is already being logged in Workspace#run
222 // should never get here because the lower-level build code wrappers
223 // builder exceptions in core exceptions if required.
224 String message = e.getMessage();
225 if (message == null)
226 message = Policy.bind("events.unknown", e.getClass().getName(), currentBuilder.getClass().getName()); //$NON-NLS-1$
227 status.add(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e));
228 }
229
230 public void run() throws Exception {
231 basicBuild(project, trigger, commands, status, monitor);
232 }
233 };
234 Platform.run(code);
235 }
236
237 protected void basicBuild(IProject project, int trigger, String builderName, Map args, MultiStatus status, IProgressMonitor monitor) {
238 IncrementalProjectBuilder builder = null;
239 try {
240 builder = getBuilder(builderName, project, status);
241 if (!validateNature(builder, builderName)) {
242 //skip this builder and null its last built tree because it is invalid
243 //if the nature gets added or re-enabled a full build will be triggered
244 ((InternalBuilder) builder).setLastBuiltTree(null);
245 return;
246 }
247 } catch (CoreException e) {
248 status.add(e.getStatus());
249 return;
250 }
251 basicBuild(trigger, builder, args, status, monitor);
252 }
253
254 /**
255 * Loop the workspace build until no more builders request a rebuild.
256 */
257 protected void basicBuildLoop(IProject[] ordered, IProject[] unordered, int trigger, MultiStatus status, IProgressMonitor monitor) {
258 int projectWork = ordered.length + unordered.length;
259 if (projectWork > 0)
260 projectWork = TOTAL_BUILD_WORK / projectWork;
261 int maxIterations = workspace.getDescription().getMaxBuildIterations();
262 if (maxIterations <= 0)
263 maxIterations = 1;
264 rebuildRequested = true;
265 for (int iter = 0; rebuildRequested && iter < maxIterations; iter++) {
266 rebuildRequested = false;
267 builtProjects.clear();
268 for (int i = 0; i < ordered.length; i++) {
269 if (ordered[i].isAccessible()) {
270 basicBuild(ordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
271 builtProjects.add(ordered[i]);
272 }
273 }
274 for (int i = 0; i < unordered.length; i++) {
275 if (unordered[i].isAccessible()) {
276 basicBuild(unordered[i], trigger, status, Policy.subMonitorFor(monitor, projectWork));
277 builtProjects.add(unordered[i]);
278 }
279 }
280 //subsequent builds should always be incremental
281 trigger = IncrementalProjectBuilder.INCREMENTAL_BUILD;
282 }
283 }
284
285 public void build(int trigger, IProgressMonitor monitor) throws CoreException {
286 monitor = Policy.monitorFor(monitor);
287 try {
288 monitor.beginTask(ICoreConstants.MSG_EVENTS_BUILDING_0, TOTAL_BUILD_WORK);
289 if (!canRun(trigger))
290 return;
291 try {
292 building = true;
293 IProject[] ordered = workspace.getBuildOrder();
294 HashSet leftover = new HashSet(Arrays.asList(workspace.getRoot().getProjects()));
295 leftover.removeAll(Arrays.asList(ordered));
296 IProject[] unordered = (IProject[]) leftover.toArray(new IProject[leftover.size()]);
297 MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.BUILD_FAILED, ICoreConstants.MSG_EVENTS_ERRORS, null);
298
299 basicBuildLoop(ordered, unordered, trigger, status, monitor);
300
301 // if the status is not ok, throw an exception
302 if (!status.isOK())
303 throw new ResourceException(status);
304 } finally {
305 cleanup(trigger);
306 }
307 } finally {
308 monitor.done();
309 if (trigger == IncrementalProjectBuilder.INCREMENTAL_BUILD || trigger == IncrementalProjectBuilder.FULL_BUILD)
310 autoBuildJob.avoidBuild();
311 }
312 }
313
314 private void cleanup(int trigger) {
315 building = false;
316 builtProjects.clear();
317 deltaCache.flush();
318 deltaTreeCache.flush();
319 //ensure autobuild runs after a clean
320 if (trigger == IncrementalProjectBuilder.CLEAN_BUILD)
321 autoBuildJob.forceBuild();
322 }
323
324 public void build(IProject project, int trigger, IProgressMonitor monitor) throws CoreException {
325 if (!canRun(trigger))
326 return;
327 try {
328 building = true;
329 MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, ICoreConstants.MSG_EVENTS_ERRORS, null);
330 basicBuild(project, trigger, status, monitor);
331 if (!status.isOK())
332 throw new ResourceException(status);
333 } finally {
334 cleanup(trigger);
335 }
336 }
337
338 public void build(IProject project, int trigger, String builderName, Map args, IProgressMonitor monitor) throws CoreException {
339 monitor = Policy.monitorFor(monitor);
340 try {
341 String message = Policy.bind("events.building.1", project.getFullPath().toString()); //$NON-NLS-1$
342 monitor.beginTask(message, 1);
343 if (!canRun(trigger))
344 return;
345 try {
346 building = true;
347 MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, ICoreConstants.MSG_EVENTS_ERRORS, null);
348 basicBuild(project, trigger, builderName, args, status, Policy.subMonitorFor(monitor, 1));
349 if (!status.isOK())
350 throw new ResourceException(status);
351 } finally {
352 cleanup(trigger);
353 }
354 } finally {
355 monitor.done();
356 }
357 }
358
359 protected boolean canRun(int trigger) {
360 return !building;
361 }
362
363 /**
364 * Another thread is attempting to modify the workspace. Cancel the
365 * autobuild and wait until it completes.
366 */
367 public void interrupt() {
368 autoBuildJob.interrupt();
369 }
370
371 /**
372 * Cancel the build if the user has canceled or if an auto-build has been interrupted.
373 */
374 private void checkCanceled(int trigger, IProgressMonitor monitor) {
375 //if the system is shutting down, don't build
376 if (systemBundle.getState() == Bundle.STOPPING)
377 throw new OperationCanceledException();
378 Policy.checkCanceled(monitor);
379 //check for auto-cancel only if we are auto-building
380 if (trigger != IncrementalProjectBuilder.AUTO_BUILD)
381 return;
382 //check for request to interrupt the auto-build
383 if (autoBuildJob.isInterrupted())
384 throw new OperationCanceledException();
385 }
386
387 protected IProject[] computeUnorderedProjects(IProject[] ordered) {
388 IProject[] unordered;
389 HashSet leftover = new HashSet(Arrays.asList(workspace.getRoot().getProjects()));
390 leftover.removeAll(Arrays.asList(ordered));
391 unordered = (IProject[]) leftover.toArray(new IProject[leftover.size()]);
392 return unordered;
393 }
394
395 /**
396 * Creates and returns a Map mapping String(builder name) -> BuilderPersistentInfo.
397 * The table includes entries for all builders that are
398 * in the builder spec, and that have a last built state, even if they
399 * have not been instantiated this session.
400 */
401 public Map createBuildersPersistentInfo(IProject project) throws CoreException {
402 /* get the old builder map */
403 Map oldInfos = getBuildersPersistentInfo(project);
404
405 ICommand[] buildCommands = ((Project) project).internalGetDescription().getBuildSpec(false);
406 if (buildCommands.length == 0)
407 return null;
408
409 /* build the new map */
410 Map newInfos = new HashMap(buildCommands.length * 2);
411 Hashtable instantiatedBuilders = getBuilders(project);
412 for (int i = 0; i < buildCommands.length; i++) {
413 String builderName = buildCommands[i].getBuilderName();
414 BuilderPersistentInfo info = null;
415 IncrementalProjectBuilder builder = (IncrementalProjectBuilder) instantiatedBuilders.get(builderName);
416 if (builder == null) {
417 // if the builder was not instantiated, use the old info if any.
418 if (oldInfos != null)
419 info = (BuilderPersistentInfo) oldInfos.get(builderName);
420 } else if (!(builder instanceof MissingBuilder)) {
421 ElementTree oldTree = ((InternalBuilder) builder).getLastBuiltTree();
422 //don't persist build state for builders that have no last built state
423 if (oldTree != null) {
424 // if the builder was instantiated, construct a memento with the important info
425 info = new BuilderPersistentInfo();
426 info.setProjectName(project.getName());
427 info.setBuilderName(builderName);
428 info.setLastBuildTree(oldTree);
429 info.setInterestingProjects(((InternalBuilder) builder).getInterestingProjects());
430 }
431 }
432 if (info != null)
433 newInfos.put(builderName, info);
434 }
435 return newInfos;
436 }
437
438 protected String debugBuilder() {
439 return currentBuilder == null ? "<no builder>" : currentBuilder.getClass().getName(); //$NON-NLS-1$
440 }
441
442 protected String debugProject() {
443 if (currentBuilder == null)
444 return "<no project>"; //$NON-NLS-1$
445 return currentBuilder.getProject().getFullPath().toString();
446 }
447
448 /**
449 * The outermost workspace operation has finished. Do an autobuild if necessary.
450 */
451 public void endTopLevel(boolean needsBuild) {
452 autoBuildJob.build(needsBuild);
453 }
454
455 protected IncrementalProjectBuilder getBuilder(String builderName, IProject project, MultiStatus status) throws CoreException {
456 Hashtable builders = getBuilders(project);
457 IncrementalProjectBuilder result = (IncrementalProjectBuilder) builders.get(builderName);
458 if (result != null)
459 return result;
460 result = initializeBuilder(builderName, project, status);
461 builders.put(builderName, result);
462 ((InternalBuilder) result).setProject(project);
463 ((InternalBuilder) result).startupOnInitialize();
464 return result;
465 }
466
467 /**
468 * Returns a hashtable of all instantiated builders for the given project.
469 * This hashtable maps String(builder name) -> Builder.
470 */
471 protected Hashtable getBuilders(IProject project) {
472 ProjectInfo info = (ProjectInfo) workspace.getResourceInfo(project.getFullPath(), false, false);
473 if (info == null)
474 Assert.isNotNull(info, Policy.bind("events.noProject", project.getName())); //$NON-NLS-1$
475 return info.getBuilders();
476 }
477
478 /**
479 * Returns a Map mapping String(builder name) -> BuilderPersistentInfo.
480 * The map includes entries for all builders that are in the builder spec,
481 * and that have a last built state, even if they have not been instantiated
482 * this session.
483 */
484 public Map getBuildersPersistentInfo(IProject project) throws CoreException {
485 return (Map) project.getSessionProperty(K_BUILD_MAP);
486 }
487
488 protected IResourceDelta getDelta(IProject project) {
489 try {
490 lock.acquire();
491 if (currentTree == null) {
492 if (Policy.DEBUG_BUILD_FAILURE)
493 Policy.debug("Build: no tree for delta " + debugBuilder() + " [" + debugProject() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
494 return null;
495 }
496 //check if this builder has indicated it cares about this project
497 if (!isInterestingProject(project)) {
498 if (Policy.DEBUG_BUILD_FAILURE)
499 Policy.debug("Build: project not interesting for this builder " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
500 return null;
501 }
502 //check if this project has changed
503 if (currentDelta != null && currentDelta.findNodeAt(project.getFullPath()) == null) {
504 //if the project never existed (not in delta and not in current tree), return null
505 if (!project.exists())
506 return null;
507 //just return an empty delta rooted at this project
508 return ResourceDeltaFactory.newEmptyDelta(project);
509 }
510 //now check against the cache
511 IResourceDelta result = (IResourceDelta) deltaCache.getDelta(project.getFullPath(), lastBuiltTree, currentTree);
512 if (result != null)
513 return result;
514
515 long startTime = 0L;
516 if (Policy.DEBUG_BUILD_DELTA) {
517 startTime = System.currentTimeMillis();
518 Policy.debug("Computing delta for project: " + project.getName()); //$NON-NLS-1$
519 }
520 result = ResourceDeltaFactory.computeDelta(workspace, lastBuiltTree, currentTree, project.getFullPath(), -1);
521 deltaCache.cache(project.getFullPath(), lastBuiltTree, currentTree, result);
522 if (Policy.DEBUG_BUILD_FAILURE && result == null)
523 Policy.debug("Build: no delta " + debugBuilder() + " [" + debugProject() + "] " + project.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
524 if (Policy.DEBUG_BUILD_DELTA)
525 Policy.debug("Finished computing delta, time: " + (System.currentTimeMillis() - startTime) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
526 return result;
527 } finally {
528 lock.release();
529 }
530 }
531
532 /**
533 * Returns the safe runnable instance for invoking a builder
534 */
535 protected ISafeRunnable getSafeRunnable(final int trigger, final Map args, final MultiStatus status, final IProgressMonitor monitor) {
536 return new ISafeRunnable() {
537 public void handleException(Throwable e) {
538 if (e instanceof OperationCanceledException) {
539 // discard tree state just in case builders did not clean
540 // up after monitor was cancelled.
541 currentBuilder.forgetLastBuiltState();
542 throw (OperationCanceledException) e;
543 }
544 //ResourceStats.buildException(e);
545 // don't log the exception....it is already being logged in Platform#run
546
547 //add a generic message to the MultiStatus
548 String builderName = currentBuilder.getLabel();
549 if (builderName == null || builderName.length() == 0)
550 builderName = currentBuilder.getClass().getName();
551 String pluginId = currentBuilder.getPluginDescriptor().getUniqueIdentifier();
552 String message = Policy.bind("events.builderError", builderName, currentBuilder.getProject().getName()); //$NON-NLS-1$
553 status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, null));
554
555 //add the exception status to the MultiStatus
556 if (e instanceof CoreException)
557 status.add(((CoreException) e).getStatus());
558 else {
559 message = e.getMessage();
560 if (message == null)
561 message = Policy.bind("events.unknown", e.getClass().getName(), builderName); //$NON-NLS-1$
562 status.add(new Status(IStatus.WARNING, pluginId, IResourceStatus.BUILD_FAILED, message, e));
563 }
564 }
565
566 public void run() throws Exception {
567 IProject[] prereqs = null;
568 //invoke the appropriate build method depending on the trigger
569 if (trigger != IncrementalProjectBuilder.CLEAN_BUILD)
570 prereqs = currentBuilder.build(trigger, args, monitor);
571 else
572 currentBuilder.clean(monitor);
573 if (prereqs == null)
574 prereqs = new IProject[0];
575 currentBuilder.setInterestingProjects((IProject[]) prereqs.clone());
576 }
577 };
578 }
579
580 public void handleEvent(LifecycleEvent event) {
581 IProject project = null;
582 switch (event.kind) {
583 case LifecycleEvent.PRE_PROJECT_DELETE :
584 case LifecycleEvent.PRE_PROJECT_MOVE :
585 project = (IProject) event.resource;
586 //make sure the builder persistent info is deleted for the project move case
587 if (project.isAccessible())
588 setBuildersPersistentInfo(project, null);
589 }
590 }
591
592 /**
593 * Returns true if the given project has been built during this build cycle, and
594 * false otherwise.
595 */
596 public boolean hasBeenBuilt(IProject project) {
597 return builtProjects.contains(project);
598 }
599
600 /**
601 * Hook for adding trace options and debug information at the end of a build.
602 */
603 private void hookEndBuild(IncrementalProjectBuilder builder) {
604 if (Policy.MONITOR_BUILDERS)
605 EventStats.endBuild();
606 if (!Policy.DEBUG_BUILD_INVOKING || timeStamp == -1)
607 return; //builder wasn't called or we are not debugging
608 Policy.debug("Builder finished: " + toString(builder) + " time: " + (System.currentTimeMillis() - timeStamp) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
609 timeStamp = -1;
610 }
611
612 /**
613 * Hook for adding trace options and debug information at the start of a build.
614 */
615 private void hookStartBuild(IncrementalProjectBuilder builder, int trigger) {
616 if (Policy.MONITOR_BUILDERS)
617 EventStats.startBuild(builder);
618 if (Policy.DEBUG_BUILD_INVOKING) {
619 timeStamp = System.currentTimeMillis();
620 String type;
621 switch (trigger) {
622 case IncrementalProjectBuilder.FULL_BUILD :
623 type = "FULL_BUILD"; //$NON-NLS-1$
624 break;
625 case IncrementalProjectBuilder.INCREMENTAL_BUILD :
626 default :
627 type = "INCREMENTAL_BUILD"; //$NON-NLS-1$
628 }
629 Policy.debug("Invoking (" + type + ") on builder: " + toString(builder)); //$NON-NLS-1$ //$NON-NLS-2$
630 }
631 }
632
633 /**
634 * Instantiates the builder with the given name. If the builder, its plugin, or its nature
635 * is missing, create a placeholder builder to takes its place. This is needed to generate
636 * appropriate exceptions when somebody tries to invoke the builder, and to
637 * prevent trying to instantiate it every time a build is run.
638 * This method NEVER returns null.
639 */
640 protected IncrementalProjectBuilder initializeBuilder(String builderName, IProject project, MultiStatus status) throws CoreException {
641 IncrementalProjectBuilder builder = null;
642 try {
643 builder = instantiateBuilder(builderName);
644 } catch (CoreException e) {
645 status.add(new ResourceStatus(IResourceStatus.BUILD_FAILED, project.getFullPath(), Policy.bind("events.instantiate.1", builderName), e)); //$NON-NLS-1$
646 status.add(e.getStatus());
647 }
648 if (builder == null) {
649 //unable to create the builder, so create a placeholder to fill in for it
650 builder = new MissingBuilder(builderName);
651 }
652 // get the map of builders to get the last built tree
653 Map infos = getBuildersPersistentInfo(project);
654 if (infos != null) {
655 BuilderPersistentInfo info = (BuilderPersistentInfo) infos.remove(builderName);
656 if (info != null) {
657 ElementTree tree = info.getLastBuiltTree();
658 if (tree != null)
659 ((InternalBuilder) builder).setLastBuiltTree(tree);
660 ((InternalBuilder) builder).setInterestingProjects(info.getInterestingProjects());
661 }
662 // delete the build map if it's now empty
663 if (infos.size() == 0)
664 setBuildersPersistentInfo(project, null);
665 }
666 return builder;
667 }
668
669 /**
670 * Instantiates and returns the builder with the given name. If the builder, its plugin, or its nature
671 * is missing, returns null.
672 */
673 protected IncrementalProjectBuilder instantiateBuilder(String builderName) throws CoreException {
674 IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, builderName);
675 if (extension == null)
676 return null;
677 IConfigurationElement[] configs = extension.getConfigurationElements();
678 if (configs.length == 0)
679 return null;
680 String hasNature = configs[0].getAttribute("hasNature"); //$NON-NLS-1$
681 String natureId = null;
682 if (hasNature != null && hasNature.equalsIgnoreCase(Boolean.TRUE.toString())) {
683 //find the nature that owns this builder
684 String builderId = extension.getUniqueIdentifier();
685 natureId = workspace.getNatureManager().findNatureForBuilder(builderId);
686 if (natureId == null)
687 return null;
688 }
689 //The nature exists, or this builder doesn't specify a nature
690 InternalBuilder builder = (InternalBuilder) configs[0].createExecutableExtension("run"); //$NON-NLS-1$
691 builder.setPluginDescriptor(extension.getDeclaringPluginDescriptor());
692 builder.setLabel(extension.getLabel());
693 builder.setNatureId(natureId);
694 return (IncrementalProjectBuilder) builder;
695 }
696
697 /**
698 * Returns true if the current builder is interested in changes
699 * to the given project, and false otherwise.
700 */
701 protected boolean isInterestingProject(IProject project) {
702 if (project.equals(currentBuilder.getProject()))
703 return true;
704 IProject[] interestingProjects = currentBuilder.getInterestingProjects();
705 for (int i = 0; i < interestingProjects.length; i++) {
706 if (interestingProjects[i].equals(project)) {
707 return true;
708 }
709 }
710 return false;
711 }
712
713 /**
714 * Returns true if the given builder needs to be invoked, and false
715 * otherwise.
716 *
717 * The algorithm is to compute the intersection of the set of projects that
718 * have changed since the last build, and the set of projects this builder
719 * cares about. This is an optimization, under the assumption that computing
720 * the forward delta once (not the resource delta) is more efficient than
721 * computing project deltas and invoking builders for projects that haven't
722 * changed.
723 */
724 protected boolean needsBuild(InternalBuilder builder) {
725 //compute the delta since the last built state
726 ElementTree oldTree = builder.getLastBuiltTree();
727 ElementTree newTree = workspace.getElementTree();
728 long start = System.currentTimeMillis();
729 currentDelta = (DeltaDataTree) deltaTreeCache.getDelta(null, oldTree, newTree);
730 if (currentDelta == null) {
731 if (Policy.DEBUG_NEEDS_BUILD) {
732 String message = "Checking if need to build. Starting delta computation between: " + oldTree.toString() + " and " + newTree.toString(); //$NON-NLS-1$ //$NON-NLS-2$
733 Policy.debug(message);
734 }
735 currentDelta = newTree.getDataTree().forwardDeltaWith(oldTree.getDataTree(), ResourceComparator.getComparator(false));
736 if (Policy.DEBUG_NEEDS_BUILD)
737 Policy.debug("End delta computation. (" + (System.currentTimeMillis() - start) + "ms)."); //$NON-NLS-1$ //$NON-NLS-2$
738 deltaTreeCache.cache(null, oldTree, newTree, currentDelta);
739 }
740
741 //search for the builder's project
742 if (currentDelta.findNodeAt(builder.getProject().getFullPath()) != null) {
743 if (Policy.DEBUG_NEEDS_BUILD)
744 Policy.debug(toString(builder) + " needs building because of changes in: " + builder.getProject().getName()); //$NON-NLS-1$
745 return true;
746 }
747
748 //search for builder's interesting projects
749 IProject[] projects = builder.getInterestingProjects();
750 for (int i = 0; i < projects.length; i++) {
751 if (currentDelta.findNodeAt(projects[i].getFullPath()) != null) {
752 if (Policy.DEBUG_NEEDS_BUILD)
753 Policy.debug(toString(builder) + " needs building because of changes in: " + projects[i].getName()); //$NON-NLS-1$
754 return true;
755 }
756 }
757 return false;
758 }
759
760 /**
761 * Removes all builders with the given ID from the build spec.
762 * Does nothing if there were no such builders in the spec
763 */
764 protected void removeBuilders(IProject project, String builderId) throws CoreException {
765 IProjectDescription desc = project.getDescription();
766 ICommand[] oldSpec = desc.getBuildSpec();
767 int oldLength = oldSpec.length;
768 if (oldLength == 0)
769 return;
770 int remaining = 0;
771 //null out all commands that match the builder to remove
772 for (int i = 0; i < oldSpec.length; i++) {
773 if (oldSpec[i].getBuilderName().equals(builderId))
774 oldSpec[i] = null;
775 else
776 remaining++;
777 }
778 //check if any were actually removed
779 if (remaining == oldSpec.length)
780 return;
781 ICommand[] newSpec = new ICommand[remaining];
782 for (int i = 0, newIndex = 0; i < oldLength; i++) {
783 if (oldSpec[i] != null)
784 newSpec[newIndex++] = oldSpec[i];
785 }
786 desc.setBuildSpec(newSpec);
787 project.setDescription(desc, IResource.NONE, null);
788 }
789
790 /**
791 * Hook for builders to request a rebuild.
792 */
793 public void requestRebuild() {
794 rebuildRequested = true;
795 }
796
797 /**
798 * Sets the builder map for the given project. The builder map is
799 * a Map mapping String(builder name) -> BuilderPersistentInfo.
800 * The map includes entries for all builders that are
801 * in the builder spec, and that have a last built state, even if they
802 * have not been instantiated this session.
803 */
804 public void setBuildersPersistentInfo(IProject project, Map map) {
805 try {
806 project.setSessionProperty(K_BUILD_MAP, map);
807 } catch (CoreException e) {
808 //project is missing -- build state will be lost
809 //can't throw an exception because this happens on startup
810 IStatus error = new ResourceStatus(IStatus.ERROR, 1, project.getFullPath(), "Project missing in setBuildersPersistentInfo", null); //$NON-NLS-1$
811 ResourcesPlugin.getPlugin().getLog().log(error);
812 }
813 }
814
815 public void shutdown(IProgressMonitor monitor) {
816 autoBuildJob.cancel();
817 }
818
819 public void startup(IProgressMonitor monitor) {
820 workspace.addLifecycleListener(this);
821 }
822
823 /**
824 * Returns a string representation of the given builder.
825 * For debugging purposes only.
826 */
827 protected String toString(InternalBuilder builder) {
828 String name = builder.getClass().getName();
829 name = name.substring(name.lastIndexOf('.') + 1);
830 return name + "(" + builder.getProject().getName() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
831 }
832
833 /**
834 * Returns true if the nature membership rules are satisifed for the given
835 * builder extension on the given project, and false otherwise. A builder that
836 * does not specify that it belongs to a nature is always valid. A builder
837 * extension that belongs to a nature can be invalid for the following reasons:
838 * <ul>
839 * <li>The nature that owns the builder does not exist on the given project</li>
840 * <li>The nature that owns the builder is disabled on the given project</li>
841 * </ul>
842 * Furthermore, if the nature that owns the builder does not exist on the project,
843 * that builder will be removed from the build spec.
844 *
845 * Note: This method only validates nature constraints that can vary at runtime.
846 * Additional checks are done in the instantiateBuilder method for constraints
847 * that cannot vary once the plugin registry is initialized.
848 */
849 protected boolean validateNature(InternalBuilder builder, String builderId) throws CoreException {
850 String nature = builder.getNatureId();
851 if (nature == null)
852 return true;
853 IProject project = builder.getProject();
854 if (!project.hasNature(nature)) {
855 //remove this builder from the build spec
856 removeBuilders(project, builderId);
857 return false;
858 }
859 return project.isNatureEnabled(nature);
860 }
861 }