From abe17d3ef7dbbd5f8ee6858f9a7d743595c3182b Mon Sep 17 00:00:00 2001 From: lonnen Date: Fri, 27 Aug 2010 21:32:34 +0000 Subject: [PATCH] Large ProcessingCore update, including better error reporting, better project lifecycle management, and redesign of SketchProject's to take advantage of existing JDT stuff. Some tie in's to the launching framework for running sketches, but nothing in the UI yet. Lots of debug stuff in the builder still. --- .../META-INF/MANIFEST.MF | 5 +- editor/processing.plugin.core/plugin.xml | 32 +- .../plugin/core/ProcessingCore.java | 269 ++++++------- .../plugin/core/builder/SketchBuilder.java | 358 ++++++++++++------ .../plugin/core/builder/SketchNature.java | 136 ------- .../plugin/core/builder/SketchProject.java | 241 ++++++++++++ .../processing.plugin.ui/META-INF/MANIFEST.MF | 2 + editor/processing.plugin.ui/plugin.xml | 4 - .../plugin/ui/wizards/NewSketchWizard.java | 4 +- 9 files changed, 643 insertions(+), 408 deletions(-) delete mode 100644 editor/processing.plugin.core/src/processing/plugin/core/builder/SketchNature.java create mode 100644 editor/processing.plugin.core/src/processing/plugin/core/builder/SketchProject.java diff --git a/editor/processing.plugin.core/META-INF/MANIFEST.MF b/editor/processing.plugin.core/META-INF/MANIFEST.MF index 45517f81f..3a9d56fd7 100644 --- a/editor/processing.plugin.core/META-INF/MANIFEST.MF +++ b/editor/processing.plugin.core/META-INF/MANIFEST.MF @@ -7,7 +7,10 @@ Bundle-Activator: processing.plugin.core.ProcessingCore Bundle-Vendor: Processing.org Require-Bundle: org.eclipse.core.runtime, org.eclipse.core.resources, - processing.plugin.appBundle;bundle-version="1.2.1";visibility:=reexport + processing.plugin.appBundle;visibility:=reexport, + org.eclipse.debug.core, + org.eclipse.jdt.core, + org.eclipse.jdt.launching Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy Export-Package: processing.plugin.core, diff --git a/editor/processing.plugin.core/plugin.xml b/editor/processing.plugin.core/plugin.xml index 06bf607a6..bc5743ca8 100644 --- a/editor/processing.plugin.core/plugin.xml +++ b/editor/processing.plugin.core/plugin.xml @@ -7,7 +7,7 @@ point="org.eclipse.core.resources.natures"> + class="processing.plugin.core.builder.SketchProject"> - - - - + + + + + + + + + + + diff --git a/editor/processing.plugin.core/src/processing/plugin/core/ProcessingCore.java b/editor/processing.plugin.core/src/processing/plugin/core/ProcessingCore.java index 773f5aca5..11e22ee25 100644 --- a/editor/processing.plugin.core/src/processing/plugin/core/ProcessingCore.java +++ b/editor/processing.plugin.core/src/processing/plugin/core/ProcessingCore.java @@ -1,61 +1,55 @@ +/******************************************************************************* + * This program and the accompanying materials are made available under the + * terms of the Common Public License v1.0 which accompanies this distribution, + * and is available at http://www.opensource.org/licenses/cpl1.0.php + * + * Contributors: + * Chris Lonnen - initial API and implementation + *******************************************************************************/ + package processing.plugin.core; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.MissingResourceException; +import java.util.ResourceBundle; -import org.eclipse.core.resources.ICommand; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectDescription; -import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Plugin; +import processing.plugin.core.builder.Utilities; + /** * The plug-in runtime class containing the core (UI-free) support for Processing * sketches. *

* Like all plug-in runtime classes (subclasses of Plugin), this class * is automatically instantiated by the platform when the plug-in gets activated. - * Clients must not attempt to instantiate plug-in runtime classes directly. *

*

* The single instance of this class can be accessed from any plug-in declaring the * Processing core plug-in as a prerequisite via - * ProcessingCore.getProcessingCore(). The Processing model plug-in - * will be activated automatically if it is not already active. + * ProcessingCore.getProcessingCore(). The Processing core plug-in + * will be activated and instantiated automatically if it is not already active. *

*/ public final class ProcessingCore extends Plugin { - /* The shared instance */ - private static Plugin PROCESSING_CORE_PLUGIN = null; - - /** - * The plug-in identifier of the Processing core support - * (value "processing.plugin.core"). - */ + /** The plug-in identifier of the Processing core support */ public static final String PLUGIN_ID = "processing.plugin.core"; - - /** - * The identifier for the Processing builder - * (value "processing.plugin.core.processingbuilder"). - */ - public static final String BUILDER_ID = PLUGIN_ID + ".sketchBuilder"; - - /** - * The identifier for the Processing nature - * (value "processing.plugin.core.processingnature"). - * The presence of this nature indicates that it is a Processing Sketch. - */ - public static final String NATURE_ID = PLUGIN_ID + ".sketchNature"; - - /** - * Problem marker for processing build chain issues - * (value "processing.plugin.core.p5marker"). - * These are just vanilla problem markers wrapped for easy identification. - */ - public static final String MARKER_ID = ProcessingCore.PLUGIN_ID + ".p5marker"; - + + /** shared plugin object */ + private static ProcessingCore plugin; + + /** shared resource bundle */ + private ResourceBundle resourceBundle; + /** * Creates the Processing core plug-in. *

@@ -68,112 +62,119 @@ public final class ProcessingCore extends Plugin { */ public ProcessingCore(){ super(); - PROCESSING_CORE_PLUGIN = this; + plugin = this; + try{ + this.resourceBundle = ResourceBundle.getBundle(PLUGIN_ID + ".CorePluginResources"); + } catch (MissingResourceException x){ + this.resourceBundle = null; + } } - - /* public void start(BundleContext context) throws Exception { */ - /* public void stop(BundleContext context) throws Exception { */ - /** Returns the single instance of the Processing core plug-in runtime class. + // special initialization and shutdown goes here + /* public void start(BundleContext context) throws Exception {} */ + /* public void stop(BundleContext context) throws Exception {} */ + + + /** + * Gets a URL to a file or folder in the plug-in's Resources folder. + * Returns null if something went wrong. + */ + public URL getPluginResource(String fileName){ + try{ + return new URL(this.getBundle().getEntry("/"), "Resources/" + fileName); + } catch (MalformedURLException e){ + return null; + } + } + + /** Returns a file handle to the plug-in's local cache folder. */ + public File getBuiltInCacheFolder(){ + return new File(this.getStateLocation().toString()); + } + + /** Returns the plug-in's resource bundle */ + public ResourceBundle getResourceBundle(){ + return resourceBundle; + } + + /** + * Get the workspace the platform workspace. + */ + public static IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace(); + } + + /** + * Returns the single instance of the Processing core plug-in runtime class. * - * @return the single instance of the Processing core plug-in runtime class + * @return the single instance of the Processing core plug-in runtime class */ public static ProcessingCore getProcessingCore(){ - return (ProcessingCore) PROCESSING_CORE_PLUGIN; + return plugin; + } + + /** Returns true if the resource is a Processing file */ + public static boolean isPDEFile(IResource resource){ + if (resource.getType() == IResource.FILE) + return isPDEFilename(resource.getName()); + return false; + } + + /** Returns true if the file is a Processing file */ + public static boolean isPDEFile(IFile resource) { + return isPDEFilename(resource.getName()); + } + + /** Returns true if the file has a Processing extension */ + public static boolean isPDEFilename(String filename){ + return filename.endsWith(".pde"); + } + + /** Returns true if the IFolder is a Processing library root folder */ + public static boolean isLibrary(IFolder rootFolder){ + return isLibrary(rootFolder.getFullPath().toFile()); + } + + /** + * Returns true if the folder is a Processing library root folder and + * only complains if there is an error. + */ + public static boolean isLibrary(File rootFolder){ + return isLibrary(rootFolder, false); } /** - * Adds the Sketch builder to a project - *

- * The preferred way to do this is with the Sketch nature. Even though this is - * public clients should consider using the Sketch nature instead of directly - * adding the builder. - * - * @param project the project having the builder added to it + * Returns true if the folder is a Processing library root folder. + * When complain is false only errors are logged and reported. When + * complain is true the standard PDE warning for improperly named + * libraries will also be reported. */ - public static void addBuilderToProject(IProject project){ - - if (!project.isOpen()) - return; - - IProjectDescription description; - try{ - description = project.getDescription(); - } catch (Exception e){ - ProcessingLog.logError(e); - return; - } - - // Look for builders already associated with the project - ICommand[] cmds = description.getBuildSpec(); - for (int j = 0; j < cmds.length; j++){ - if (cmds[j].getBuilderName().equals(BUILDER_ID)) - return; - } - - //Associate builder with project. - ICommand newCmd = description.newCommand(); - newCmd.setBuilderName(BUILDER_ID); - List newCmds = new ArrayList(); - newCmds.addAll(Arrays.asList(cmds)); - newCmds.add(newCmd); - description.setBuildSpec( - (ICommand[]) newCmds.toArray(new ICommand[newCmds.size()])); - try{ - project.setDescription(description,null); - } catch (CoreException e){ - ProcessingLog.logError(e); - } - } - - /** - * Remove the Sketch builder from the project - * - * If the builder is being managed by the sketch nature, calling this may cause - * problems with the nature life cycle. - * - * - * @param project the project whose build spec we are to modify - * @see processing.plugin.core.builder.SketchNature - */ - public static void removeBuilderFromProject(IProject project){ - - if (!project.isOpen()) - return; - - IProjectDescription description; - try{ - description = project.getDescription(); - } catch (Exception e){ - ProcessingLog.logError(e); - return; - } - - // Look for the builder - int index = -1; - ICommand[] cmds = description.getBuildSpec(); - for (int j = 0; j < cmds.length; j++){ - if (cmds[j].getBuilderName().equals(BUILDER_ID)){ - index = j; - break; + public static boolean isLibrary(File rootFolder, boolean complain){ + if (rootFolder != null){ + String name = rootFolder.getName(); + try { + File libraryJar = new File(rootFolder.getCanonicalPath() + + File.separatorChar + "library" + File.separatorChar + + name + ".jar"); + if (libraryJar.exists()) + if (Utilities.sanitizeName(name).equals(name)){ + return true; + } else { + if(complain){ + String mess = + "The library \"" + name + "\" cannot be used.\n" + + "Library names must contain only basic letters and numbers.\n" + + "(ASCII only and no spaces, and it cannot start with a number)"; + ProcessingLog.logInfo("Ignoring bad library " + name + "\n" + mess); + } + } + } catch (IOException e) { + ProcessingLog.logError("Problem checking librarary " + + name + ", could not resolve canonical path.", e); } } - if (index == -1) - return; - - //Remove builder with project. - List newCmds = new ArrayList(); - newCmds.addAll(Arrays.asList(cmds)); - newCmds.remove(index); - description.setBuildSpec( - (ICommand[]) newCmds.toArray(new ICommand[newCmds.size()])); - try{ - project.setDescription(description,null); - } catch (CoreException e){ - ProcessingLog.logError(e); - } + return false; } - - - + + } diff --git a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java index c781291eb..fa94429b8 100644 --- a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java +++ b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchBuilder.java @@ -1,14 +1,9 @@ package processing.plugin.core.builder; -import java.io.BufferedInputStream; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; @@ -24,18 +19,26 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.JavaRuntime; import org.osgi.framework.Bundle; import processing.app.Preferences; import processing.app.Sketch; +import processing.app.debug.RunnerException; import processing.app.preproc.PdePreprocessor; import processing.app.preproc.PreprocessResult; import processing.core.PApplet; import processing.plugin.core.ProcessingCore; +import processing.plugin.core.ProcessingCorePreferences; import processing.plugin.core.ProcessingLog; /** @@ -61,6 +64,24 @@ import processing.plugin.core.ProcessingLog; */ public class SketchBuilder extends IncrementalProjectBuilder{ + /** + * The identifier for the Processing builder + * (value "processing.plugin.core.processingbuilder"). + */ + public static final String BUILDER_ID = ProcessingCore.PLUGIN_ID + ".sketchBuilder"; + + /** + * Problem marker for processing preprocessor issues + * (value "processing.plugin.core.preprocError"). + */ + public static final String PREPROCMARKER = ProcessingCore.PLUGIN_ID + ".preprocError"; + + /** + * Problem marker for processing compile issues + * value "processing.plugin.core.compileError" + */ + public static final String COMPILEMARKER = ProcessingCore.PLUGIN_ID + ".compileError"; + /** All of these need to be set for the Processing.app classes. */ static{ Preferences.set("editor.tabs.size", "4"); @@ -81,8 +102,10 @@ public class SketchBuilder extends IncrementalProjectBuilder{ ///** For testing */ //public boolean DEBUG = true; - /** Sketch folder that contains the sketch */ - private IProject sketch; + // TODO the builder is maintaining too much state. move this all to a model + // Right now it is building an ad-hoc model on the fly using the implied + // structure of a Sketch project. Minimally a model should manage these state + // objects, manage markers, and be controlled by the SketchProject object. /** Data folder, located in the sketch folder, may not exist */ private IFolder dataFolder; @@ -116,27 +139,64 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** Supplied as a build argument, usually empty */ private String packageName = ""; - - /** - * Build the sketch. - *

- * 1. Prepare the Sketch. - * 2. Preprocess the files to Java - * 3. Shuffle the generated Java and supporting libs into the proper folders - *

- * Unlike the PDE build chain, the Sketch Builder stops short of compiling the generated files. - * In a properly configured project the Java builder will run after the Sketch builder finishes - * and automagically handle the heavy compiling work. - * - * - * @param kind the build type - * @param args build arguments - * @param monitor let the user know things are happening - * @throws CoreException for I/O problems, the Eclipse UI should handle these automatically - */ - protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { - this.sketch = getProject(); + /** Clean any leftover state from previous builds. */ + protected void clean(SketchProject sketch, IProgressMonitor monitor) throws CoreException{ + deleteP5ProblemMarkers(sketch); + if (buildFolder != null && buildFolder.exists()){ + for( IResource r : buildFolder.members()){ + r.delete(IResource.FORCE, monitor); + } + } + // any other cleaning stuff goes here + // Eventually, a model should control the markers, and once a model is + // written it should be controlled by the SketchProject. 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(); + } + + /** + * Build the sketch project. + *

+ * 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. + *

+ *

+ * For now all builds are full builds because the preprocessor does not + * handle incremental builds. + *

+ * + */ + protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException{ + SketchProject sketch = SketchProject.forProject(this.getProject()); + // Eventually we may distinguish between build types. + // 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 + // } + return this.fullBuild(sketch, monitor); + } + + /** + * Full build from scratch. + *

+ * Try to clean out and old markers from derived files. They may not be present, + * but if they are wipe 'em out and get along with the business of building. + * This can be a long running process, so we use a monitor. + */ + protected IProject[] fullBuild( SketchProject sketchProject, IProgressMonitor monitor) throws CoreException { + this.clean(sketchProject, monitor); + IProject sketch = sketchProject.getProject(); + if ( sketch == null || !sketch.isAccessible() ){ System.out.println("Sketch is null!"); return null; @@ -152,13 +212,6 @@ public class SketchBuilder extends IncrementalProjectBuilder{ if(!sketch.isOpen()) { return null; } // has to be open to access it if(checkCancel(monitor)) { return null; } - deleteP5ProblemMarkers(sketch); - - // save time on autobuilds, but respect clean and full builds - if (kind == IncrementalProjectBuilder.CLEAN_BUILD || kind == IncrementalProjectBuilder.FULL_BUILD){ - removeDerived(monitor); // delete all the old state - } - // 1 . PREPARE if (!buildFolder.exists()) @@ -195,12 +248,12 @@ public class SketchBuilder extends IncrementalProjectBuilder{ for( IResource file : sketch.members()){ if(file.getFileExtension() != null && file.getFileExtension().equalsIgnoreCase("pde")){ - file.setSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc start"), bigCount); + file.setSessionProperty(new QualifiedName(BUILDER_ID, "preproc start"), bigCount); String content = Utilities.readFile((IFile) file); bigCode.append(content); bigCode.append("\n"); bigCount += Utilities.getLineCount(content); - file.setSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc end"), bigCount); + file.setSessionProperty(new QualifiedName(BUILDER_ID, "preproc end"), bigCount); } } @@ -212,7 +265,7 @@ public class SketchBuilder extends IncrementalProjectBuilder{ IFile outputFile = buildFolder.getFile(sketch.getName() + ".java"); StringWriter stream = new StringWriter(); result = preproc.write(stream, bigCode.toString(), codeFolderPackages); - + // Eclipse idiom for generating the java file and marking it as a generated file ByteArrayInputStream inStream = new ByteArrayInputStream(stream.toString().getBytes()); try{ @@ -233,8 +286,8 @@ public class SketchBuilder extends IncrementalProjectBuilder{ for( IResource file : sketch.members()){ if(file.getFileExtension() != null && file.getFileExtension().equalsIgnoreCase("pde")){ - int low = (Integer) file.getSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc start")); - int high = (Integer) file.getSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc end")); + int low = (Integer) file.getSessionProperty(new QualifiedName(BUILDER_ID, "preproc start")); + int high = (Integer) file.getSessionProperty(new QualifiedName(BUILDER_ID, "preproc end")); if( low <= errorLine && high > errorLine){ errorFile = file; errorLine -= low; @@ -283,8 +336,8 @@ public class SketchBuilder extends IncrementalProjectBuilder{ for( IResource file : sketch.members()){ if(file.getFileExtension() != null && file.getFileExtension().equalsIgnoreCase("pde")){ - int low = (Integer) file.getSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc start")); - int high = (Integer) file.getSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc end")); + int low = (Integer) file.getSessionProperty(new QualifiedName(BUILDER_ID, "preproc start")); + int high = (Integer) file.getSessionProperty(new QualifiedName(BUILDER_ID, "preproc end")); if( low <= errorLine && high > errorLine){ errorFile = file; errorLine -= low; @@ -302,8 +355,55 @@ public class SketchBuilder extends IncrementalProjectBuilder{ errorLine = -1; } - reportProblem(tsre.getMessage(), errorFile, errorLine, true); + reportProblem(tsre.getMessage(), errorFile, errorLine, true); return null; + } catch (RunnerException re){ + /* + * This error is not addressed in the PDE. I've only seen it correspond to + * an unclosed, double quote mark ("). The runner reports 1 line behind where + * it occurs, so we add 1 to its line. + */ + IResource errorFile = null; // if this remains null, the error is reported back on the sketch itself with no line + int errorLine = re.getCodeLine() + 1; + + for( IResource file : sketch.members()){ + if(file.getFileExtension() != null && file.getFileExtension().equalsIgnoreCase("pde")){ + int low = (Integer) file.getSessionProperty(new QualifiedName(BUILDER_ID, "preproc start")); + int high = (Integer) file.getSessionProperty(new QualifiedName(BUILDER_ID, "preproc end")); + if( low <= errorLine && high > errorLine){ + errorFile = file; + errorLine -= low; + break; + } + } + } + + // mark the whole project if no file will step forward. + if (errorFile == null){ + errorFile = sketch; + errorLine = -1; + } + + //DEBUG + //System.out.println("error line - error file - offset"); + //System.out.println(errorLine + " - " + errorFile + " - " + getPreprocOffset((IFile) folderContents[errorFile])); + + String msg = re.getMessage(); + + //TODO better remapping of errors, matching errors often get put after the document end. see highlightLine() inside editor. + if (msg.equals("expecting RCURLY, found 'null'")) + msg = "Found one too many { characters without a } to match it."; + if (msg.indexOf("expecting RBRACK") != -1) + msg = "Syntax error, maybe a missing right ] character?"; + if (msg.indexOf("expecting SEMI") != -1) + msg = "Syntax error, maybe a missing semicolon?"; + if (msg.indexOf("expecting RPAREN") != -1) + msg = "Syntax error, maybe a missing right parenthesis?"; + if (msg.indexOf("preproc.web_colors") != -1) + msg = "A web color (such as #ffcc00) must be six digits."; + + reportProblem(msg, errorFile, errorLine, true); + return null; // exit the build } catch (CoreException e){ ProcessingLog.logError(e); // logging the error is a better return null; @@ -318,11 +418,13 @@ public class SketchBuilder extends IncrementalProjectBuilder{ // Get the imports from the code that was preproc'd importedLibraries = new ArrayList(); - coreLibs = getCoreLibsFolder(); - sketchBookLibs = getSketchBookLibsFolder(); + coreLibs = getCoreLibsFolder().getAbsoluteFile(); + sketchBookLibs = getSketchBookLibsFolder(sketch).getAbsoluteFile(); // Clean the library table and rebuild it importToLibraryTable = new HashMap(); + + // addLibraries internally checks for null folders try{ addLibraries(coreLibs); addLibraries(sketchBookLibs); @@ -364,12 +466,12 @@ public class SketchBuilder extends IncrementalProjectBuilder{ if(pkg == null){ pkg = new String[] { packageName }; // add the package name to the source - program = "package " + packageName + ";" + program; + program = "package " + packageName + ";" + program; } IFolder packageFolder = buildFolder.getFolder(pkg[0].replace('.', '/')); if (!packageFolder.exists()) packageFolder.create(IResource.NONE, true, null); - + IFile modFile = packageFolder.getFile(file.getName() + ".java"); ByteArrayInputStream inStream = new ByteArrayInputStream(program.getBytes()); @@ -389,37 +491,41 @@ public class SketchBuilder extends IncrementalProjectBuilder{ } else if (file.getFileExtension() != null && file.getFileExtension().equalsIgnoreCase("pde")){ // The compiler will need this to have a proper offset // not sure why every file gets the same offset, but ok I'll go with it... [lonnen] aug 20 2011 - file.setSessionProperty(new QualifiedName(ProcessingCore.BUILDER_ID, "preproc start"), result.headerOffset); + file.setSessionProperty(new QualifiedName(BUILDER_ID, "preproc start"), result.headerOffset); } } - boolean foundMain = preproc.getFoundMain(); // is this still necessary? - monitor.worked(10); if(checkCancel(monitor)) { return null; } //COMPILE + + // setup the VM + IPath containerPath = new Path(JavaRuntime.JRE_CONTAINER); + IVMInstall vm = JavaRuntime.getDefaultVMInstall(); + IPath vmPath = containerPath.append(vm.getVMInstallType().getId()).append(vm.getName()); + + System.out.println("classPath entries:"); + for( String s : classPath.split(File.pathSeparator)){ + if (!s.isEmpty()) + System.out.println(s); + } + + System.out.println("IClasspathEntry[] items:"); + // Build the classpath entries + IClasspathEntry[] newClasspath = { + JavaCore.newSourceEntry(buildFolder.getFullPath()), + //JavaCore.newLibraryEntry(), + JavaCore.newContainerEntry(vmPath) + }; + for (IClasspathEntry s : newClasspath){ + System.out.println(s.toString()); + } return null; } - /** - * Removes all the derived files and markers generated by previous builds. - * If there are issues expect a CoreException with details. Deletion can be - * a long running operation, so give it access to the monitor. - * - * @param monitor - * @throws CoreException - */ - private void removeDerived(IProgressMonitor monitor) throws CoreException { - if (buildFolder.exists()){ - for( IResource r : buildFolder.members()){ - r.delete(IResource.FORCE, monitor); - } - } - } - /** * Try to delete all of the existing P5 problem markers *

@@ -431,7 +537,16 @@ public class SketchBuilder extends IncrementalProjectBuilder{ */ protected static void deleteP5ProblemMarkers(IResource resource) throws CoreException{ if(resource != null && resource.exists()) - resource.deleteMarkers(ProcessingCore.MARKER_ID, true, IResource.DEPTH_INFINITE); + resource.deleteMarkers(SketchBuilder.PREPROCMARKER, true, IResource.DEPTH_INFINITE); + resource.deleteMarkers(SketchBuilder.COMPILEMARKER, true, IResource.DEPTH_INFINITE); + } + + protected static void deleteP5ProblemMarkers(SketchProject sketch)throws CoreException{ + if(sketch != null){ + IProject proj = sketch.getProject(); + if (proj.exists()) + deleteP5ProblemMarkers(proj); + } } /** @@ -447,7 +562,7 @@ public class SketchBuilder extends IncrementalProjectBuilder{ */ private void reportProblem( String msg, IResource file, int lineNumber, boolean isError){ try{ - IMarker marker = file.createMarker(ProcessingCore.MARKER_ID); + IMarker marker = file.createMarker(SketchBuilder.PREPROCMARKER); marker.setAttribute(IMarker.MESSAGE, msg); marker.setAttribute(IMarker.SEVERITY, isError ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING); if( lineNumber != -1) @@ -480,16 +595,16 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** * Finds any Processing Libraries in the folder and loads them into the HashMap * importToLibraryTable so they can be imported later. Based on the Base.addLibraries, - * but adapted to avoid GUI elements + * but adapted to avoid GUI elements * * @param folder the folder containing libraries * @return true if libraries were imported, false otherwise */ - public boolean addLibraries(File folder) throws IOException{ + public boolean addLibraries(File folder) throws IOException{ if (folder == null) return false; if (!folder.isDirectory()) return false; - String list[] = folder.list(new FilenameFilter() { + File list[] = folder.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { // skip .DS_Store files, .svn folders, etc if (name.charAt(0) == '.') return false; @@ -501,86 +616,85 @@ public class SketchBuilder extends IncrementalProjectBuilder{ // if a bad folder or something like that, this might come back null if (list == null) return false; - // alphabetize list, since it's not always alpha order - Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); - boolean ifound = false; - for (String potentialName : list) { - File subfolder = new File(folder, potentialName); - File libraryFolder = new File(subfolder, "library"); - File libraryJar = new File(libraryFolder, potentialName + ".jar"); - // look for the .jar inside //library/.jar - if (libraryJar.exists()) { - String sanityCheck = Sketch.sanitizeName(potentialName); // TODO remove this processing.app class dependency - if (!sanityCheck.equals(potentialName)) { - String mess = - "The library \"" + potentialName + "\" cannot be used.\n" + - "Library names must contain only basic letters and numbers.\n" + - "(ASCII only and no spaces, and it cannot start with a number)"; - ProcessingLog.logInfo("Ignoring bad library name \n" + mess); - continue; - } - - // get the path for all .jar files in this code folder + for (File potentialFile : list){ + if(ProcessingCore.isLibrary(potentialFile, true)){ + File libraryFolder = new File(potentialFile, "library"); + // get the path of all .jar files in this code folder String libraryClassPath = Utilities.contentsToClassPath(libraryFolder); - - // the librariesClassPath is not used in the main build process - //librariesClassPath += File.pathSeparatorChar + libraryClassPath; - - // need to associate each import with a library folder - String packages[] = Utilities.packageListFromClassPath(libraryClassPath); - for (String pkg : packages) { + // associate each import with a library folder + String packages[] = Utilities.packageListFromClassPath(libraryClassPath); + for (String pkg:packages){ importToLibraryTable.put(pkg, libraryFolder); } - ifound = true; - - } else { - // not a library, but is still a folder, so recurse - boolean found = addLibraries(subfolder); - if (found) ifound = true; // it was found in the subfolder + } else { + if (addLibraries(potentialFile)) // recurse! + ifound = true; } } + return ifound; } /** * Finds the folder containing the Processing core libraries, which are bundled with the - * plugin. This folder doesn't exist in the workspace, so we return it as a plain File. - * If a CoreException is thrown it will be logged and this will return null. + * plugin. This folder doesn't exist in the workspace, so we return it as a File, not IFile. + * If something goes wrong, logs an error and returns null. * - * @return File containing the core libraries folder or null if it cannot be found + * @return File containing the core libraries folder or null */ public File getCoreLibsFolder() { - Bundle bundle = ProcessingCore.getProcessingCore().getBundle(); - URL fileLocation; + URL fileLocation = ProcessingCore.getProcessingCore().getPluginResource("libraries"); try { - fileLocation = FileLocator.toFileURL(bundle.getEntry("/Resources" + File.pathSeparatorChar + "libraries")); - File folder = new File(fileLocation.getPath()); - folder.isDirectory(); // throw an error if the folder does not exist - return folder; + File folder = new File(FileLocator.toFileURL(fileLocation).getPath()); + if (folder.exists()) + return folder; } catch (Exception e) { ProcessingLog.logError(e); - return null; } + return null; } /** - * Find the folder containing the Processing sketchbook libraries, which should be contained - * in the same directory as the sketch itself. This only returns a handle to the folder. If - * the folder does not exist it will be logged and returned as null + * Find the folder containing the users libraries, which should be in the sketchbook. + * Looks in the user's preferences first and if that is null it checks for the appropriate + * folder relative to the sketch location. * - * @return File containing the Sketch book library folder, or null if it doesn't exist + * @return File containing the Sketch book library folder, or null if it can't be located */ - public File getSketchBookLibsFolder() { + public File getSketchBookLibsFolder(IProject proj) { + IPath sketchbook = ProcessingCorePreferences.current().getSketchbookPath(); + if (sketchbook == null) + sketchbook = findSketchBookLibsFolder(proj); + if (sketchbook == null) + return null; + return new File(sketchbook.toOSString()); + } + + /** + * Tries to locate the sketchbook library folder relative to the project path + * based on the default sketch / sketchbook setup. If such a folder exists, loop + * through its contents until a valid library is found and then return the path + * to the sketchbook. If no valid libraries are found (empty folder, improper + * sketchbook setup), or if no valid folder is found, return null. + * + * @return IPath containing the location of the new library folder, or null + */ + public IPath findSketchBookLibsFolder(IProject proj) { try{ - File folder = sketch.getLocation().removeLastSegments(1).append("libraries").toFile(); - folder.isDirectory(); // throw an error if the folder does not exist - return folder; + IPath guess = proj.getLocation().removeLastSegments(1).append("libraries"); + File folder = new File(guess.toOSString()); + if(folder.isDirectory()) + for( File file : folder.listFiles()){ + if(file.isDirectory()) + if (ProcessingCore.isLibrary(file)) + return guess; + } } catch (Exception e){ ProcessingLog.logError(e); - return null; } + return null; } } diff --git a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchNature.java b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchNature.java deleted file mode 100644 index 0cedc0f9e..000000000 --- a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchNature.java +++ /dev/null @@ -1,136 +0,0 @@ -package processing.plugin.core.builder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectDescription; -import org.eclipse.core.resources.IProjectNature; -import org.eclipse.core.resources.IncrementalProjectBuilder; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; - -import processing.plugin.core.ProcessingCore; -import processing.plugin.core.ProcessingLog; - -public class SketchNature implements IProjectNature { - - /** The project this SketchNature is modifying */ - protected IProject project; - - /** Access method for this natures project */ - public IProject getProject() { - return project; - } - - /** Set the project this nature is managing */ - public void setProject(IProject project) { - this.project = project; - } - - /** Associate the sketch builder with this nature's project */ - public void configure() throws CoreException { - ProcessingCore.addBuilderToProject(project); - new Job("Build Sketch"){ - protected IStatus run(IProgressMonitor monitor){ - try{ - project.build( - IncrementalProjectBuilder.FULL_BUILD, - ProcessingCore.BUILDER_ID, - null, - monitor); - } catch (CoreException e){ - ProcessingLog.logError(e); - } - return Status.OK_STATUS; - } - }.schedule(); - } - - /** - * Dissociate the processing sketch builder from this nature's - * project and remove the markers it generated. - */ - public void deconfigure() throws CoreException { - ProcessingCore.removeBuilderFromProject(project); - SketchBuilder.deleteP5ProblemMarkers(project); - } - - /** - * Adds the sketch nature to the project if it is accessible and - * does not already have the sketch nature. - * - * @param project - */ - public static void addNature(IProject project){ - // Cannot modify closed projects. - if (!project.isOpen()) - return; - - try { - // If it has the nature already, we're done - if (project.hasNature(ProcessingCore.NATURE_ID)) - return; - - IProjectDescription description = project.getDescription();; - - List newIds = new ArrayList(); - newIds.addAll(Arrays.asList(description.getNatureIds())); - newIds.add(ProcessingCore.NATURE_ID); - description.setNatureIds(newIds.toArray(new String[newIds.size()])); - - project.setDescription(description, null); - - } catch(CoreException e){ - ProcessingLog.logError(e); - } - } - - /** - * Tests a project to see if it has the Processing nature - * - * IProject project to test - * returns true if the project has the processing nature - */ - public static boolean hasNature(IProject project){ - try{ - return project.hasNature(ProcessingCore.NATURE_ID); - } catch (CoreException e){ - // project doesn't exist or is not open - return false; - } - } - - /** - * Removes the nature from the project if it has it - * - * @param project - */ - public static void removeNature(IProject project){ - // Cannot modify closed projects - if (!project.isOpen()) - return; - try{ - // doesn't have the nature, we're done - if (!project.hasNature(ProcessingCore.NATURE_ID)) - return; - - IProjectDescription description = project.getDescription(); - - List newIds = new ArrayList(); - newIds.addAll(Arrays.asList(description.getNatureIds())); - newIds.remove(newIds.indexOf(ProcessingCore.NATURE_ID)); - description.setNatureIds(newIds.toArray(new String[newIds.size()])); - - project.setDescription(description,null); - - } catch (CoreException e){ - ProcessingLog.logError(e); - return; - } - } -} diff --git a/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchProject.java b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchProject.java new file mode 100644 index 000000000..ea4ccc998 --- /dev/null +++ b/editor/processing.plugin.core/src/processing/plugin/core/builder/SketchProject.java @@ -0,0 +1,241 @@ +package processing.plugin.core.builder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IProjectNature; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.JavaRuntime; + +import processing.plugin.core.ProcessingCore; +import processing.plugin.core.ProcessingLog; + +public class SketchProject implements IProjectNature { + + /** value: "processing.plugin.core.processingnature" */ + public static final String NATURE_ID = ProcessingCore.PLUGIN_ID + ".sketchNature"; + + /** The basic project entry being managed */ + protected IProject project; + + /** The Java project underlying the SketchProject */ + protected IJavaProject jproject; + + /** + * Return the SketchProject associated with the given IProject, or null + * if the project is not associated with a SketchProject. + */ + public static SketchProject forProject(IProject project){ + IProjectNature nature = null; + try{ + nature = project.getNature(NATURE_ID); + } catch (CoreException e){ + return null; + } + return (SketchProject) nature; + } + + /** True if the project has the SketchProject nature, false otherwise. */ + public static boolean isSketchProject(IProject project){ + return SketchProject.forProject(project) != null; + } + + /** + * Add the Sketch Project nature to the front of the nature list if the + * project doesn't already have it as a nature, or move it to the front + * of the nature list if it does. + */ + public static void addSketchNature(IProject project) throws CoreException{ + if (!project.isOpen()) + return; + + if (SketchProject.isSketchProject(project)){ + IProjectDescription description = project.getDescription(); + List newIds = new ArrayList(); + newIds.addAll(Arrays.asList(description.getNatureIds())); + newIds.remove(NATURE_ID); + newIds.add(0,NATURE_ID); + description.setNatureIds(newIds.toArray(new String[newIds.size()])); + return; + } + + IProjectDescription description = project.getDescription(); + + List newIds = new ArrayList(); + newIds.add(NATURE_ID); // front of the line + newIds.add(JavaCore.NATURE_ID); // add the nature ID afterwards + newIds.addAll(Arrays.asList(description.getNatureIds())); + description.setNatureIds(newIds.toArray(new String[newIds.size()])); + + project.setDescription(description, null); + } + + /** Removes the nature from the project if it has it */ + public static void removeSketchNature(IProject project) throws CoreException{ + if (!project.isOpen()) + return; + + // doesn't have the nature, we're done + if (!SketchProject.isSketchProject(project)) + return; + + IProjectDescription description = project.getDescription(); + + List newIds = new ArrayList(); + newIds.addAll(Arrays.asList(description.getNatureIds())); + newIds.remove(newIds.indexOf(NATURE_ID)); + description.setNatureIds(newIds.toArray(new String[newIds.size()])); + + project.setDescription(description,null); + + } + + /** Access method for this nature's project */ + public IProject getProject() { + return project; + } + + /** + * Should not be triggered by clients. + * Sent by the NatureManager when the nature is added to a project. + */ + public void setProject(IProject project) { + this.project = project; + } + + /** Access method for this nature's java project */ + public IJavaProject getJavaProject(){ + return jproject; + } + + /** Associate the sketch builder with this nature's project */ + public void configure() throws CoreException { + if (!project.isOpen()) + return; + + // Setup the folders + IFolder codeFolder = project.getFolder("code"); + IFolder dataFolder = project.getFolder("data"); + IFolder buildFolder = project.getFolder("bin"); // TODO relocate to MyPlugin.getPlugin().getStateLocation().getFolder("bin") + IFolder appletFolder = project.getFolder("applet"); + + if(!codeFolder.exists()) + buildFolder.create(IResource.NONE, true, null); + if(!dataFolder.exists()) + dataFolder.create(IResource.NONE, true, null); + if(!buildFolder.exists()) + buildFolder.create(IResource.NONE, true, null); + if(!appletFolder.exists()) + appletFolder.create(IResource.NONE, true, null); + + // Setup the Java project underlying the Sketch + jproject = JavaCore.create(project); + + // Check the description to see if it already has the builder + IProjectDescription description = this.project.getDescription(); + List newCmds = new ArrayList(); + newCmds.addAll(Arrays.asList(description.getBuildSpec())); + + int ploc = -1; // builder ID location + for (int i = 0; i < newCmds.size(); i++){ + if (newCmds.get(i).getBuilderName().equals(SketchBuilder.BUILDER_ID)) + ploc = i; + } + + if (ploc == 0) // its there and where we want it + return; + + if (ploc > 0) + newCmds.remove(ploc); // its not where we want it, remove it and add to the beggining + + ICommand command = description.newCommand(); + command.setBuilderName(SketchBuilder.BUILDER_ID); + newCmds.add(0, command); + description.setBuildSpec( + (ICommand[]) newCmds.toArray(new ICommand[newCmds.size()])); + project.setDescription(description,null); + + // refresh the local space, folders were created + project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); + + // schedule and run a build once its added. + /*new Job("Build Sketch"){ + protected IStatus run(IProgressMonitor monitor){ + try{ + project.build( + IncrementalProjectBuilder.FULL_BUILD, + SketchBuilder.BUILDER_ID, + null, + monitor); + } catch (CoreException e){ + ProcessingLog.logError(e); + } + return Status.OK_STATUS; + } + }.schedule();*/ + + } + + /** + * Dissociate the processing sketch builder from this nature's + * project and remove the markers it generated. + */ + public void deconfigure() throws CoreException { + if (!project.isOpen()) + return; + + IProjectDescription description = this.project.getDescription(); + ICommand[] cmds = description.getBuildSpec(); + for (int i=0; i newCmds = new ArrayList(); + newCmds.addAll(Arrays.asList(cmds)); + newCmds.remove(i); + description.setBuildSpec(newCmds.toArray(new ICommand[newCmds.size()])); + } + } + + // clean up your tokens + SketchBuilder.deleteP5ProblemMarkers(project); + } + + /** Returns a qualified name for the property relative to this plug-in */ + public QualifiedName getPropertyName(String localName){ + return new QualifiedName(ProcessingCore.PLUGIN_ID, localName); + } + + /** + * Sets a persistent property in the pojects property store + * If there is an exception it will be reported and the project + * will not persist. + **/ + public void setPersistentProperty(String localName, String value){ + try{ + this.project.setPersistentProperty(this.getPropertyName(localName), value); + } catch (CoreException e){ + ProcessingLog.logError(e); + } + } + + /** Trigger a full build of the project being managed */ + public void fullBuild(IProgressMonitor monitor) throws CoreException{ + project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); + } + +} diff --git a/editor/processing.plugin.ui/META-INF/MANIFEST.MF b/editor/processing.plugin.ui/META-INF/MANIFEST.MF index a5498a991..f5c5b2b0a 100644 --- a/editor/processing.plugin.ui/META-INF/MANIFEST.MF +++ b/editor/processing.plugin.ui/META-INF/MANIFEST.MF @@ -8,10 +8,12 @@ Bundle-Vendor: Processing.org Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.core.filebuffers, + org.eclipse.jdt.debug.ui, org.eclipse.jface.text;bundle-version="3.6.0", org.eclipse.ui.editors;bundle-version="3.6.0", org.eclipse.core.resources;bundle-version="3.6.0", processing.plugin.core;bundle-version="0.1.0" + Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy Import-Package: org.eclipse.ui.actions diff --git a/editor/processing.plugin.ui/plugin.xml b/editor/processing.plugin.ui/plugin.xml index 0fe7be4c3..b31fdaed6 100644 --- a/editor/processing.plugin.ui/plugin.xml +++ b/editor/processing.plugin.ui/plugin.xml @@ -18,10 +18,6 @@ extensions="pde"> -