Core improvement to avoid any unecessary builds. Really speeds up workspaces with more than a few projects.

This commit is contained in:
lonnen
2010-10-07 00:50:57 +00:00
parent 0a5b3acf7b
commit 444be9ffe5
4 changed files with 395 additions and 58 deletions

View File

@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Processing Plugin Core
Bundle-SymbolicName: processing.plugin.core;singleton:=true
Bundle-Version: 0.2.2.0
Bundle-Version: 0.2.3.0
Bundle-Activator: processing.plugin.core.ProcessingCore
Bundle-Vendor: Processing.org
Require-Bundle: org.eclipse.core.runtime,

View File

@@ -25,18 +25,33 @@
</run>
</builder>
</extension>
<extension
id="processingMarker"
name="Processing Parent Marker"
point="org.eclipse.core.resources.markers">
<persistent value="true" />
<super
type="org.eclipse.core.resources.marker">
</super>
</extension>
<extension
id="preprocError"
name="Processing Preprocessor Problem Marker"
point="org.eclipse.core.resources.markers">
<persistent value="true" />
<super type="org.eclipse.core.resources.problemmarker" />
<super
type="processing.plugin.core.processingMarker">
</super>
</extension>
<extension
id="compileError"
name="Processing Compile Problem Marker"
point="org.eclipse.core.resources.markers">
<persistent value="true" />
<super type="org.eclipse.core.resources.problemmarker" />
<super type="processing.plugin.core.processingMarker" />
<super
type="org.eclipse.core.resources.problemmarker">
</super>
</extension>
</plugin>

View File

@@ -0,0 +1,327 @@
/**
* Copyright (c) 2010 Chris Lonnen. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.opensource.org/licenses/eclipse-1.0.php
*
* Contributors:
* Chris Lonnen - initial API and implementation
*/
package processing.plugin.core.builder;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import processing.plugin.core.ProcessingCore;
import processing.plugin.core.ProcessingLog;
/**
* Handle an IResourceDelta reported against a Sketch Project.
* <p>
* Each instance of this class should only handle a single
* resource delta. Create a new one for each delta that needs
* to be processed.
* <p>
* This class was inspired by the JyDT class of the same name
* implemented by Red Robin software.
* <p>
* Right now only a full build is available, but the granularity
* of this class prevents unnecessary builds, which saves a lot
* of time, especially on workspaces with more than a handful
* of sketches.
* <p>
* In the future this should be easy to integrate with a more
* incremental build process.
*/
public class IncrementalChangeProcessor {
private boolean fullBuildRequired;
private SketchProject sp;
/** Give it a project as context */
public IncrementalChangeProcessor(SketchProject sketchProject){
sp = sketchProject;
fullBuildRequired = false;
}
/** Indicate that a full build should be carried out */
private void setFullBuildRequired(){
fullBuildRequired = true;
}
/** Process an IResourceChangeEvent */
public boolean resourceChanged(IResourceChangeEvent event){
printEvent(event);
IResourceDelta delta = event.getDelta();
return this.resourceChanged(delta);
}
/** Process an IResourceDelta */
public boolean resourceChanged(IResourceDelta delta){
if (delta == null) return false;
printResourceChanges(delta);
IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) throws CoreException {
switch (delta.getResource().getType()){
case IResource.ROOT:
// do nothing
break;
case IResource.PROJECT:
IProject changedProject = (IProject) delta.getResource();
if(changedProject != sp.getProject())
return false;
processProjectChange(delta);
break;
case IResource.FILE:
processFileChange(delta);
break;
case IResource.FOLDER:
processFolderChange(delta);
break;
default:
break;
}
return true; // visit children
}
};
try{
delta.accept(visitor);
} catch (CoreException ex){
ProcessingLog.logError(ex);
}
return fullBuildRequired;
}
/** Handle a change against the Project itself */
private void processProjectChange(IResourceDelta delta){
// Do nothing for now.
switch (delta.getFlags()) {
case IResourceDelta.DESCRIPTION:
break;
case IResourceDelta.OPEN:
setFullBuildRequired();
break;
case IResourceDelta.TYPE:
break;
case IResourceDelta.SYNC:
setFullBuildRequired();
break;
case IResourceDelta.MARKERS:
break;
case IResourceDelta.REPLACED:
setFullBuildRequired();
break;
case IResourceDelta.MOVED_TO:
setFullBuildRequired();
break;
case IResourceDelta.MOVED_FROM:
break;
default:
break;
}
}
/** Handle a resource change against a folder */
private void processFolderChange(IResourceDelta delta){
if (!sp.getProject().exists(delta.getProjectRelativePath()))
return;
switch(delta.getKind()){
case IResourceDelta.ADDED:
break;
case IResourceDelta.REMOVED:
if (delta.getFlags() == IResourceDelta.MOVED_TO)
processInterestingFolderMove(delta);
else
processInterestingFolderRemoval(delta);
break;
case IResourceDelta.CHANGED:
processInterestingFolderChange(delta);
break;
default:
break;
}
}
/** Handle the change of a folder in the project */
private void processInterestingFolderChange(IResourceDelta delta){
// we don't really care that the folder changed at the moment
// switch(delta.getFlags()){
// case IResourceDelta.CONTENT:
// case IResourceDelta.DESCRIPTION:
// case IResourceDelta.OPEN:
// case IResourceDelta.TYPE:
// case IResourceDelta.SYNC:
// case IResourceDelta.MARKERS:
// case IResourceDelta.REPLACED:
// case IResourceDelta.MOVED_TO:
// case IResourceDelta.MOVED_FROM:
// default:
// break;
// }
}
/** Handle the removal of a folder in the project */
private void processInterestingFolderRemoval(IResourceDelta delta){
setFullBuildRequired();
}
/** Handle the move of a folder in the project */
private void processInterestingFolderMove(IResourceDelta delta){
setFullBuildRequired();
}
/** Handle the change of a file in the project */
private void processFileChange(IResourceDelta delta){
IFile resource = (IFile) delta.getResource();
// we only care about .pde files
if(!ProcessingCore.isProcessingFile(resource)) return;
if (!sp.getProject().exists(resource.getProjectRelativePath())) return;
// System.out.println(delta.getProjectRelativePath() + " exists in " + sp.getProject().getName());
setFullBuildRequired();
//switch (delta.getKind()){
//case IResourceDelta.ADDED:
//case IResourceDelta.REMOVED:
//case IResourceDelta.CHANGED:
// int deltaFlags = delta.getFlags();
// switch(deltaFlags){
// case IResourceDelta.CONTENT:
// case IResourceDelta.ENCODING:
// case IResourceDelta.TYPE:
// case IResourceDelta.SYNC:
// case IResourceDelta.MARKERS:
// case IResourceDelta.REPLACED:
// case IResourceDelta.MOVED_TO:
// case IResourceDelta.MOVED_FROM:
// default:
// break;
// }
// break;
// default:
// break;
// }
}
/** Debug code */
private static void printChange(IResourceDelta delta, StringBuffer buffer) {
buffer.append("CHANGED ");
buffer.append(delta.getFlags());
switch (delta.getFlags()) {
case IResourceDelta.CONTENT:
buffer.append(" (CONTENT)");
break;
case IResourceDelta.ENCODING:
buffer.append(" (ENCODING)");
break;
case IResourceDelta.DESCRIPTION:
buffer.append(" (DESCRIPTION)");
break;
case IResourceDelta.OPEN:
buffer.append(" (OPEN)");
break;
case IResourceDelta.TYPE:
buffer.append(" (TYPE)");
break;
case IResourceDelta.SYNC:
buffer.append(" (SYNC)");
break;
case IResourceDelta.MARKERS:
buffer.append(" (MARKERS)");
break;
case IResourceDelta.REPLACED:
buffer.append(" (REPLACED)");
break;
case IResourceDelta.MOVED_TO:
buffer.append(" (MOVED_TO)");
break;
case IResourceDelta.MOVED_FROM:
buffer.append(" (MOVED_FROM)");
break;
default:
buffer.append(" (<unknown>)");
break;
}
}
/** Debug code */
private static void printOneResourceChanged(IResourceDelta delta, StringBuffer buffer, int indent) {
for (int i = 0; i < indent; i++) {
buffer.append(' ');
}
switch (delta.getKind()) {
case IResourceDelta.ADDED:
buffer.append("ADDED");
break;
case IResourceDelta.REMOVED:
buffer.append("REMOVED");
break;
case IResourceDelta.CHANGED:
printChange(delta, buffer);
break;
default:
buffer.append('[');
buffer.append(delta.getKind());
buffer.append(']');
break;
}
buffer.append(' ');
buffer.append(delta.getResource());
buffer.append('\n');
}
/** Debug code */
private static void printResourcesChanged(IResourceDelta delta, StringBuffer buffer, int indent) {
printOneResourceChanged(delta, buffer, indent);
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++) {
printResourcesChanged(children[i], buffer, indent + 1);
}
}
/** Debug code */
public static void printEvent(IResourceChangeEvent event) {
StringBuffer buffer = new StringBuffer(80);
buffer.append("Resource change event received ");
switch (event.getType()) {
case IResourceChangeEvent.POST_BUILD:
buffer.append("[POST_BUILD]");
break;
case IResourceChangeEvent.POST_CHANGE:
buffer.append("[POST_CHANGE]");
break;
case IResourceChangeEvent.PRE_BUILD:
buffer.append("[PRE_BUILD]");
break;
case IResourceChangeEvent.PRE_CLOSE:
buffer.append("[PRE_CLOSE]");
break;
case IResourceChangeEvent.PRE_DELETE:
buffer.append("[PRE_DELETE]");
break;
default:
break;
}
buffer.append(".\n");
System.out.println(buffer);
}
/** Debug code */
public static void printResourceChanges(IResourceDelta delta) {
StringBuffer buffer = new StringBuffer(80);
if (delta != null)
printResourcesChanged(delta, buffer, 0);
System.out.println(buffer);
}
}

View File

@@ -51,24 +51,24 @@ import processing.plugin.core.ProcessingUtilities;
* The SketchNature class is tightly integrated and manages the configuration so this builder
* works together with the JDT, so woe be to those who would carelessly manipulate this builder
* directly.
* </p>
* <p>
* The builder is compatible with the PDE, and it expects sketches to be laid out with the
* same folder structure. It may store metadata and temporary build files in the sketch
* file system but these will not change how the PDE interacts with it. Users should be
* able to use the PDE interchangeably with this builder.
* </p>
* <p>
* Though this implements the Incremental Project Builder, the preprocessor is not incremental
* and forces all builds to be full builds. To save a little bit of time, any build request is
* treated as a full build without an inspection of the resource delta.
* </p>
*/
public class SketchBuilder extends IncrementalProjectBuilder{
/** The identifier for the Processing builder (value <code>"processing.plugin.core.processingbuilder"</code>). */
public static final String BUILDER_ID = ProcessingCore.PLUGIN_ID + ".sketchBuilder";
/** Parent marker for Processing created markers (value <code>"processing.plugin.core.processingMarker"</code>). */
public static final String PROCESSINGMARKER = ProcessingCore.PLUGIN_ID + ".processingMarker";
/** Problem marker for processing preprocessor issues (value <code>"processing.plugin.core.preprocError"</code>). */
public static final String PREPROCMARKER = ProcessingCore.PLUGIN_ID + ".preprocError";
@@ -111,9 +111,14 @@ public class SketchBuilder extends IncrementalProjectBuilder{
/** Full paths to jars required to compile the sketch */
private ArrayList<IPath>libraryJarPathList;
/** Clean any leftover state from previous builds. */
protected void clean(SketchProject sketch, IProgressMonitor monitor) throws CoreException{
deleteP5ProblemMarkers(sketch);
/**
* Triggered by platform Clean command.
* <p>
* Reset any state from previous builds.
*/
protected void clean(IProgressMonitor monitor) throws CoreException{
SketchProject sketch = SketchProject.forProject(this.getProject());
deleteP5ProblemMarkers(sketch.getProject());
srcFolderPathList = new ArrayList<IPath>();
libraryJarPathList = new ArrayList<IPath>();
// if this is the first run of the builder the build folder will not be stored yet,
@@ -127,7 +132,6 @@ public class SketchBuilder extends IncrementalProjectBuilder{
// Eventually, a model (controlled by SketchProject) should manage the markers,
// folders, etc. Cleaning the model should be in a method called beCleaned()
// in the SketchProject. This method will then look something like:
// SketchProject sketch = SketchProject.forProject(this.getProject());
// sketch.beCleaned();
}
@@ -137,47 +141,44 @@ public class SketchBuilder extends IncrementalProjectBuilder{
* This usually means grabbing all the Processing files and compiling them
* to Java source files and moving them into a designated folder where the
* JDT will grab them and build them.
* </p>
* <p>
* For now all builds are full builds because the preprocessor does not
* handle incremental builds.
* </p>
*
* handle incremental builds.
*/
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException{
IProject project = this.getProject();
SketchProject sketch = SketchProject.forProject(project);
switch (kind){
case FULL_BUILD:
return this.fullBuild(sketch, monitor);
case AUTO_BUILD:
return (fullBuildRequired(project)) ? this.fullBuild(sketch, monitor) : null;
// return this.autoBuild(sketch, monitor);
case INCREMENTAL_BUILD:
return (fullBuildRequired(project)) ? this.fullBuild(sketch, monitor) : null;
// return this.incrementalBuild(sketch, monitor);
default:
return null;
switch (kind) {
case FULL_BUILD:
return this.fullBuild(sketch, monitor);
case AUTO_BUILD:
return this.autoBuild(sketch, monitor);
case INCREMENTAL_BUILD:
return this.incrementalBuild(sketch, monitor);
default:
return null; // everything falls through to return null
}
}
/**
* Returns false if the resource change definitely didn't affect this project.
* <p>
* Runs simple checks to try and rule out a build. If it can't find a reason
* to rule out a build, it defaults to true. Hopefully this will spare a few
* CPU cycles, though there is a lot of room for improvement here.
*/
private boolean fullBuildRequired(IProject proj) {
if (proj == null) return false;
IResourceDelta delta = this.getDelta(proj);
if (delta == null) return false;
if (delta.getResource().getType() == IResource.PROJECT){
IProject changedProject = (IProject) delta.getResource();
if (changedProject != proj) return false;
}
return true;
/** Handles platform auto builds */
protected IProject[] autoBuild(SketchProject sketchProject, IProgressMonitor monitor) throws CoreException{
//System.err.println("Auto Build");
IResourceDelta delta = this.getDelta(this.getProject());
IncrementalChangeProcessor changeProcessor = new IncrementalChangeProcessor(sketchProject);
boolean shouldBuild = changeProcessor.resourceChanged(delta);
if (shouldBuild)
fullBuild(sketchProject, monitor);
return null;
}
/** Incremental builds are ignored. */
protected IProject[] incrementalBuild(SketchProject sketchProject, IProgressMonitor monitor){
//System.err.println("Incremental Build");
// triggered by launching a sketch or explicitly by a user iff auto build is off
// if auto build is on, launching a sketch triggers an auto build first
// save a few cycles by ignoring these
return null;
}
/**
@@ -188,7 +189,9 @@ public class SketchBuilder extends IncrementalProjectBuilder{
* This can be a long running process, so we use a monitor.
*/
protected IProject[] fullBuild( SketchProject sketchProject, IProgressMonitor monitor) throws CoreException {
clean(sketchProject, monitor); // tabula rasa
// System.err.println("Full Build of " + sketchProject.getProject().getName());
clean(monitor); // tabula rasa
IProject sketch = sketchProject.getProject();
if ( sketch == null || !sketch.isAccessible() ){
@@ -481,16 +484,8 @@ public class SketchBuilder extends IncrementalProjectBuilder{
/** Delete all of the existing P5 problem markers. */
protected static void deleteP5ProblemMarkers(IResource resource) throws CoreException{
if(resource != null && resource.exists()){
resource.deleteMarkers(SketchBuilder.PREPROCMARKER, true, IResource.DEPTH_INFINITE);
resource.deleteMarkers(SketchBuilder.COMPILEMARKER, true, IResource.DEPTH_INFINITE);
}
}
/** Purge all the Processing set problem markers from the Processing sketch. */
protected static void deleteP5ProblemMarkers(SketchProject sketch)throws CoreException{
if(sketch != null)
deleteP5ProblemMarkers(sketch.getProject());
if(resource != null && resource.exists())
resource.deleteMarkers(SketchBuilder.PROCESSINGMARKER , true, IResource.DEPTH_INFINITE);
}
/**
@@ -500,7 +495,6 @@ public class SketchBuilder extends IncrementalProjectBuilder{
* to a specific file it will be marked against the project and the line will not be marked.
*/
private void reportProblem(String message, IResource problemFile, int lineNumber, boolean isError){
// translate error messages to a friendlier form
if (message.equals("expecting RCURLY, found 'null'"))
message = "Found one too many { characters without a } to match it.";
@@ -528,17 +522,18 @@ public class SketchBuilder extends IncrementalProjectBuilder{
/**
* Check for interruptions.
* <p>
* The build process is rather long so if the user cancels things toss an exception.
* The build process can run long, so if the user cancels things toss an exception.
* Builds also tie up the workspace, so check to see if something is demanding to
* interrupt it and let it. Usually these interruptions would force a rebuild of the
* system anyhow, so save some CPU cycles and time.
* system anyhow, nullifying the work being done now, so save some cycles and let
* it budge in line.
* </p>
* @return true if the build is hogging the resource thread
* @throws OperationCanceledException is the user cancels
*/
private boolean checkCancel(IProgressMonitor monitor){
if (monitor.isCanceled()){ throw new OperationCanceledException(); }
if (isInterrupted()){ return true; }
if (monitor.isCanceled()) throw new OperationCanceledException();
if (isInterrupted()) return true;
return false;
}
}