changeset 122:562d1041a7b9

Added initial revision of Eclipse plugin. The plugin adds a PySMCL nature to Eclipse. This nature can be toggled on or off for each project by right-clicking the project and selecting Add/Remove PySMCL Warnings. When enabled, a warning will be displayed for each variable that might contain secure information. For the plugin to work properly, one must specify the location of the Python interpreter and the location of the PySMCL compiler. This is done by selecting Preferences->PySMCL in the Eclipse menu bar. Note: If you import this plugin into Eclipse using its Import feature (Import Plugin Project), you may need to modify META-INF/MANIFEST.MF by replacing Bundle-ClassPath: eclipse with Bundle-ClassPath: . in order to run the plugin.
author Thomas P Jakobsen <tpj@cs.au.dk>
date Mon, 26 Oct 2009 10:34:45 +0100
parents a60551eac165
children 7b2b7c4f30c8
files eclipse/META-INF/MANIFEST.MF eclipse/build.properties eclipse/plugin.xml eclipse/src/eu/cace/pysmcl/Activator.java eclipse/src/eu/cace/pysmcl/builder/ProcessExecutor.java eclipse/src/eu/cace/pysmcl/builder/PySMCLBuilder.java eclipse/src/eu/cace/pysmcl/builder/PySMCLCompiler.java eclipse/src/eu/cace/pysmcl/builder/PySMCLNature.java eclipse/src/eu/cace/pysmcl/builder/ToggleNatureAction.java eclipse/src/eu/cace/pysmcl/preferences/PreferenceConstants.java eclipse/src/eu/cace/pysmcl/preferences/PreferenceInitializer.java eclipse/src/eu/cace/pysmcl/preferences/PreferencePage.java
diffstat 12 files changed, 916 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/META-INF/MANIFEST.MF	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Pysmcl
+Bundle-SymbolicName: eu.cace.pysmcl; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: eu.cace.pysmcl.Activator
+Require-Bundle: org.eclipse.ui,org.eclipse.core.runtime,org.eclipse.core.resources,
+ org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.core.resources
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/build.properties	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = plugin.xml,\
+               META-INF/,\
+               .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/plugin.xml	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+
+   <extension
+         id="pySMCLBuilder"
+         name="PySMCL Project Builder"
+         point="org.eclipse.core.resources.builders">
+      <builder
+            hasNature="true">
+         <run
+               class="eu.cace.pysmcl.builder.PySMCLBuilder">
+         </run>
+      </builder>
+   </extension>
+   <extension
+         id="pySMCLNature"
+         name="PySMCL Project Nature"
+         point="org.eclipse.core.resources.natures">
+      <runtime>
+         <run
+               class="eu.cace.pysmcl.builder.PySMCLNature">
+         </run>
+      </runtime>
+      <builder
+            id="eu.cace.pysmcl.pySMCLBuilder">
+      </builder>
+   </extension>
+   <extension
+         point="org.eclipse.ui.popupMenus">
+      <objectContribution
+            adaptable="true"
+            objectClass="org.eclipse.core.resources.IProject"
+            nameFilter="*"
+            id="eu.cace.pysmcl.contribution1">
+         <action
+               label="Add/Remove PySMCL warnings"
+               class="eu.cace.pysmcl.builder.ToggleNatureAction"
+               menubarPath="additions"
+               enablesFor="+"
+               id="eu.cace.pysmcl.addRemoveNatureAction">
+         </action>
+      </objectContribution>
+   </extension>
+   <extension
+         id="smclProblem"
+         name="SMCL Problem"
+         point="org.eclipse.core.resources.markers">
+      <super
+            type="org.eclipse.core.resources.problemmarker">
+      </super>
+      <persistent
+            value="true">
+      </persistent>
+   </extension>
+   
+   <extension
+         point="org.eclipse.ui.preferencePages">
+      <page
+            name="PySMCL"
+            class="eu.cace.pysmcl.preferences.PreferencePage"
+            id="eu.cace.pysmcl.preferences.SamplePreferencePage">
+      </page>
+   </extension>
+   <extension
+         point="org.eclipse.core.runtime.preferences">
+      <initializer
+            class="eu.cace.pysmcl.preferences.PreferenceInitializer">
+      </initializer>
+   </extension>
+
+</plugin>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/Activator.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,61 @@
+package eu.cace.pysmcl;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "eu.cace.pysmcl";
+
+	// The shared instance
+	private static Activator plugin;
+	
+	/**
+	 * The constructor
+	 */
+	public Activator() {
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static Activator getDefault() {
+		return plugin;
+	}
+
+	/**
+	 * Returns an image descriptor for the image file at the given
+	 * plug-in relative path
+	 *
+	 * @param path the path
+	 * @return the image descriptor
+	 */
+	public static ImageDescriptor getImageDescriptor(String path) {
+		return imageDescriptorFromPlugin(PLUGIN_ID, path);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/builder/ProcessExecutor.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,97 @@
+package eu.cace.pysmcl.builder;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+
+/**
+ * Logic for executing an external process from Java.
+ * 
+ * This is needed since direct usage of ProcessBuilder will potentially deadlock
+ * if it is not run in its own thread.
+ * 
+ */
+class ProcessExecutor {
+
+	private final String[] cmd;
+
+	private final Map<String, String> environ;
+
+	private volatile int exitValue;
+
+	private volatile String output;
+
+	private volatile IOException exception;
+
+	public ProcessExecutor(String[] cmd, Map<String, String> environ) {
+		this.cmd = cmd;
+		this.environ = environ;
+	}
+
+	/**
+	 * Executes the process and blocks until it is finished. Stdout, stderr and
+	 * exit value can then be read using getOutput() and getExitValue().
+	 * 
+	 * @throws IOException
+	 *             If the process couldn't be launched.
+	 * 
+	 */
+	public void run() throws IOException {
+		ProcessThread t = new ProcessThread();
+		t.start();
+		try {
+			t.join();
+		} catch (InterruptedException e) {
+			// Thread interrupted; ok to just continue.
+		}
+		if (null != this.exception)
+			throw this.exception;
+	}
+
+	/**
+	 * Exit value for the process. Should be called only after thread has
+	 * finished.
+	 */
+	public int getExitValue() {
+		return this.exitValue;
+	}
+
+	/**
+	 * Combined stdout and stderr for the executed process. Should be called
+	 * only after thread has finished.
+	 */
+	public String getOutput() {
+		return this.output;
+	}
+
+	private class ProcessThread extends Thread {
+
+		public void run() {
+			ProcessBuilder builder = new ProcessBuilder(cmd);
+			// Merge error stream into output stream.
+			builder.redirectErrorStream(true);
+			Map<String, String> env = builder.environment();
+			env.putAll(environ);
+			StringBuilder res = new StringBuilder();
+			try {
+				final Process process = builder.start();
+				InputStream is = process.getInputStream();
+				InputStreamReader isr = new InputStreamReader(is);
+				BufferedReader br = new BufferedReader(isr);
+				String line;
+				while ((line = br.readLine()) != null) {
+					res.append(line);
+					res.append(System.getProperty("line.separator"));
+				}
+				exitValue = process.waitFor();
+				output = res.toString();
+			} catch (IOException e) {
+				exception = e;
+			} catch (InterruptedException e) {
+				// Thread interrupted; ok to just return.
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/builder/PySMCLBuilder.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,129 @@
+package eu.cace.pysmcl.builder;
+
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+public class PySMCLBuilder extends IncrementalProjectBuilder implements
+		IMarkable {
+
+	/**
+	 * Only files with this file name extension will be compiled with PySMCL.
+	 * 
+	 */
+	private static final String PYSMCL_EXTENSION = ".pysmcl";
+
+	public static final String BUILDER_ID = "eu.cace.pysmcl.pySMCLBuilder";
+
+	private static final String MARKER_TYPE = "eu.cace.pysmcl.smclProblem";
+
+	class SampleDeltaVisitor implements IResourceDeltaVisitor {
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see
+		 * org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse
+		 * .core.resources.IResourceDelta)
+		 */
+		public boolean visit(IResourceDelta delta) throws CoreException {
+			IResource resource = delta.getResource();
+			switch (delta.getKind()) {
+			case IResourceDelta.ADDED:
+				// handle added resource
+				checkResource(resource);
+				break;
+			case IResourceDelta.REMOVED:
+				// handle removed resource
+				break;
+			case IResourceDelta.CHANGED:
+				// handle changed resource
+				checkResource(resource);
+				break;
+			}
+			return true; // Continue visiting next resource.
+		}
+	}
+
+	class SampleResourceVisitor implements IResourceVisitor {
+		public boolean visit(IResource resource) {
+			checkResource(resource);
+			return true; // Continue visiting next resource.
+		}
+	}
+
+	public void addMarker(IFile file, String message, int start, int end,
+			int severity, String location) {
+		try {
+			IMarker marker = file.createMarker(MARKER_TYPE);
+			marker.setAttribute(IMarker.MESSAGE, message);
+			marker.setAttribute(IMarker.SEVERITY, severity);
+			marker.setAttribute(IMarker.CHAR_START, start);
+			marker.setAttribute(IMarker.CHAR_END, end);
+			marker.setAttribute(IMarker.LOCATION, location);
+		} catch (CoreException e) {
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.core.internal.events.InternalBuilder#build(int,
+	 * java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
+	 */
+	@SuppressWarnings("unchecked")
+	protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
+			throws CoreException {
+		if (kind == FULL_BUILD) {
+			fullBuild(monitor);
+		} else {
+			IResourceDelta delta = getDelta(getProject());
+			if (delta == null) {
+				fullBuild(monitor);
+			} else {
+				incrementalBuild(delta, monitor);
+			}
+		}
+		return null;
+	}
+
+	private void checkResource(IResource resource) {
+		if (resource instanceof IFile
+				&& resource.getName().endsWith(PYSMCL_EXTENSION)) {
+			IFile file = (IFile) resource;
+			deleteMarkers(file);
+			CompilerMessageHandler reporter = new CompilerMessageHandler(file,
+					this);
+			PySMCLCompiler.getCompiler().compile(file, reporter);
+		}
+	}
+
+	public void deleteMarkers(IFile file) {
+		try {
+			file.deleteMarkers(MARKER_TYPE, false, IResource.DEPTH_ZERO);
+		} catch (CoreException ce) {
+		}
+	}
+
+	protected void fullBuild(final IProgressMonitor monitor)
+			throws CoreException {
+		try {
+			getProject().accept(new SampleResourceVisitor());
+		} catch (CoreException e) {
+		}
+	}
+
+	protected void incrementalBuild(IResourceDelta delta,
+			IProgressMonitor monitor) throws CoreException {
+		// The visitor does the work.
+		delta.accept(new SampleDeltaVisitor());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/builder/PySMCLCompiler.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,269 @@
+package eu.cace.pysmcl.builder;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import eu.cace.pysmcl.Activator;
+import eu.cace.pysmcl.preferences.PreferenceConstants;
+
+interface IMarkable {
+
+	void addMarker(IFile file, String message, int start, int end,
+			int severity, String location);
+
+	void deleteMarkers(IFile file);
+}
+
+class PySMCLCompiler {
+
+	/**
+	 * Indicates an exceptional condition when compiling with the PySMCL.
+	 * 
+	 */
+	class PySMCLException extends Exception {
+
+		PySMCLException(String msg) {
+			super(msg);
+		}
+
+		PySMCLException(Throwable cause) {
+			super(cause);
+		}
+
+		private static final long serialVersionUID = -5914882464300227898L;
+
+	}
+
+	private static PySMCLCompiler compilerFactory;
+
+	static PySMCLCompiler getCompiler() {
+		if (compilerFactory == null) {
+			compilerFactory = new PySMCLCompiler();
+		}
+		return compilerFactory;
+	}
+
+	public void compile(IFile resource, CompilerMessageHandler handler) {
+
+		try {
+			String result = compile(resource.getRawLocation().makeAbsolute());
+			for (SMCLProblem problem : parsePySMCLOutput(resource, result)) {
+
+				handler.warning(problem.getDescription(), problem
+						.getStartChar(), problem.getEndChar(), problem
+						.getLocation());
+			}
+		} catch (PySMCLException e) {
+			// TODO: How does PySMCL handle badly formatted programs?
+			// We need to be able to differentiate between badly formed programs
+			// and true compilation errors:
+			//
+			// Badly formed programs should probably be ignored while true
+			// compilation errors should be marked with an error at the top
+			// line.
+			handler.error(e.getMessage(), 0, 0, "");
+		}
+
+	}
+
+	/**
+	 * Parses the output from the PySMCL compiler into separate SMCLProblem
+	 * instances.
+	 * 
+	 * @throws PySMCLException
+	 *             If the PySMCL output couldn't be properly parsed.
+	 */
+	private Set<SMCLProblem> parsePySMCLOutput(IFile resource, String output)
+			throws PySMCLException {
+		Set<SMCLProblem> res = new HashSet<SMCLProblem>();
+
+		// TODO: Could be optimized by compiling pattern only once.
+		Pattern pattern = Pattern.compile("\\((\\d+) (\\d+) (\\d+)\\)");
+		Matcher matcher = pattern.matcher(output);
+		while (matcher.find()) {
+			int line = Integer.valueOf(matcher.group(1));
+			int startChar = Integer.valueOf(matcher.group(2));
+			int length = Integer.valueOf(matcher.group(3));
+			String program = readProgram(resource);
+			int lineOffset = getLineOffset(program, line - 1);
+			String location = "Line " + line + " (" + (startChar + 1) + ";"
+					+ (startChar + length) + ")";
+			res.add(new SMCLProblem(lineOffset + startChar, lineOffset
+					+ startChar + length,
+					"This variable may contain secret information", location));
+		}
+		return res;
+	}
+
+	/**
+	 * Reads the program and returns it as a string.
+	 * 
+	 */
+	private String readProgram(IFile resource) throws PySMCLException {
+		StringBuffer res = new StringBuffer();
+		try {
+			InputStream is = resource.getContents();
+			InputStreamReader isr = new InputStreamReader(is);
+			BufferedReader br = new BufferedReader(isr);
+			try {
+				String line = null;
+				while ((line = br.readLine()) != null) {
+					res.append(line);
+					res.append(System.getProperty("line.separator"));
+				}
+			} finally {
+				br.close();
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new PySMCLException(e);
+		}
+		return res.toString();
+	}
+
+	/**
+	 * Convert from char offsets relative to line number to global char offsets.
+	 * 
+	 * @param program
+	 *            The program to compile.
+	 * @param line
+	 *            A line number within the program (zero-based).
+	 * @return A char offset into the program; where the given line begins.
+	 */
+	private int getLineOffset(String program, int line) throws PySMCLException {
+
+		int offset = 0;
+		int lineNo = 0;
+		// TODO: Do we handle Windows line endings correctly?
+		for (String l : program.split(System.getProperty("line.separator"))) {
+			if (line == lineNo)
+				return offset;
+			offset += l.length()
+					+ System.getProperty("line.separator").length();
+			lineNo++;
+		}
+		throw new PySMCLException("Line " + line + " doens't exist in "
+				+ program);
+	}
+
+	/**
+	 * Represents a PySMCL compiler issue error/warning report.
+	 * 
+	 */
+	private class SMCLProblem {
+		private final int startChar;
+		private final int endChar;
+		private final String description;
+		private final String location;
+
+		public SMCLProblem(int start, int end, String description,
+				String location) {
+			this.startChar = start;
+			this.endChar = end;
+			this.description = description;
+			this.location = location;
+		}
+
+		public String getLocation() {
+			return this.location;
+		}
+
+		int getStartChar() {
+			return this.startChar;
+		}
+
+		int getEndChar() {
+			return this.endChar;
+		}
+
+		String getDescription() {
+			return this.description;
+		}
+
+		@Override
+		public String toString() {
+			return "SMCLProblem(" + this.description + ";" + this.startChar
+					+ "," + this.endChar + ")";
+		}
+
+	}
+
+	/**
+	 * Starts PySMCL as an external process.
+	 * 
+	 * @return The output from the PySMCL compiler.
+	 * 
+	 * @throws PySMCLException
+	 *             If the compiler couldn't be invoked or couldn't compile the
+	 *             program.
+	 */
+	private String compile(IPath resource) throws PySMCLException {
+
+		IPreferenceStore store = Activator.getDefault().getPreferenceStore();
+		String python = store.getString(PreferenceConstants.P_PYTHON);
+		String pySmclBasePath = store.getString(PreferenceConstants.P_PYSMCL);
+
+		// TODO: Currently hardcoded to invoke the emacs/info.py program.
+		if (!pySmclBasePath.endsWith(File.pathSeparator))
+			pySmclBasePath += File.separator;
+		String compiler = pySmclBasePath + "pysmcl" + File.separator + "emacs"
+				+ File.separator + "info.py";
+
+		Map<String, String> environ = new HashMap<String, String>();
+		environ.put("PYTHONPATH", pySmclBasePath);
+		String[] cmd = new String[] { python, compiler, resource.toOSString() };
+		ProcessExecutor exec = new ProcessExecutor(cmd, environ);
+
+		try {
+			exec.run();
+		} catch (IOException e) {
+			throw new PySMCLException("Failed to invoke PySMCL compiler" + "\n"
+					+ exec.getOutput());
+		}
+
+		if (0 != exec.getExitValue())
+			throw new PySMCLException("PySMCL returned with error code "
+					+ exec.getExitValue() + "\n" + exec.getOutput());
+
+		return exec.getOutput();
+	}
+
+}
+
+class CompilerMessageHandler {
+
+	private IMarkable markable;
+	private IFile file;
+
+	public CompilerMessageHandler(IFile file, IMarkable markable) {
+		this.file = file;
+		this.markable = markable;
+	}
+
+	private void addMarker(String msg, int start, int end, int severity,
+			String location) {
+		markable.addMarker(file, msg, start, end, severity, location);
+	}
+
+	public void error(String msg, int start, int end, String location) {
+		addMarker(msg, start, end, IMarker.SEVERITY_ERROR, location);
+	}
+
+	public void warning(String msg, int start, int end, String location) {
+		addMarker(msg, start, end, IMarker.SEVERITY_WARNING, location);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/builder/PySMCLNature.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,81 @@
+package eu.cace.pysmcl.builder;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.runtime.CoreException;
+
+public class PySMCLNature implements IProjectNature {
+
+	/**
+	 * ID of this project nature
+	 */
+	public static final String NATURE_ID = "eu.cace.pysmcl.pySMCLNature";
+
+	private IProject project;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.core.resources.IProjectNature#configure()
+	 */
+	public void configure() throws CoreException {
+		IProjectDescription desc = project.getDescription();
+		ICommand[] commands = desc.getBuildSpec();
+
+		for (int i = 0; i < commands.length; ++i) {
+			if (commands[i].getBuilderName().equals(PySMCLBuilder.BUILDER_ID)) {
+				return;
+			}
+		}
+
+		ICommand[] newCommands = new ICommand[commands.length + 1];
+		System.arraycopy(commands, 0, newCommands, 0, commands.length);
+		ICommand command = desc.newCommand();
+		command.setBuilderName(PySMCLBuilder.BUILDER_ID);
+		newCommands[newCommands.length - 1] = command;
+		desc.setBuildSpec(newCommands);
+		project.setDescription(desc, null);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.core.resources.IProjectNature#deconfigure()
+	 */
+	public void deconfigure() throws CoreException {
+		IProjectDescription description = getProject().getDescription();
+		ICommand[] commands = description.getBuildSpec();
+		for (int i = 0; i < commands.length; ++i) {
+			if (commands[i].getBuilderName().equals(PySMCLBuilder.BUILDER_ID)) {
+				ICommand[] newCommands = new ICommand[commands.length - 1];
+				System.arraycopy(commands, 0, newCommands, 0, i);
+				System.arraycopy(commands, i + 1, newCommands, i,
+						commands.length - i - 1);
+				description.setBuildSpec(newCommands);
+				project.setDescription(description, null);			
+				return;
+			}
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.core.resources.IProjectNature#getProject()
+	 */
+	public IProject getProject() {
+		return project;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject)
+	 */
+	public void setProject(IProject project) {
+		this.project = project;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/builder/ToggleNatureAction.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,99 @@
+package eu.cace.pysmcl.builder;
+
+import java.util.Iterator;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IObjectActionDelegate;
+import org.eclipse.ui.IWorkbenchPart;
+
+public class ToggleNatureAction implements IObjectActionDelegate {
+
+	private ISelection selection;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
+	 */
+	@SuppressWarnings("unchecked")
+	public void run(IAction action) {
+		if (selection instanceof IStructuredSelection) {
+			for (Iterator it = ((IStructuredSelection) selection).iterator(); it
+					.hasNext();) {
+				Object element = it.next();
+				IProject project = null;
+				if (element instanceof IProject) {
+					project = (IProject) element;
+				} else if (element instanceof IAdaptable) {
+					project = (IProject) ((IAdaptable) element)
+							.getAdapter(IProject.class);
+				}
+				if (project != null) {
+					toggleNature(project);
+				}
+			}
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action
+	 * .IAction, org.eclipse.jface.viewers.ISelection)
+	 */
+	public void selectionChanged(IAction action, ISelection selection) {
+		this.selection = selection;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.eclipse.ui.IObjectActionDelegate#setActivePart(org.eclipse.jface.
+	 * action.IAction, org.eclipse.ui.IWorkbenchPart)
+	 */
+	public void setActivePart(IAction action, IWorkbenchPart targetPart) {
+	}
+
+	/**
+	 * Toggles sample nature on a project
+	 * 
+	 * @param project
+	 *            to have sample nature added or removed
+	 */
+	private void toggleNature(IProject project) {
+		try {
+			IProjectDescription description = project.getDescription();
+			String[] natures = description.getNatureIds();
+
+			for (int i = 0; i < natures.length; ++i) {
+				if (PySMCLNature.NATURE_ID.equals(natures[i])) {
+					// Remove the nature
+					String[] newNatures = new String[natures.length - 1];
+					System.arraycopy(natures, 0, newNatures, 0, i);
+					System.arraycopy(natures, i + 1, newNatures, i,
+							natures.length - i - 1);
+					description.setNatureIds(newNatures);
+					project.setDescription(description, null);
+					return;
+				}
+			}
+
+			// Add the nature
+			String[] newNatures = new String[natures.length + 1];
+			System.arraycopy(natures, 0, newNatures, 0, natures.length);
+			newNatures[natures.length] = PySMCLNature.NATURE_ID;
+			description.setNatureIds(newNatures);
+			project.setDescription(description, null);
+		} catch (CoreException e) {
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/preferences/PreferenceConstants.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,13 @@
+package eu.cace.pysmcl.preferences;
+
+/**
+ * Constant definitions for plug-in preferences.
+ * 
+ */
+public class PreferenceConstants {
+	
+	public static final String P_PYTHON = "pathPython";
+	
+	public static final String P_PYSMCL = "pathPySMCL";
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/preferences/PreferenceInitializer.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,28 @@
+package eu.cace.pysmcl.preferences;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import eu.cace.pysmcl.Activator;
+
+/**
+ * Class used to initialize default preference values.
+ */
+public class PreferenceInitializer extends AbstractPreferenceInitializer {
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences()
+	 */
+	public void initializeDefaultPreferences() {
+		IPreferenceStore store = Activator.getDefault().getPreferenceStore();
+
+		// TODO: Search for python location.
+		store.setDefault(PreferenceConstants.P_PYTHON, "/usr/bin/python");
+		
+		// TODO: Search for PySMCL location?
+		store.setDefault(PreferenceConstants.P_PYSMCL, "");
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eclipse/src/eu/cace/pysmcl/preferences/PreferencePage.java	Mon Oct 26 10:34:45 2009 +0100
@@ -0,0 +1,50 @@
+package eu.cace.pysmcl.preferences;
+
+import org.eclipse.jface.preference.*;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.IWorkbench;
+
+import eu.cace.pysmcl.Activator;
+
+/**
+ * This class represents a preference page that is contributed to the
+ * Preferences dialog. By subclassing <samp>FieldEditorPreferencePage</samp>, we
+ * can use the field support built into JFace that allows us to create a page
+ * that is small and knows how to save, restore and apply itself.
+ * <p>
+ * This page is used to modify preferences only. They are stored in the
+ * preference store that belongs to the main plug-in class. That way,
+ * preferences can be accessed directly via the preference store.
+ */
+public class PreferencePage extends FieldEditorPreferencePage implements
+		IWorkbenchPreferencePage {
+
+	public PreferencePage() {
+		super(GRID);
+		setPreferenceStore(Activator.getDefault().getPreferenceStore());
+		setDescription("Preferences for the PySMCL compiler.");
+	}
+
+	/**
+	 * Creates the field editors. Field editors are abstractions of the common
+	 * GUI blocks needed to manipulate various types of preferences. Each field
+	 * editor knows how to save and restore itself.
+	 * 
+	 */
+	public void createFieldEditors() {
+		addField(new FileFieldEditor(PreferenceConstants.P_PYTHON,
+				"&Python VM Location:", getFieldEditorParent()));
+		addField(new DirectoryFieldEditor(PreferenceConstants.P_PYSMCL,
+				"&PySMCL Location:", getFieldEditorParent()));
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
+	 */ 
+	public void init(IWorkbench workbench) {
+	}
+
+}