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"> -