1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core.builder;
13 import java.io.DataInputStream;
14 import java.io.DataOutputStream;
15 import java.io.IOException;
16 import java.util.ArrayList;
17 import java.util.Date;
18 import java.util.HashSet;
19 import java.util.Iterator;
22 import net.sourceforge.phpdt.core.IClasspathEntry;
23 import net.sourceforge.phpdt.core.IJavaModelMarker;
24 import net.sourceforge.phpdt.core.JavaModelException;
25 import net.sourceforge.phpdt.core.compiler.CharOperation;
26 import net.sourceforge.phpdt.internal.core.JavaModel;
27 import net.sourceforge.phpdt.internal.core.JavaModelManager;
28 import net.sourceforge.phpdt.internal.core.JavaProject;
29 import net.sourceforge.phpdt.internal.core.Util;
30 import net.sourceforge.phpdt.internal.core.util.SimpleLookupTable;
31 import net.sourceforge.phpeclipse.PHPCore;
33 import org.eclipse.core.resources.IMarker;
34 import org.eclipse.core.resources.IProject;
35 import org.eclipse.core.resources.IResource;
36 import org.eclipse.core.resources.IResourceChangeEvent;
37 import org.eclipse.core.resources.IResourceDelta;
38 import org.eclipse.core.resources.IWorkspaceRoot;
39 import org.eclipse.core.resources.IncrementalProjectBuilder;
40 import org.eclipse.core.runtime.CoreException;
41 import org.eclipse.core.runtime.IPath;
42 import org.eclipse.core.runtime.IProgressMonitor;
44 public class PHPBuilder extends IncrementalProjectBuilder {
46 IProject currentProject;
47 JavaProject javaProject;
48 IWorkspaceRoot workspaceRoot;
49 NameEnvironment nameEnvironment;
50 SimpleLookupTable binaryLocationsPerProject; // maps a project to its binary resources (output folders, class folders, zip/jar files)
52 BuildNotifier notifier;
53 char[][] extraResourceFileFilters;
54 String[] extraResourceFolderFilters;
56 public static final String CLASS_EXTENSION = "class"; //$NON-NLS-1$
58 public static boolean DEBUG = true;
61 * A list of project names that have been built.
62 * This list is used to reset the JavaModel.existingExternalFiles cache when a build cycle begins
63 * so that deleted external jars are discovered.
65 static ArrayList builtProjects = null;
67 public static IMarker[] getProblemsFor(IResource resource) {
69 if (resource != null && resource.exists())
70 return resource.findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
71 } catch (CoreException e) {
72 } // assume there are no problems
73 return new IMarker[0];
76 public static IMarker[] getTasksFor(IResource resource) {
78 if (resource != null && resource.exists())
79 return resource.findMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
80 } catch (CoreException e) {
81 } // assume there are no tasks
82 return new IMarker[0];
85 public static void finishedBuilding(IResourceChangeEvent event) {
86 BuildNotifier.resetProblemCounters();
89 public static void removeProblemsFor(IResource resource) {
91 if (resource != null && resource.exists())
92 resource.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
93 } catch (CoreException e) {
94 } // assume there were no problems
97 public static void removeTasksFor(IResource resource) {
99 if (resource != null && resource.exists())
100 resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
101 } catch (CoreException e) {
102 } // assume there were no problems
105 public static void removeProblemsAndTasksFor(IResource resource) {
107 if (resource != null && resource.exists()) {
108 resource.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
109 resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
111 } catch (CoreException e) {
112 } // assume there were no problems
115 public static State readState(IProject project, DataInputStream in) throws IOException {
116 return State.read(project, in);
119 public static void writeState(Object state, DataOutputStream out) throws IOException {
120 ((State) state).write(out);
123 public PHPBuilder() {
126 protected IProject[] build(int kind, Map ignored, IProgressMonitor monitor) throws CoreException {
127 this.currentProject = getProject();
128 if (currentProject == null || !currentProject.isAccessible())
129 return new IProject[0];
132 System.out.println("\nStarting build of " + currentProject.getName() //$NON-NLS-1$
133 +" @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$
134 this.notifier = new BuildNotifier(monitor, currentProject);
138 notifier.checkCancel();
141 if (isWorthBuilding()) {
142 if (kind == FULL_BUILD) {
145 if ((this.lastState = getLastState(currentProject)) == null) {
147 System.out.println("Performing full build since last saved state was not found"); //$NON-NLS-1$
149 } else if (hasClasspathChanged()) {
150 // if the output location changes, do not delete the binary files from old location
151 // the user may be trying something
153 } else if (nameEnvironment.sourceLocations.length > 0) {
154 // if there is no source to compile & no classpath changes then we are done
155 SimpleLookupTable deltas = findDeltas();
158 else if (deltas.elementSize > 0)
161 System.out.println("Nothing to build since deltas were empty"); //$NON-NLS-1$
163 if (hasStructuralDelta()) { // double check that a jar file didn't get replaced in a binary project
167 System.out.println("Nothing to build since there are no source folders and no deltas"); //$NON-NLS-1$
168 lastState.tagAsNoopBuild();
174 } catch (CoreException e) {
175 Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
176 IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
177 marker.setAttribute(IMarker.MESSAGE, Util.bind("build.inconsistentProject", e.getLocalizedMessage())); //$NON-NLS-1$
178 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
179 } catch (ImageBuilderInternalException e) {
180 Util.log(e.getThrowable(), "JavaBuilder handling ImageBuilderInternalException"); //$NON-NLS-1$
181 IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
182 marker.setAttribute(IMarker.MESSAGE, Util.bind("build.inconsistentProject", e.coreException.getLocalizedMessage())); //$NON-NLS-1$
183 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
184 } catch (MissingClassFileException e) {
185 // do not log this exception since its thrown to handle aborted compiles because of missing class files
187 System.out.println(Util.bind("build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$
188 IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
189 marker.setAttribute(IMarker.MESSAGE, Util.bind("build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$
190 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
191 } catch (MissingSourceFileException e) {
192 // do not log this exception since its thrown to handle aborted compiles because of missing source files
194 System.out.println(Util.bind("build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$
195 removeProblemsAndTasksFor(currentProject); // make this the only problem for this project
196 IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
197 marker.setAttribute(IMarker.MESSAGE, Util.bind("build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$
198 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
201 // If the build failed, clear the previously built state, forcing a full build next time.
206 IProject[] requiredProjects = getRequiredProjects(true);
208 System.out.println("Finished build of " + currentProject.getName() //$NON-NLS-1$
209 +" @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$
210 return requiredProjects;
213 private void buildAll() {
214 notifier.checkCancel();
215 notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$
216 if (DEBUG && lastState != null)
217 System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$
219 BatchImageBuilder imageBuilder = new BatchImageBuilder(this);
220 imageBuilder.build();
221 recordNewState(imageBuilder.newState);
224 private void buildDeltas(SimpleLookupTable deltas) {
225 notifier.checkCancel();
226 notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$
227 if (DEBUG && lastState != null)
228 System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$
229 clearLastState(); // clear the previously built state so if the build fails, a full build will occur next time
230 IncrementalImageBuilder imageBuilder = new IncrementalImageBuilder(this);
231 if (imageBuilder.build(deltas))
232 recordNewState(imageBuilder.newState);
237 private void cleanup() {
238 this.nameEnvironment = null;
239 this.binaryLocationsPerProject = null;
240 this.lastState = null;
241 this.notifier = null;
242 this.extraResourceFileFilters = null;
243 this.extraResourceFolderFilters = null;
246 private void clearLastState() {
247 JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, null);
250 boolean filterExtraResource(IResource resource) {
251 if (extraResourceFileFilters != null) {
252 char[] name = resource.getName().toCharArray();
253 for (int i = 0, l = extraResourceFileFilters.length; i < l; i++)
254 if (CharOperation.match(extraResourceFileFilters[i], name, true))
257 if (extraResourceFolderFilters != null) {
258 IPath path = resource.getProjectRelativePath();
259 String pathName = path.toString();
260 int count = path.segmentCount();
261 if (resource.getType() == IResource.FILE)
263 for (int i = 0, l = extraResourceFolderFilters.length; i < l; i++)
264 if (pathName.indexOf(extraResourceFolderFilters[i]) != -1)
265 for (int j = 0; j < count; j++)
266 if (extraResourceFolderFilters[i].equals(path.segment(j)))
272 private SimpleLookupTable findDeltas() {
273 notifier.subTask(Util.bind("build.readingDelta", currentProject.getName())); //$NON-NLS-1$
274 IResourceDelta delta = getDelta(currentProject);
275 SimpleLookupTable deltas = new SimpleLookupTable(3);
277 if (delta.getKind() != IResourceDelta.NO_CHANGE) {
279 System.out.println("Found source delta for: " + currentProject.getName()); //$NON-NLS-1$
280 deltas.put(currentProject, delta);
284 System.out.println("Missing delta for: " + currentProject.getName()); //$NON-NLS-1$
285 notifier.subTask(""); //$NON-NLS-1$
289 Object[] keyTable = binaryLocationsPerProject.keyTable;
290 Object[] valueTable = binaryLocationsPerProject.valueTable;
291 nextProject : for (int i = 0, l = keyTable.length; i < l; i++) {
292 IProject p = (IProject) keyTable[i];
293 if (p != null && p != currentProject) {
294 State s = getLastState(p);
295 if (!lastState.wasStructurallyChanged(p, s)) { // see if we can skip its delta
296 if (s.wasNoopBuild())
297 continue nextProject; // project has no source folders and can be skipped
298 // ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) valueTable[i];
299 boolean canSkip = true;
300 // for (int j = 0, m = classFoldersAndJars.length; j < m; j++) {
301 // if (classFoldersAndJars[j].isOutputFolder())
302 // classFoldersAndJars[j] = null; // can ignore output folder since project was not structurally changed
307 continue nextProject; // project has no structural changes in its output folders
310 notifier.subTask(Util.bind("build.readingDelta", p.getName())); //$NON-NLS-1$
313 if (delta.getKind() != IResourceDelta.NO_CHANGE) {
315 System.out.println("Found binary delta for: " + p.getName()); //$NON-NLS-1$
316 deltas.put(p, delta);
320 System.out.println("Missing delta for: " + p.getName()); //$NON-NLS-1$
321 notifier.subTask(""); //$NON-NLS-1$
326 notifier.subTask(""); //$NON-NLS-1$
330 private State getLastState(IProject project) {
331 return (State) JavaModelManager.getJavaModelManager().getLastBuiltState(project, notifier.monitor);
334 /* Return the list of projects for which it requires a resource delta. This builder's project
335 * is implicitly included and need not be specified. Builders must re-specify the list
336 * of interesting projects every time they are run as this is not carried forward
337 * beyond the next build. Missing projects should be specified but will be ignored until
338 * they are added to the workspace.
340 private IProject[] getRequiredProjects(boolean includeBinaryPrerequisites) {
341 if (javaProject == null || workspaceRoot == null)
342 return new IProject[0];
344 ArrayList projects = new ArrayList();
346 IClasspathEntry[] entries = javaProject.getExpandedClasspath(true);
347 for (int i = 0, l = entries.length; i < l; i++) {
348 IClasspathEntry entry = entries[i];
349 IPath path = entry.getPath();
351 switch (entry.getEntryKind()) {
352 case IClasspathEntry.CPE_PROJECT :
353 p = workspaceRoot.getProject(path.lastSegment()); // missing projects are considered too
355 case IClasspathEntry.CPE_LIBRARY :
356 if (includeBinaryPrerequisites && path.segmentCount() > 1) {
357 // some binary resources on the class path can come from projects that are not included in the project references
358 IResource resource = workspaceRoot.findMember(path.segment(0));
359 if (resource instanceof IProject)
360 p = (IProject) resource;
363 if (p != null && !projects.contains(p))
366 } catch (JavaModelException e) {
367 return new IProject[0];
369 IProject[] result = new IProject[projects.size()];
370 projects.toArray(result);
374 private boolean hasClasspathChanged() {
375 ClasspathMultiDirectory[] newSourceLocations = nameEnvironment.sourceLocations;
376 ClasspathMultiDirectory[] oldSourceLocations = lastState.sourceLocations;
377 int newLength = newSourceLocations.length;
378 int oldLength = oldSourceLocations.length;
380 for (n = o = 0; n < newLength && o < oldLength; n++, o++) {
381 if (newSourceLocations[n].equals(oldSourceLocations[o]))
382 continue; // checks source & output folders
384 if (newSourceLocations[n].sourceFolder.members().length == 0) { // added new empty source folder
388 } catch (CoreException ignore) {
391 System.out.println(newSourceLocations[n] + " != " + oldSourceLocations[o]); //$NON-NLS-1$
394 while (n < newLength) {
396 if (newSourceLocations[n].sourceFolder.members().length == 0) { // added new empty source folder
400 } catch (CoreException ignore) {
403 System.out.println("Added non-empty source folder"); //$NON-NLS-1$
408 System.out.println("Removed source folder"); //$NON-NLS-1$
412 // ClasspathLocation[] newBinaryLocations = nameEnvironment.binaryLocations;
413 // ClasspathLocation[] oldBinaryLocations = lastState.binaryLocations;
414 // newLength = newBinaryLocations.length;
415 // oldLength = oldBinaryLocations.length;
416 // for (n = o = 0; n < newLength && o < oldLength; n++, o++) {
417 // if (newBinaryLocations[n].equals(oldBinaryLocations[o])) continue;
419 // System.out.println(newBinaryLocations[n] + " != " + oldBinaryLocations[o]); //$NON-NLS-1$
422 // if (n < newLength || o < oldLength) {
424 // System.out.println("Number of binary folders/jar files has changed"); //$NON-NLS-1$
430 private boolean hasStructuralDelta() {
431 // handle case when currentProject has only .class file folders and/or jar files... no source/output folders
432 IResourceDelta delta = getDelta(currentProject);
433 if (delta != null && delta.getKind() != IResourceDelta.NO_CHANGE) {
434 // ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) binaryLocationsPerProject.get(currentProject);
435 // if (classFoldersAndJars != null) {
436 // for (int i = 0, l = classFoldersAndJars.length; i < l; i++) {
437 // ClasspathLocation classFolderOrJar = classFoldersAndJars[i]; // either a .class file folder or a zip/jar file
438 // if (classFolderOrJar != null) {
439 // IPath p = classFolderOrJar.getProjectRelativePath();
441 // IResourceDelta binaryDelta = delta.findMember(p);
442 // if (binaryDelta != null && binaryDelta.getKind() != IResourceDelta.NO_CHANGE)
452 private void initializeBuilder() throws CoreException {
453 this.javaProject = (JavaProject) PHPCore.create(currentProject);
454 this.workspaceRoot = currentProject.getWorkspace().getRoot();
456 // Flush the existing external files cache if this is the beginning of a build cycle
457 String projectName = currentProject.getName();
458 if (builtProjects == null || builtProjects.contains(projectName)) {
459 JavaModel.flushExternalFileCache();
460 builtProjects = new ArrayList();
462 builtProjects.add(projectName);
464 this.binaryLocationsPerProject = new SimpleLookupTable(3);
465 this.nameEnvironment = new NameEnvironment(workspaceRoot, javaProject, binaryLocationsPerProject);
467 String filterSequence = javaProject.getOption(PHPCore.CORE_JAVA_BUILD_RESOURCE_COPY_FILTER, true);
468 char[][] filters = filterSequence != null && filterSequence.length() > 0 ? CharOperation.splitAndTrimOn(',', filterSequence.toCharArray()) : null;
469 if (filters == null) {
470 this.extraResourceFileFilters = null;
471 this.extraResourceFolderFilters = null;
473 int fileCount = 0, folderCount = 0;
474 for (int i = 0, l = filters.length; i < l; i++) {
475 char[] f = filters[i];
478 if (f[f.length - 1] == '/')
483 this.extraResourceFileFilters = new char[fileCount][];
484 this.extraResourceFolderFilters = new String[folderCount];
485 for (int i = 0, l = filters.length; i < l; i++) {
486 char[] f = filters[i];
489 if (f[f.length - 1] == '/')
490 extraResourceFolderFilters[--folderCount] = new String(CharOperation.subarray(f, 0, f.length - 1));
492 extraResourceFileFilters[--fileCount] = f;
497 private boolean isClasspathBroken(IClasspathEntry[] classpath, IProject p) throws CoreException {
498 if (classpath == JavaProject.INVALID_CLASSPATH) // the .classpath file could not be read
501 IMarker[] markers = p.findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO);
502 for (int i = 0, l = markers.length; i < l; i++)
503 if (((Integer) markers[i].getAttribute(IMarker.SEVERITY)).intValue() == IMarker.SEVERITY_ERROR)
508 private boolean isWorthBuilding() throws CoreException {
509 boolean abortBuilds = PHPCore.ABORT.equals(javaProject.getOption(PHPCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, true));
513 // Abort build only if there are classpath errors
514 // if (isClasspathBroken(javaProject.getRawClasspath(), currentProject)) {
516 // System.out.println("Aborted build because project has classpath errors (incomplete or involved in cycle)"); //$NON-NLS-1$
518 // JavaModelManager.getJavaModelManager().deltaProcessor.addForRefresh(javaProject);
520 // removeProblemsAndTasksFor(currentProject); // remove all compilation problems
522 // IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
523 // marker.setAttribute(IMarker.MESSAGE, Util.bind("build.abortDueToClasspathProblems")); //$NON-NLS-1$
524 // marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
528 // make sure all prereq projects have valid build states... only when aborting builds since projects in cycles do not have build states
529 // except for projects involved in a 'warning' cycle (see below)
530 IProject[] requiredProjects = getRequiredProjects(false);
531 next : for (int i = 0, l = requiredProjects.length; i < l; i++) {
532 IProject p = requiredProjects[i];
533 if (getLastState(p) == null) {
534 // The prereq project has no build state: if this prereq project has a 'warning' cycle marker then allow build (see bug id 23357)
535 JavaProject prereq = (JavaProject) PHPCore.create(p);
536 if (prereq.hasCycleMarker() && PHPCore.WARNING.equals(javaProject.getOption(PHPCore.CORE_CIRCULAR_CLASSPATH, true)))
539 System.out.println("Aborted build because prereq project " + p.getName() //$NON-NLS-1$
540 +" was not built"); //$NON-NLS-1$
542 removeProblemsAndTasksFor(currentProject); // make this the only problem for this project
543 IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
544 marker.setAttribute(IMarker.MESSAGE, isClasspathBroken(prereq.getRawClasspath(), p) ? Util.bind("build.prereqProjectHasClasspathProblems", p.getName()) //$NON-NLS-1$
545 : Util.bind("build.prereqProjectMustBeRebuilt", p.getName())); //$NON-NLS-1$
546 marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
554 * Instruct the build manager that this project is involved in a cycle and
555 * needs to propagate structural changes to the other projects in the cycle.
557 void mustPropagateStructuralChanges() {
558 HashSet cycleParticipants = new HashSet(3);
559 javaProject.updateCycleParticipants(null, new ArrayList(), cycleParticipants, workspaceRoot, new HashSet(3));
560 IPath currentPath = javaProject.getPath();
561 Iterator i = cycleParticipants.iterator();
562 while (i.hasNext()) {
563 IPath participantPath = (IPath) i.next();
564 if (participantPath != currentPath) {
565 IProject project = this.workspaceRoot.getProject(participantPath.segment(0));
566 if (hasBeenBuilt(project)) {
568 System.out.println("Requesting another build iteration since cycle participant " + project.getName() //$NON-NLS-1$
569 +" has not yet seen some structural changes"); //$NON-NLS-1$
577 private void recordNewState(State state) {
578 Object[] keyTable = binaryLocationsPerProject.keyTable;
579 for (int i = 0, l = keyTable.length; i < l; i++) {
580 IProject prereqProject = (IProject) keyTable[i];
581 if (prereqProject != null && prereqProject != currentProject)
582 state.recordStructuralDependency(prereqProject, getLastState(prereqProject));
586 System.out.println("Recording new state : " + state); //$NON-NLS-1$
588 JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, state);
592 * String representation for debugging purposes
594 public String toString() {
595 return currentProject == null ? "JavaBuilder for unknown project" //$NON-NLS-1$
596 : "JavaBuilder for " + currentProject.getName(); //$NON-NLS-1$