You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by mc...@apache.org on 2011/10/17 12:31:51 UTC

svn commit: r1185095 [2/15] - in /felix/trunk/bundleplugin: ./ src/main/java/aQute/ src/main/java/aQute/bnd/ src/main/java/aQute/bnd/annotation/ src/main/java/aQute/bnd/annotation/component/ src/main/java/aQute/bnd/annotation/metatype/ src/main/java/aQ...

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Project.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Project.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Project.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Project.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,1832 @@
+package aQute.bnd.build;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.jar.Manifest;
+
+import aQute.bnd.help.Syntax;
+import aQute.bnd.maven.support.Pom;
+import aQute.bnd.maven.support.ProjectPom;
+import aQute.bnd.service.CommandPlugin;
+import aQute.bnd.service.DependencyContributor;
+import aQute.bnd.service.Deploy;
+import aQute.bnd.service.RepositoryPlugin;
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.bnd.service.Scripter;
+import aQute.bnd.service.action.Action;
+import aQute.bnd.service.action.NamedAction;
+import aQute.lib.io.IO;
+import aQute.lib.osgi.Builder;
+import aQute.lib.osgi.Constants;
+import aQute.lib.osgi.Instruction;
+import aQute.lib.osgi.Jar;
+import aQute.lib.osgi.Processor;
+import aQute.lib.osgi.Resource;
+import aQute.lib.osgi.eclipse.EclipseClasspath;
+import aQute.libg.generics.Create;
+import aQute.libg.header.OSGiHeader;
+import aQute.libg.sed.Sed;
+
+/**
+ * This class is NOT threadsafe
+ * 
+ * @author aqute
+ * 
+ */
+
+public class Project extends Processor {
+
+	final static String			DEFAULT_ACTIONS			= "build; label='Build', test; label='Test', run; label='Run', clean; label='Clean', release; label='Release', refreshAll; label=Refresh, deploy;label=Deploy";
+	public final static String	BNDFILE					= "bnd.bnd";
+	public final static String	BNDCNF					= "cnf";
+	final Workspace				workspace;
+	boolean						preparedPaths;
+	final Collection<Project>	dependson				= new LinkedHashSet<Project>();
+	final Collection<Container>	buildpath				= new LinkedHashSet<Container>();
+	final Collection<Container>	testpath				= new LinkedHashSet<Container>();
+	final Collection<Container>	runpath					= new LinkedHashSet<Container>();
+	final Collection<Container>	runbundles				= new LinkedHashSet<Container>();
+	File						runstorage;
+	final Collection<File>		sourcepath				= new LinkedHashSet<File>();
+	final Collection<File>		allsourcepath			= new LinkedHashSet<File>();
+	final Collection<Container>	bootclasspath			= new LinkedHashSet<Container>();
+	final Lock					lock					= new ReentrantLock(true);
+	volatile String				lockingReason;
+	volatile Thread				lockingThread;
+	File						output;
+	File						target;
+	boolean						inPrepare;
+	int							revision;
+	File						files[];
+	private long				buildtime;
+	static List<Project>		trail					= new ArrayList<Project>();
+	boolean						delayRunDependencies	= false;
+
+	public Project(Workspace workspace, File projectDir, File buildFile) throws Exception {
+		super(workspace);
+		this.workspace = workspace;
+		setFileMustExist(false);
+		setProperties(buildFile);
+		assert workspace != null;
+		// For backward compatibility reasons, we also read
+		readBuildProperties();
+	}
+
+	public Project(Workspace workspace, File buildDir) throws Exception {
+		this(workspace, buildDir, new File(buildDir, BNDFILE));
+	}
+
+	private void readBuildProperties() throws Exception {
+		try {
+			File f = getFile("build.properties");
+			if (f.isFile()) {
+				Properties p = loadProperties(f);
+				for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
+					String key = (String) e.nextElement();
+					String newkey = key;
+					if (key.indexOf('$') >= 0) {
+						newkey = getReplacer().process(key);
+					}
+					setProperty(newkey, p.getProperty(key));
+				}
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	public static Project getUnparented(File propertiesFile) throws Exception {
+		propertiesFile = propertiesFile.getAbsoluteFile();
+		Workspace workspace = new Workspace(propertiesFile.getParentFile());
+		Project project = new Project(workspace, propertiesFile.getParentFile());
+		project.setProperties(propertiesFile);
+		project.setFileMustExist(true);
+		return project;
+	}
+
+	public synchronized boolean isValid() {
+		return getBase().isDirectory() && getPropertiesFile().isFile();
+	}
+
+	/**
+	 * Return a new builder that is nicely setup for this project. Please close
+	 * this builder after use.
+	 * 
+	 * @param parent
+	 *            The project builder to use as parent, use this project if null
+	 * @return
+	 * @throws Exception
+	 */
+	public synchronized ProjectBuilder getBuilder(ProjectBuilder parent) throws Exception {
+
+		ProjectBuilder builder;
+
+		if (parent == null)
+			builder = new ProjectBuilder(this);
+		else
+			builder = new ProjectBuilder(parent);
+
+		builder.setBase(getBase());
+
+		return builder;
+	}
+
+	public synchronized int getChanged() {
+		return revision;
+	}
+
+	/*
+	 * Indicate a change in the external world that affects our build. This will
+	 * clear any cached results.
+	 */
+	public synchronized void setChanged() {
+		// if (refresh()) {
+		preparedPaths = false;
+		files = null;
+		revision++;
+		// }
+	}
+
+	public Workspace getWorkspace() {
+		return workspace;
+	}
+
+	public String toString() {
+		return getBase().getName();
+	}
+
+	/**
+	 * Set up all the paths
+	 */
+
+	public synchronized void prepare() throws Exception {
+		if (!isValid()) {
+			warning("Invalid project attempts to prepare: %s", this);
+			return;
+		}
+
+		if (inPrepare)
+			throw new CircularDependencyException(trail.toString() + "," + this);
+
+		trail.add(this);
+		try {
+			if (!preparedPaths) {
+				inPrepare = true;
+				try {
+					dependson.clear();
+					buildpath.clear();
+					sourcepath.clear();
+					allsourcepath.clear();
+					bootclasspath.clear();
+					testpath.clear();
+					runpath.clear();
+					runbundles.clear();
+
+					// We use a builder to construct all the properties for
+					// use.
+					setProperty("basedir", getBase().getAbsolutePath());
+
+					// If a bnd.bnd file exists, we read it.
+					// Otherwise, we just do the build properties.
+					if (!getPropertiesFile().isFile() && new File(getBase(), ".classpath").isFile()) {
+						// Get our Eclipse info, we might depend on other
+						// projects
+						// though ideally this should become empty and void
+						doEclipseClasspath();
+					}
+
+					// Calculate our source directory
+
+					File src = getSrc();
+					if (src.isDirectory()) {
+						sourcepath.add(src);
+						allsourcepath.add(src);
+					} else
+						sourcepath.add(getBase());
+
+					// Set default bin directory
+					output = getFile(getProperty("bin", "bin")).getAbsoluteFile();
+					if (!output.exists()) {
+						output.mkdirs();
+						getWorkspace().changedFile(output);
+					}
+					if (!output.isDirectory())
+						error("Can not find output directory: " + output);
+					else if (!buildpath.contains(output))
+						buildpath.add(new Container(this, output));
+
+					// Where we store all our generated stuff.
+					target = getFile(getProperty("target", "generated"));
+					if (!target.exists()) {
+						target.mkdirs();
+						getWorkspace().changedFile(target);
+					}
+					
+					// Where the launched OSGi framework stores stuff
+					String runStorageStr = getProperty(Constants.RUNSTORAGE);
+					runstorage = runStorageStr != null ? getFile(runStorageStr) : null;
+
+					// We might have some other projects we want build
+					// before we do anything, but these projects are not in
+					// our path. The -dependson allows you to build them before.
+
+					List<Project> dependencies = new ArrayList<Project>();
+					// dependencies.add( getWorkspace().getProject("cnf"));
+
+					String dp = getProperty(Constants.DEPENDSON);
+					Set<String> requiredProjectNames = parseHeader(dp).keySet();
+					List<DependencyContributor> dcs = getPlugins(DependencyContributor.class);
+					for (DependencyContributor dc : dcs)
+						dc.addDependencies(this, requiredProjectNames);
+
+					for (String p : requiredProjectNames) {
+						Project required = getWorkspace().getProject(p);
+						if (required == null)
+							error("No such project " + required + " on " + Constants.DEPENDSON);
+						else {
+							dependencies.add(required);
+						}
+
+					}
+
+					// We have two paths that consists of repo files, projects,
+					// or some other stuff. The doPath routine adds them to the
+					// path and extracts the projects so we can build them
+					// before.
+
+					doPath(buildpath, dependencies, parseBuildpath(), bootclasspath);
+					doPath(testpath, dependencies, parseTestpath(), bootclasspath);
+					if (!delayRunDependencies) {
+						doPath(runpath, dependencies, parseRunpath(), null);
+						doPath(runbundles, dependencies, parseRunbundles(), null);
+					}
+
+					// We now know all dependent projects. But we also depend
+					// on whatever those projects depend on. This creates an
+					// ordered list without any duplicates. This of course
+					// assumes
+					// that there is no circularity. However, this is checked
+					// by the inPrepare flag, will throw an exception if we
+					// are circular.
+
+					Set<Project> done = new HashSet<Project>();
+					done.add(this);
+					allsourcepath.addAll(sourcepath);
+
+					for (Project project : dependencies)
+						project.traverse(dependson, done);
+
+					for (Project project : dependson) {
+						allsourcepath.addAll(project.getSourcePath());
+					}
+					if (isOk())
+						preparedPaths = true;
+				} finally {
+					inPrepare = false;
+				}
+			}
+		} finally {
+			trail.remove(this);
+		}
+	}
+
+	public File getSrc() {
+		return new File(getBase(), getProperty("src", "src"));
+	}
+
+	private void traverse(Collection<Project> dependencies, Set<Project> visited) throws Exception {
+		if (visited.contains(this))
+			return;
+
+		visited.add(this);
+
+		for (Project project : getDependson())
+			project.traverse(dependencies, visited);
+
+		dependencies.add(this);
+	}
+
+	/**
+	 * Iterate over the entries and place the projects on the projects list and
+	 * all the files of the entries on the resultpath.
+	 * 
+	 * @param resultpath
+	 *            The list that gets all the files
+	 * @param projects
+	 *            The list that gets any projects that are entries
+	 * @param entries
+	 *            The input list of classpath entries
+	 */
+	private void doPath(Collection<Container> resultpath, Collection<Project> projects,
+			Collection<Container> entries, Collection<Container> bootclasspath) {
+		for (Container cpe : entries) {
+			if (cpe.getError() != null)
+				error(cpe.getError());
+			else {
+				if (cpe.getType() == Container.TYPE.PROJECT) {
+					projects.add(cpe.getProject());
+				}
+				if (bootclasspath != null && cpe.getBundleSymbolicName().startsWith("ee.")
+						|| cpe.getAttributes().containsKey("boot"))
+					bootclasspath.add(cpe);
+				else
+					resultpath.add(cpe);
+			}
+		}
+	}
+
+	/**
+	 * Parse the list of bundles that are a prerequisite to this project.
+	 * 
+	 * Bundles are listed in repo specific names. So we just let our repo
+	 * plugins iterate over the list of bundles and we get the highest version
+	 * from them.
+	 * 
+	 * @return
+	 */
+
+	private List<Container> parseBuildpath() throws Exception {
+		List<Container> bundles = getBundles(Strategy.LOWEST, getProperty(Constants.BUILDPATH), Constants.BUILDPATH);
+		appendPackages(Strategy.LOWEST, getProperty(Constants.BUILDPACKAGES), bundles, ResolverMode.build);
+		return bundles;
+	}
+
+	private List<Container> parseRunpath() throws Exception {
+		return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNPATH), Constants.RUNPATH);
+	}
+
+	private List<Container> parseRunbundles() throws Exception {
+		return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNBUNDLES), Constants.RUNBUNDLES);
+	}
+	
+	private List<Container> parseTestpath() throws Exception {
+		return getBundles(Strategy.HIGHEST, getProperty(Constants.TESTPATH), Constants.TESTPATH);
+	}
+	
+	/**
+	 * Analyze the header and return a list of files that should be on the
+	 * build, test or some other path. The list is assumed to be a list of bsns
+	 * with a version specification. The special case of version=project
+	 * indicates there is a project in the same workspace. The path to the
+	 * output directory is calculated. The default directory ${bin} can be
+	 * overridden with the output attribute.
+	 * 
+	 * @param strategy
+	 *            STRATEGY_LOWEST or STRATEGY_HIGHEST
+	 * @param spec
+	 *            The header
+	 * @return
+	 */
+	public List<Container> getBundles(Strategy strategyx, String spec, String source) throws Exception {
+		List<Container> result = new ArrayList<Container>();
+		Map<String, Map<String, String>> bundles = parseHeader(spec);
+
+		try {
+			for (Iterator<Map.Entry<String, Map<String, String>>> i = bundles.entrySet().iterator(); i
+					.hasNext();) {
+				Map.Entry<String, Map<String, String>> entry = i.next();
+				String bsn = entry.getKey();
+				Map<String, String> attrs = entry.getValue();
+
+				Container found = null;
+
+				// Check if we have to use the maven pom ...
+				if (bsn.equals("pom")) {
+					doMavenPom(strategyx, result, attrs.get("scope"));
+					continue;
+				}
+
+				String versionRange = attrs.get("version");
+
+				if (versionRange != null) {
+					if (versionRange.equals("latest") || versionRange.equals("snapshot")) {
+						found = getBundle(bsn, versionRange, strategyx, attrs);
+					}
+				}
+				if (found == null) {
+					if (versionRange != null
+							&& (versionRange.equals("project") || versionRange.equals("latest"))) {
+						Project project = getWorkspace().getProject(bsn);
+						if (project != null && project.exists()) {
+							File f = project.getOutput();
+							found = new Container(project, bsn, "project", Container.TYPE.PROJECT,
+									f, null, attrs);
+						} else {
+							error("Reference to project that does not exist in workspace\n"
+									+ "  Project       %s\n" + "  Specification %s", bsn, spec);
+							continue;
+						}
+					} else if (versionRange != null && versionRange.equals("file")) {
+						File f = getFile(bsn);
+						String error = null;
+						if (!f.exists())
+							error = "File does not exist: " + f.getAbsolutePath();
+						if (f.getName().endsWith(".lib")) {
+							found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f,
+									error, attrs);
+						} else {
+							found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f,
+									error, attrs);
+						}
+					} else {
+						found = getBundle(bsn, versionRange, strategyx, attrs);
+					}
+				}
+
+				if (found != null) {
+					List<Container> libs = found.getMembers();
+					for (Container cc : libs) {
+						if (result.contains(cc))
+							warning("Multiple bundles with the same final URL: " + cc);
+
+						result.add(cc);
+					}
+				} else {
+					// Oops, not a bundle in sight :-(
+					Container x = new Container(this, bsn, versionRange, Container.TYPE.ERROR,
+							null, bsn + ";version=" + versionRange + " not found", attrs);
+					result.add(x);
+					warning("Can not find URL for bsn " + bsn);
+				}
+			}
+		} catch (CircularDependencyException e) {
+			String message = e.getMessage();
+			if (source != null)
+				message = String.format("%s (from property: %s)", message, source);
+			error("Circular dependency detected from project %s: %s", e, getName(), message);
+		} catch (Exception e) {
+			error("Unexpected error while trying to get the bundles from " + spec, e);
+			e.printStackTrace();
+		}
+		return result;
+	}
+	
+	/**
+	 * Calculates the containers required to fulfil the {@code -buildpackages}
+	 * instruction, and appends them to the existing list of containers.
+	 * 
+	 * @param strategyx
+	 *            The package-version disambiguation strategy.
+	 * @param spec
+	 *            The value of the @{code -buildpackages} instruction.
+	 * @throws Exception
+	 */
+	public void appendPackages(Strategy strategyx, String spec, List<Container> resolvedBundles, ResolverMode mode) throws Exception {
+		Map<File, Container> pkgResolvedBundles = new HashMap<File, Container>();
+		
+		List<Entry<String, Map<String, String>>> queue = new LinkedList<Map.Entry<String,Map<String,String>>>();
+		queue.addAll(parseHeader(spec).entrySet());
+		
+		while (!queue.isEmpty()) {
+			Entry<String, Map<String, String>> entry = queue.remove(0);
+			
+			String pkgName = entry.getKey();
+			Map<String, String> attrs = entry.getValue();
+			
+			Container found = null;
+			
+			String versionRange = attrs.get(Constants.VERSION_ATTRIBUTE);
+			if ("latest".equals(versionRange) || "snapshot".equals(versionRange))
+				found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
+
+			if (found == null)
+				found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
+
+			if (found != null) {
+				if (resolvedBundles.contains(found)) {
+					// Don't add his bundle because it was already included using -buildpath
+				} else {
+					List<Container> libs = found.getMembers();
+					for (Container cc : libs) {
+						Container existing = pkgResolvedBundles.get(cc.file);
+						if (existing != null)
+							addToPackageList(existing, attrs.get("packages"));
+						else {
+							addToPackageList(cc, attrs.get("packages"));
+							pkgResolvedBundles.put(cc.file, cc);
+						}
+						
+						String importUses = cc.getAttributes().get("import-uses");
+						if (importUses != null)
+							queue.addAll(0, parseHeader(importUses).entrySet());
+					}
+				}
+			} else {
+				// Unable to resolve
+				Container x = new Container(this, "X", versionRange, Container.TYPE.ERROR, null, "package " + pkgName + ";version=" + versionRange + " not found", attrs);
+				resolvedBundles.add(x);
+				warning("Can not find URL for package " + pkgName);
+			}
+		}
+		
+		for (Container container : pkgResolvedBundles.values()) {
+			resolvedBundles.add(container);
+		}
+	}
+	
+	static void mergeNames(String names, Set<String> set) {
+		StringTokenizer tokenizer = new StringTokenizer(names, ",");
+		while (tokenizer.hasMoreTokens())
+			set.add(tokenizer.nextToken().trim());
+	}
+	
+	static String flatten(Set<String> names) {
+		StringBuilder builder = new StringBuilder();
+		boolean first = true;
+		for (String name : names) {
+			if (!first) builder.append(',');
+			builder.append(name);
+			first = false;
+		}
+		return builder.toString();
+	}
+
+	static void addToPackageList(Container container, String newPackageNames) {
+		Set<String> merged = new HashSet<String>();
+		
+		String packageListStr = container.attributes.get("packages");
+		if (packageListStr != null)
+			mergeNames(packageListStr, merged);
+		if (newPackageNames != null)
+			mergeNames(newPackageNames, merged);
+		
+		container.putAttribute("packages", flatten(merged));
+	}
+
+	/**
+	 * Find a container to fulfil a package requirement
+	 * 
+	 * @param packageName
+	 *            The package required
+	 * @param range
+	 *            The package version range required
+	 * @param strategyx
+	 *            The package-version disambiguation strategy
+	 * @param attrs
+	 *            Other attributes specified by the search.
+	 * @return
+	 * @throws Exception
+	 */
+	public Container getPackage(String packageName, String range, Strategy strategyx, Map<String, String> attrs, ResolverMode mode) throws Exception {
+		if ("snapshot".equals(range))
+			return new Container(this, "", range, Container.TYPE.ERROR, null, "snapshot not supported for package lookups", null);
+		
+		if (attrs == null)
+			attrs = new HashMap<String, String>(2);
+		attrs.put("package", packageName);
+		attrs.put("mode", mode.name());
+		
+		Strategy useStrategy = findStrategy(attrs, strategyx, range);
+		
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		for (RepositoryPlugin plugin : plugins) {
+			try {
+				File result = plugin.get(null, range, useStrategy, attrs);
+				if (result != null) {
+					if (result.getName().endsWith("lib"))
+						return new Container(this, result.getName(), range, Container.TYPE.LIBRARY, result, null, attrs);
+					else
+						return new Container(this, result.getName(), range, Container.TYPE.REPO, result, null, attrs);
+				}
+			} catch (Exception e) {
+				// Ignore... lots of repos will fail here
+			}
+		}
+		
+		return new Container(this, "X", range, Container.TYPE.ERROR, null, "package " + packageName + ";version=" + range + " Not found in " + plugins, null);
+	}
+
+	private Strategy findStrategy(Map<String, String> attrs, Strategy defaultStrategy, String versionRange) {
+		Strategy useStrategy = defaultStrategy;
+		String overrideStrategy = attrs.get("strategy");
+		if (overrideStrategy != null) {
+			if ("highest".equalsIgnoreCase(overrideStrategy))
+				useStrategy = Strategy.HIGHEST;
+			else if ("lowest".equalsIgnoreCase(overrideStrategy))
+				useStrategy = Strategy.LOWEST;
+			else if ("exact".equalsIgnoreCase(overrideStrategy))
+				useStrategy = Strategy.EXACT;
+		}
+		if ("latest".equals(versionRange))
+			useStrategy = Strategy.HIGHEST;
+		return useStrategy;
+	}
+
+	/**
+	 * The user selected pom in a path. This will place the pom as well as its
+	 * dependencies on the list
+	 * 
+	 * @param strategyx
+	 *            the strategy to use.
+	 * @param result
+	 *            The list of result containers
+	 * @param attrs
+	 *            The attributes
+	 * @throws Exception
+	 *             anything goes wrong
+	 */
+	public void doMavenPom(Strategy strategyx, List<Container> result, String action)
+			throws Exception {
+		File pomFile = getFile("pom.xml");
+		if (!pomFile.isFile())
+			error("Specified to use pom.xml but the project directory does not contain a pom.xml file");
+		else {
+			ProjectPom pom = getWorkspace().getMaven().createProjectModel(pomFile);
+			if (action == null)
+				action = "compile";
+			Pom.Scope act = Pom.Scope.valueOf(action);
+			Set<Pom> dependencies = pom.getDependencies(act);
+			for (Pom sub : dependencies) {
+				File artifact = sub.getArtifact();
+				Container container = new Container(artifact);
+				result.add(container);
+			}
+		}
+	}
+
+	public Collection<Project> getDependson() throws Exception {
+		prepare();
+		return dependson;
+	}
+
+	public Collection<Container> getBuildpath() throws Exception {
+		prepare();
+		return buildpath;
+	}
+
+	public Collection<Container> getTestpath() throws Exception {
+		prepare();
+		return testpath;
+	}
+
+	/**
+	 * Handle dependencies for paths that are calculated on demand.
+	 * 
+	 * @param testpath2
+	 * @param parseTestpath
+	 */
+	private void justInTime(Collection<Container> path, List<Container> entries) {
+		if (delayRunDependencies && path.isEmpty())
+			doPath(path, dependson, entries, null);
+	}
+
+	public Collection<Container> getRunpath() throws Exception {
+		prepare();
+		justInTime(runpath, parseRunpath());
+		return runpath;
+	}
+
+	public Collection<Container> getRunbundles() throws Exception {
+		prepare();
+		justInTime(runbundles, parseRunbundles());
+		return runbundles;
+	}
+	
+	public File getRunStorage() throws Exception {
+		prepare();
+		return runstorage;
+	}
+	
+	public boolean getRunBuilds() {
+		boolean result;
+		String runBuildsStr = getProperty(Constants.RUNBUILDS);
+		if (runBuildsStr == null)
+			result = !getPropertiesFile().getName().toLowerCase().endsWith(Constants.DEFAULT_BNDRUN_EXTENSION);
+		else
+			result = Boolean.parseBoolean(runBuildsStr);
+		return result;
+	}
+
+	public Collection<File> getSourcePath() throws Exception {
+		prepare();
+		return sourcepath;
+	}
+
+	public Collection<File> getAllsourcepath() throws Exception {
+		prepare();
+		return allsourcepath;
+	}
+
+	public Collection<Container> getBootclasspath() throws Exception {
+		prepare();
+		return bootclasspath;
+	}
+
+	public File getOutput() throws Exception {
+		prepare();
+		return output;
+	}
+
+	private void doEclipseClasspath() throws Exception {
+		EclipseClasspath eclipse = new EclipseClasspath(this, getWorkspace().getBase(), getBase());
+		eclipse.setRecurse(false);
+
+		// We get the file directories but in this case we need
+		// to tell ant that the project names
+		for (File dependent : eclipse.getDependents()) {
+			Project required = workspace.getProject(dependent.getName());
+			dependson.add(required);
+		}
+		for (File f : eclipse.getClasspath()) {
+			buildpath.add(new Container(f));
+		}
+		for (File f : eclipse.getBootclasspath()) {
+			bootclasspath.add(new Container(f));
+		}
+		sourcepath.addAll(eclipse.getSourcepath());
+		allsourcepath.addAll(eclipse.getAllSources());
+		output = eclipse.getOutput();
+	}
+
+	public String _p_dependson(String args[]) throws Exception {
+		return list(args, toFiles(getDependson()));
+	}
+
+	private Collection<?> toFiles(Collection<Project> projects) {
+		List<File> files = new ArrayList<File>();
+		for (Project p : projects) {
+			files.add(p.getBase());
+		}
+		return files;
+	}
+
+	public String _p_buildpath(String args[]) throws Exception {
+		return list(args, getBuildpath());
+	}
+
+	public String _p_testpath(String args[]) throws Exception {
+		return list(args, getRunpath());
+	}
+
+	public String _p_sourcepath(String args[]) throws Exception {
+		return list(args, getSourcePath());
+	}
+
+	public String _p_allsourcepath(String args[]) throws Exception {
+		return list(args, getAllsourcepath());
+	}
+
+	public String _p_bootclasspath(String args[]) throws Exception {
+		return list(args, getBootclasspath());
+	}
+
+	public String _p_output(String args[]) throws Exception {
+		if (args.length != 1)
+			throw new IllegalArgumentException("${output} should not have arguments");
+		return getOutput().getAbsolutePath();
+	}
+
+	private String list(String[] args, Collection<?> list) {
+		if (args.length > 3)
+			throw new IllegalArgumentException("${" + args[0]
+					+ "[;<separator>]} can only take a separator as argument, has "
+					+ Arrays.toString(args));
+
+		String separator = ",";
+
+		if (args.length == 2) {
+			separator = args[1];
+		}
+
+		return join(list, separator);
+	}
+
+	protected Object[] getMacroDomains() {
+		return new Object[] { workspace };
+	}
+
+	public File release(Jar jar) throws Exception {
+		String name = getProperty(Constants.RELEASEREPO);
+		return release(name, jar);
+	}
+
+	/**
+	 * Release
+	 * 
+	 * @param name
+	 *            The repository name
+	 * @param jar
+	 * @return
+	 * @throws Exception
+	 */
+	public File release(String name, Jar jar) throws Exception {
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		RepositoryPlugin rp = null;
+		for (RepositoryPlugin plugin : plugins) {
+			if (!plugin.canWrite()) {
+				continue;
+			}
+			if (name == null) {
+				rp = plugin;
+				break;
+			} else if (name.equals(plugin.getName())) {
+				rp = plugin;
+				break;
+			}
+		}
+
+		if (rp != null) {
+			try {
+				return rp.put(jar);
+			} catch (Exception e) {
+				error("Deploying " + jar.getName() + " on " + rp.getName(), e);
+			} finally {
+				jar.close();
+			}
+		}
+		return null;
+
+	}
+
+	public void release(boolean test) throws Exception {
+		String name = getProperty(Constants.RELEASEREPO);
+		release(name, test);
+	}
+
+	/**
+	 * Release
+	 * 
+	 * @param name
+	 *            The respository name
+	 * @param test
+	 *            Run testcases
+	 * @throws Exception
+	 */
+	public void release(String name, boolean test) throws Exception {
+		File[] jars = build(test);
+		// If build fails jars will be null
+		if (jars == null) {
+			return;
+		}
+		for (File jar : jars) {
+			Jar j = new Jar(jar);
+			release(name, j);
+			j.close();
+		}
+
+	}
+
+	/**
+	 * Get a bundle from one of the plugin repositories.
+	 * 
+	 * @param bsn
+	 *            The bundle symbolic name
+	 * @param range
+	 *            The version range
+	 * @param lowest
+	 *            set to LOWEST or HIGHEST
+	 * @return the file object that points to the bundle or null if not found
+	 * @throws Exception
+	 *             when something goes wrong
+	 */
+	public Container getBundle(String bsn, String range, Strategy strategyx,
+			Map<String, String> attrs) throws Exception {
+
+		if ("snapshot".equals(range)) {
+			return getBundleFromProject(bsn, attrs);
+		}
+
+		if ("latest".equals(range)) {
+			Container c = getBundleFromProject(bsn, attrs);
+			if (c != null)
+				return c;
+		}
+
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		;
+
+		Strategy useStrategy = strategyx;
+		if (attrs != null) {
+			String overrideStrategy = attrs.get("strategy");
+
+			if (overrideStrategy != null) {
+				if ("highest".equalsIgnoreCase(overrideStrategy))
+					useStrategy = Strategy.HIGHEST;
+				else if ("lowest".equalsIgnoreCase(overrideStrategy))
+					useStrategy = Strategy.LOWEST;
+				else if ("exact".equalsIgnoreCase(overrideStrategy))
+					useStrategy = Strategy.EXACT;
+			}
+		}
+
+		// If someone really wants the latest, lets give it to them.
+		// regardless of they asked for a lowest strategy
+		if (range != null && range.equals("latest"))
+			useStrategy = Strategy.HIGHEST;
+
+		// if ( bsn.indexOf('+')>0) {
+		// return getMavenContainer(bsn,range,useStrategy,attrs);
+		// }
+
+		// Maybe we want an exact match this time.
+		// In that case we limit the range to be exactly
+		// the version specified. We ignore it when a range
+		// is used instead of a version
+
+		for (RepositoryPlugin plugin : plugins) {
+			File result = plugin.get(bsn, range, useStrategy, attrs);
+			if (result != null) {
+				if (result.getName().endsWith("lib"))
+					return new Container(this, bsn, range, Container.TYPE.LIBRARY, result, null,
+							attrs);
+				else
+					return new Container(this, bsn, range, Container.TYPE.REPO, result, null, attrs);
+			}
+		}
+
+		return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version="
+				+ range + " Not found in " + plugins, null);
+	}
+
+	/**
+	 * Look for the bundle in the workspace. The premise is that the bsn must
+	 * start with the project name.
+	 * 
+	 * @param bsn
+	 *            The bsn
+	 * @param attrs
+	 *            Any attributes
+	 * @return
+	 * @throws Exception
+	 */
+	private Container getBundleFromProject(String bsn, Map<String, String> attrs) throws Exception {
+		String pname = bsn;
+		while (true) {
+			Project p = getWorkspace().getProject(pname);
+			if (p != null && p.isValid()) {
+				Container c = p.getDeliverable(bsn, attrs);
+				return c;
+			}
+
+			int n = pname.lastIndexOf('.');
+			if (n <= 0)
+				return null;
+			pname = pname.substring(0, n);
+		}
+	}
+
+	/**
+	 * Deploy the file (which must be a bundle) into the repository.
+	 * 
+	 * @param name
+	 *            The repository name
+	 * @param file
+	 *            bundle
+	 */
+	public void deploy(String name, File file) throws Exception {
+		List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+		;
+		RepositoryPlugin rp = null;
+		for (RepositoryPlugin plugin : plugins) {
+			if (!plugin.canWrite()) {
+				continue;
+			}
+			if (name == null) {
+				rp = plugin;
+				break;
+			} else if (name.equals(plugin.getName())) {
+				rp = plugin;
+				break;
+			}
+		}
+
+		if (rp != null) {
+			Jar jar = new Jar(file);
+			try {
+				rp.put(jar);
+				return;
+			} catch (Exception e) {
+				error("Deploying " + file + " on " + rp.getName(), e);
+			} finally {
+				jar.close();
+			}
+			return;
+		}
+		trace("No repo found " + file);
+		throw new IllegalArgumentException("No repository found for " + file);
+	}
+
+	/**
+	 * Deploy the file (which must be a bundle) into the repository.
+	 * 
+	 * @param file
+	 *            bundle
+	 */
+	public void deploy(File file) throws Exception {
+		String name = getProperty(Constants.DEPLOYREPO);
+		deploy(name, file);
+	}
+
+	/**
+	 * Deploy the current project to a repository
+	 * 
+	 * @throws Exception
+	 */
+	public void deploy() throws Exception {
+		Map<String, Map<String, String>> deploy = parseHeader(getProperty(DEPLOY));
+		if (deploy.isEmpty()) {
+			warning("Deploying but %s is not set to any repo", DEPLOY);
+			return;
+		}
+		File[] outputs = getBuildFiles();
+		for (File output : outputs) {
+			Jar jar = new Jar(output);
+			try {
+				for (Deploy d : getPlugins(Deploy.class)) {
+					trace("Deploying %s to: %s", jar, d);
+					try {
+						if (d.deploy(this, jar))
+							trace("deployed %s successfully to %s", output, d);
+					} catch (Exception e) {
+						error("Error while deploying %s, %s", this, e);
+						e.printStackTrace();
+					}
+				}
+			} finally {
+				jar.close();
+			}
+		}
+	}
+
+	/**
+	 * Macro access to the repository
+	 * 
+	 * ${repo;<bsn>[;<version>[;<low|high>]]}
+	 */
+
+	public String _repo(String args[]) throws Exception {
+		if (args.length < 2)
+			throw new IllegalArgumentException(
+					"Too few arguments for repo, syntax=: ${repo ';'<bsn> [ ; <version> [; ('HIGHEST'|'LOWEST')]}");
+
+		String bsns = args[1];
+		String version = null;
+		Strategy strategy = Strategy.HIGHEST;
+
+		if (args.length > 2) {
+			version = args[2];
+			if (args.length == 4) {
+				if (args[3].equalsIgnoreCase("HIGHEST"))
+					strategy = Strategy.HIGHEST;
+				else if (args[3].equalsIgnoreCase("LOWEST"))
+					strategy = Strategy.LOWEST;
+				else if (args[3].equalsIgnoreCase("EXACT"))
+					strategy = Strategy.EXACT;
+				else
+					error("${repo;<bsn>;<version>;<'highest'|'lowest'|'exact'>} macro requires a strategy of 'highest' or 'lowest', and is "
+							+ args[3]);
+			}
+		}
+
+		Collection<String> parts = split(bsns);
+		List<String> paths = new ArrayList<String>();
+
+		for (String bsn : parts) {
+			Container container = getBundle(bsn, version, strategy, null);
+			add(paths, container);
+		}
+		return join(paths);
+	}
+
+	private void add(List<String> paths, Container container) throws Exception {
+		if (container.getType() == Container.TYPE.LIBRARY) {
+			List<Container> members = container.getMembers();
+			for (Container sub : members) {
+				add(paths, sub);
+			}
+		} else {
+			if (container.getError() == null)
+				paths.add(container.getFile().getAbsolutePath());
+			else {
+				paths.add("<<${repo} = " + container.getBundleSymbolicName() + "-"
+						+ container.getVersion() + " : " + container.getError() + ">>");
+
+				if (isPedantic()) {
+					warning("Could not expand repo path request: %s ", container);
+				}
+			}
+
+		}
+	}
+
+	public File getTarget() throws Exception {
+		prepare();
+		return target;
+	}
+
+	/**
+	 * This is the external method that will pre-build any dependencies if it is
+	 * out of date.
+	 * 
+	 * @param underTest
+	 * @return
+	 * @throws Exception
+	 */
+	public File[] build(boolean underTest) throws Exception {
+		if (getProperty(NOBUNDLES) != null)
+			return null;
+
+		if (getProperty("-nope") != null) {
+			warning("Please replace -nope with %s", NOBUNDLES);
+			return null;
+		}
+
+
+		if (isStale()) {
+			trace("Building " + this);
+			files = buildLocal(underTest);
+		}
+
+		return files;
+	}
+	
+	/**
+	 * Return the files
+	 */
+	
+	public File[] getFiles() {
+		return files;
+	}
+	/**
+	 * Check if this project needs building
+	 */
+	public boolean isStale() throws Exception {
+		if ( files == null)
+			return true;
+		
+		long localTime = getBuildTime();
+		if ( lastModified()>localTime)
+			return true;
+		
+		for (Project dependency : getDependson()) {
+			if (dependency.isStale())
+				return true;
+	
+			if ( dependency.getBuildTime() > localTime)
+				return true;
+			
+			if ( dependency.lastModified() > localTime)
+				return true;
+			
+		}
+		return false;
+	}
+
+	/**
+	 * This method must only be called when it is sure that the project has been
+	 * build before in the same session.
+	 * 
+	 * It is a bit yucky, but ant creates different class spaces which makes it
+	 * hard to detect we already build it.
+	 * 
+	 * @return
+	 */
+
+	public File[] getBuildFiles() throws Exception {
+		return getBuildFiles(true);
+	}
+
+	public File[] getBuildFiles(boolean buildIfAbsent) throws Exception {
+		File f = new File(getTarget(), BUILDFILES);
+		if (f.isFile()) {
+			FileReader fin = new FileReader(f);
+			BufferedReader rdr = new BufferedReader(fin);
+			try {
+				List<File> files = newList();
+				for (String s = rdr.readLine(); s != null; s = rdr.readLine()) {
+					s = s.trim();
+					File ff = new File(s);
+					if (!ff.isFile()) {
+						error("buildfile lists file but the file does not exist %s", ff);
+					} else
+						files.add(ff);
+				}
+				return files.toArray(new File[files.size()]);
+			} finally {
+				fin.close();
+			}
+		}
+		if (buildIfAbsent)
+			return buildLocal(false);
+		else
+			return null;
+	}
+
+	/**
+	 * Build without doing any dependency checking. Make sure any dependent
+	 * projects are built first.
+	 * 
+	 * @param underTest
+	 * @return
+	 * @throws Exception
+	 */
+	public File[] buildLocal(boolean underTest) throws Exception {
+		if (getProperty(NOBUNDLES) != null)
+			return null;
+
+		long buildtime = System.currentTimeMillis();
+		File bfs = new File(getTarget(), BUILDFILES);
+		bfs.delete();
+
+		files = null;
+		ProjectBuilder builder = getBuilder(null);
+		if (underTest)
+			builder.setProperty(Constants.UNDERTEST, "true");
+		Jar jars[] = builder.builds();
+		File[] files = new File[jars.length];
+
+		for (int i = 0; i < jars.length; i++) {
+			Jar jar = jars[i];
+			files[i] = saveBuild(jar);
+		}
+		getInfo(builder);
+		builder.close();
+		if (isOk()) {
+			this.files = files;
+
+			// Write out the filenames in the buildfiles file
+			// so we can get them later evenin another process
+			FileWriter fw = new FileWriter(bfs);
+			try {
+				for (File f : files) {
+					fw.append(f.getAbsolutePath());
+					fw.append("\n");
+				}
+			} finally {
+				fw.close();
+			}
+			getWorkspace().changedFile(bfs);
+			this.buildtime = buildtime;
+			return files;
+		} else
+			return null;
+	}
+
+	public File saveBuild(Jar jar) throws Exception {
+		try {
+			String bsn = jar.getName();
+			File f = getOutputFile(bsn);
+			String msg = "";
+			if (!f.exists() || f.lastModified() < jar.lastModified()) {
+				reportNewer(f.lastModified(), jar);
+				f.delete();
+				if (!f.getParentFile().isDirectory())
+					f.getParentFile().mkdirs();
+				jar.write(f);
+
+				getWorkspace().changedFile(f);
+			} else {
+				msg = "(not modified since " + new Date(f.lastModified()) + ")";
+			}
+			trace(jar.getName() + " (" + f.getName() + ") " + jar.getResources().size() + " " + msg);
+			return f;
+		} finally {
+			jar.close();
+		}
+	}
+
+	private File getOutputFile(String bsn) throws Exception {
+		return new File(getTarget(), bsn + ".jar");
+	}
+
+	private void reportNewer(long lastModified, Jar jar) {
+		if (isTrue(getProperty(Constants.REPORTNEWER))) {
+			StringBuilder sb = new StringBuilder();
+			String del = "Newer than " + new Date(lastModified);
+			for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
+				if (entry.getValue().lastModified() > lastModified) {
+					sb.append(del);
+					del = ", \n     ";
+					sb.append(entry.getKey());
+				}
+			}
+			if (sb.length() > 0)
+				warning(sb.toString());
+		}
+	}
+
+	/**
+	 * Refresh if we are based on stale data. This also implies our workspace.
+	 */
+	public boolean refresh() {
+		boolean changed = false;
+		if (isCnf()) {
+			changed = workspace.refresh();
+		}
+		return super.refresh() || changed;
+	}
+
+	public boolean isCnf() {
+		return getBase().getName().equals(Workspace.CNFDIR);
+	}
+
+	public void propertiesChanged() {
+		super.propertiesChanged();
+		preparedPaths = false;
+		files = null;
+
+	}
+
+	public String getName() {
+		return getBase().getName();
+	}
+
+	public Map<String, Action> getActions() {
+		Map<String, Action> all = newMap();
+		Map<String, Action> actions = newMap();
+		fillActions(all);
+		getWorkspace().fillActions(all);
+
+		for (Map.Entry<String, Action> action : all.entrySet()) {
+			String key = getReplacer().process(action.getKey());
+			if (key != null && key.trim().length() != 0)
+				actions.put(key, action.getValue());
+		}
+		return actions;
+	}
+
+	public void fillActions(Map<String, Action> all) {
+		List<NamedAction> plugins = getPlugins(NamedAction.class);
+		for (NamedAction a : plugins)
+			all.put(a.getName(), a);
+
+		Map<String, Map<String, String>> actions = parseHeader(getProperty("-actions",
+				DEFAULT_ACTIONS));
+		for (Map.Entry<String, Map<String, String>> entry : actions.entrySet()) {
+			String key = Processor.removeDuplicateMarker(entry.getKey());
+			Action action;
+
+			if (entry.getValue().get("script") != null) {
+				// TODO check for the type
+				action = new ScriptAction(entry.getValue().get("type"), entry.getValue().get(
+						"script"));
+			} else {
+				action = new ReflectAction(key);
+			}
+			String label = entry.getValue().get("label");
+			all.put(label.toLowerCase(), action);
+		}
+	}
+
+	public void release() throws Exception {
+		release(false);
+	}
+
+	/**
+	 * Release.
+	 * 
+	 * @param name
+	 *            The repository name
+	 * @throws Exception
+	 */
+	public void release(String name) throws Exception {
+		release(name, false);
+	}
+
+	public void clean() throws Exception {
+		File target = getTarget();
+		if (target.isDirectory() && target.getParentFile() != null) {
+			IO.delete(target);
+		}
+		if (getOutput().isDirectory())
+			IO.delete(getOutput());
+		getOutput().mkdirs();
+	}
+
+	public File[] build() throws Exception {
+		return build(false);
+	}
+
+	public void run() throws Exception {
+		ProjectLauncher pl = getProjectLauncher();
+		pl.setTrace(isTrace());
+		pl.launch();
+	}
+
+	public void test() throws Exception {
+		clear();
+		ProjectTester tester = getProjectTester();
+		tester.setContinuous(isTrue( getProperty(Constants.TESTCONTINUOUS)));
+		tester.prepare();
+
+		if (!isOk()) {
+			return;
+		}
+		int errors = tester.test();
+		if (errors == 0) {
+			System.out.println("No Errors");
+		} else {
+			if (errors > 0) {
+				System.out.println(errors + " Error(s)");
+
+			} else
+				System.out.println("Error " + errors);
+		}
+	}
+
+	/**
+	 * This methods attempts to turn any jar into a valid jar. If this is a
+	 * bundle with manifest, a manifest is added based on defaults. If it is a
+	 * bundle, but not r4, we try to add the r4 headers.
+	 * 
+	 * @param descriptor
+	 * @param in
+	 * @return
+	 * @throws Exception
+	 */
+	public Jar getValidJar(File f) throws Exception {
+		Jar jar = new Jar(f);
+		return getValidJar(jar, f.getAbsolutePath());
+	}
+
+	public Jar getValidJar(URL url) throws Exception {
+		InputStream in = url.openStream();
+		try {
+			Jar jar = new Jar(url.getFile().replace('/', '.'), in, System.currentTimeMillis());
+			return getValidJar(jar, url.toString());
+		} finally {
+			in.close();
+		}
+	}
+
+	public Jar getValidJar(Jar jar, String id) throws Exception {
+		Manifest manifest = jar.getManifest();
+		if (manifest == null) {
+			trace("Wrapping with all defaults");
+			Builder b = new Builder(this);
+			b.addClasspath(jar);
+			b.setProperty("Bnd-Message", "Wrapped from " + id + "because lacked manifest");
+			b.setProperty(Constants.EXPORT_PACKAGE, "*");
+			b.setProperty(Constants.IMPORT_PACKAGE, "*;resolution:=optional");
+			jar = b.build();
+		} else if (manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION) == null) {
+			trace("Not a release 4 bundle, wrapping with manifest as source");
+			Builder b = new Builder(this);
+			b.addClasspath(jar);
+			b.setProperty(Constants.PRIVATE_PACKAGE, "*");
+			b.mergeManifest(manifest);
+			String imprts = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
+			if (imprts == null)
+				imprts = "";
+			else
+				imprts += ",";
+			imprts += "*;resolution=optional";
+
+			b.setProperty(Constants.IMPORT_PACKAGE, imprts);
+			b.setProperty("Bnd-Message", "Wrapped from " + id + "because had incomplete manifest");
+			jar = b.build();
+		}
+		return jar;
+	}
+
+	public String _project(String args[]) {
+		return getBase().getAbsolutePath();
+	}
+
+	public void bump(String mask) throws IOException {
+		Sed sed = new Sed(getReplacer(), getPropertiesFile());
+		sed.replace("(Bundle-Version\\s*(:|=)\\s*)(([0-9]+(\\.[0-9]+(\\.[0-9]+)?)?))",
+				"$1${version;" + mask + ";$3}");
+		sed.doIt();
+		refresh();
+	}
+
+	public void bump() throws IOException {
+		bump(getProperty(BUMPPOLICY, "=+0"));
+	}
+
+	public void action(String command) throws Throwable {
+		Map<String, Action> actions = getActions();
+
+		Action a = actions.get(command);
+		if (a == null)
+			a = new ReflectAction(command);
+
+		before(this, command);
+		try {
+			a.execute(this, command);
+		} catch (Throwable t) {
+			after(this, command, t);
+			throw t;
+		}
+	}
+
+	/**
+	 * Run all before command plugins
+	 * 
+	 */
+	void before(Project p, String a) {
+		List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
+		for (CommandPlugin testPlugin : testPlugins) {
+			testPlugin.before(this, a);
+		}
+	}
+
+	/**
+	 * Run all after command plugins
+	 */
+	void after(Project p, String a, Throwable t) {
+		List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
+		for (int i = testPlugins.size() - 1; i >= 0; i--) {
+			testPlugins.get(i).after(this, a, t);
+		}
+	}
+
+	public String _findfile(String args[]) {
+		File f = getFile(args[1]);
+		List<String> files = new ArrayList<String>();
+		tree(files, f, "", Instruction.getPattern(args[2]));
+		return join(files);
+	}
+
+	void tree(List<String> list, File current, String path, Instruction instr) {
+		if (path.length() > 0)
+			path = path + "/";
+
+		String subs[] = current.list();
+		if (subs != null) {
+			for (String sub : subs) {
+				File f = new File(current, sub);
+				if (f.isFile()) {
+					if (instr.matches(sub) && !instr.isNegated())
+						list.add(path + sub);
+				} else
+					tree(list, f, path + sub, instr);
+			}
+		}
+	}
+
+	public void refreshAll() {
+		workspace.refresh();
+		refresh();
+	}
+
+	@SuppressWarnings("unchecked") public void script(String type, String script) throws Exception {
+		// TODO check tyiping
+		List<Scripter> scripters = getPlugins(Scripter.class);
+		if (scripters.isEmpty()) {
+			error("Can not execute script because there are no scripters registered: %s", script);
+			return;
+		}
+		@SuppressWarnings("rawtypes")
+		Map x = (Map) getProperties();
+		scripters.get(0).eval((Map<String, Object>) x, new StringReader(script));
+	}
+
+	public String _repos(String args[]) throws Exception {
+		List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
+		List<String> names = new ArrayList<String>();
+		for (RepositoryPlugin rp : repos)
+			names.add(rp.getName());
+		return join(names, ", ");
+	}
+
+	public String _help(String args[]) throws Exception {
+		if (args.length == 1)
+			return "Specify the option or header you want information for";
+
+		Syntax syntax = Syntax.HELP.get(args[1]);
+		if (syntax == null)
+			return "No help for " + args[1];
+
+		String what = null;
+		if (args.length > 2)
+			what = args[2];
+
+		if (what == null || what.equals("lead"))
+			return syntax.getLead();
+		if (what == null || what.equals("example"))
+			return syntax.getExample();
+		if (what == null || what.equals("pattern"))
+			return syntax.getPattern();
+		if (what == null || what.equals("values"))
+			return syntax.getValues();
+
+		return "Invalid type specified for help: lead, example, pattern, values";
+	}
+
+	/**
+	 * Returns containers for the deliverables of this project. The deliverables
+	 * is the project builder for this project if no -sub is specified.
+	 * Otherwise it contains all the sub bnd files.
+	 * 
+	 * @return A collection of containers
+	 * 
+	 * @throws Exception
+	 */
+	public Collection<Container> getDeliverables() throws Exception {
+		List<Container> result = new ArrayList<Container>();
+		Collection<? extends Builder> builders = getSubBuilders();
+
+		for (Builder builder : builders) {
+			Container c = new Container(this, builder.getBsn(), builder.getVersion(),
+					Container.TYPE.PROJECT, getOutputFile(builder.getBsn()), null, null);
+			result.add(c);
+		}
+		return result;
+
+	}
+
+	/**
+	 * Return the builder associated with the give bnd file or null. The bnd.bnd
+	 * file can contain -sub option. This option allows specifying files in the
+	 * same directory that should drive the generation of multiple deliverables.
+	 * This method figures out if the bndFile is actually one of the bnd files
+	 * of a deliverable.
+	 * 
+	 * @param bndFile
+	 *            A file pointing to a bnd file.
+	 * @return null or the builder for a sub file.
+	 * @throws Exception
+	 */
+	public Builder getSubBuilder(File bndFile) throws Exception {
+		bndFile = bndFile.getCanonicalFile();
+
+		// Verify that we are inside the project.
+		File base = getBase().getCanonicalFile();
+		if (!bndFile.getAbsolutePath().startsWith(base.getAbsolutePath()))
+			return null;
+
+		Collection<? extends Builder> builders = getSubBuilders();
+		for (Builder sub : builders) {
+			File propertiesFile = sub.getPropertiesFile();
+			if (propertiesFile != null) {
+				if (propertiesFile.getCanonicalFile().equals(bndFile)) {
+					// Found it!
+					return sub;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Answer the container associated with a given bsn.
+	 * 
+	 * @param bndFile
+	 *            A file pointing to a bnd file.
+	 * @return null or the builder for a sub file.
+	 * @throws Exception
+	 */
+	public Container getDeliverable(String bsn, Map<String, String> attrs) throws Exception {
+		Collection<? extends Builder> builders = getSubBuilders();
+		for (Builder sub : builders) {
+			if (sub.getBsn().equals(bsn))
+				return new Container(this, getOutputFile(bsn));
+		}
+		return null;
+	}
+
+	/**
+	 * Get a list of the sub builders. A bnd.bnd file can contain the -sub
+	 * option. This will generate multiple deliverables. This method returns the
+	 * builders for each sub file. If no -sub option is present, the list will
+	 * contain a builder for the bnd.bnd file.
+	 * 
+	 * @return A list of builders.
+	 * @throws Exception
+	 */
+	public Collection<? extends Builder> getSubBuilders() throws Exception {
+		return getBuilder(null).getSubBuilders();
+	}
+
+	/**
+	 * Calculate the classpath. We include our own runtime.jar which includes
+	 * the test framework and we include the first of the test frameworks
+	 * specified.
+	 * 
+	 * @throws Exception
+	 */
+	Collection<File> toFile(Collection<Container> containers) throws Exception {
+		ArrayList<File> files = new ArrayList<File>();
+		for (Container container : containers) {
+			container.contributeFiles(files, this);
+		}
+		return files;
+	}
+
+	public Collection<String> getRunVM() {
+		Map<String, Map<String, String>> hdr = parseHeader(getProperty(RUNVM));
+		return hdr.keySet();
+	}
+
+	public Map<String, String> getRunProperties() {
+		return OSGiHeader.parseProperties(getProperty(RUNPROPERTIES));
+	}
+
+	/**
+	 * Get a launcher.
+	 * 
+	 * @return
+	 * @throws Exception
+	 */
+	public ProjectLauncher getProjectLauncher() throws Exception {
+		return getHandler(ProjectLauncher.class, getRunpath(), LAUNCHER_PLUGIN,
+				"biz.aQute.launcher");
+	}
+
+	public ProjectTester getProjectTester() throws Exception {
+		return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN, "biz.aQute.junit");
+	}
+
+	private <T> T getHandler(Class<T> target, Collection<Container> containers, String header,
+			String defaultHandler) throws Exception {
+		Class<? extends T> handlerClass = target;
+
+		// Make sure we find at least one handler, but hope to find an earlier
+		// one
+		List<Container> withDefault = Create.list();
+		withDefault.addAll(containers);
+		withDefault.addAll(getBundles(Strategy.HIGHEST, defaultHandler, null));
+
+		for (Container c : withDefault) {
+			Manifest manifest = c.getManifest();
+
+			if (manifest != null) {
+				String launcher = manifest.getMainAttributes().getValue(header);
+				if (launcher != null) {
+					Class<?> clz = getClass(launcher, c.getFile());
+					if (clz != null) {
+						if (!target.isAssignableFrom(clz)) {
+							error("Found a %s class in %s but it is not compatible with: %s", clz,
+									c, target);
+						} else {
+							handlerClass = clz.asSubclass(target);
+							Constructor<? extends T> constructor = handlerClass
+									.getConstructor(Project.class);
+							return constructor.newInstance(this);
+						}
+					}
+				}
+			}
+		}
+		throw new IllegalArgumentException("Default handler for " + header + " not found in "
+				+ defaultHandler);
+	}
+
+	public synchronized boolean lock(String reason) throws InterruptedException {
+		if (!lock.tryLock(5, TimeUnit.SECONDS)) {
+			error("Could not acquire lock for %s, was locked by %s for %s", reason, lockingThread,
+					lockingReason);
+			System.out.printf("Could not acquire lock for %s, was locked by %s for %s\n", reason,
+					lockingThread, lockingReason);
+			System.out.flush();
+			return false;
+		}
+		this.lockingReason = reason;
+		this.lockingThread = Thread.currentThread();
+		return true;
+	}
+
+	public void unlock() {
+		lockingReason = null;
+		lock.unlock();
+	}
+
+	public long getBuildTime() throws Exception {
+		if (buildtime == 0) {
+
+			files = getBuildFiles();
+			if (files != null && files.length >= 1)
+				buildtime = files[0].lastModified();
+		}
+		return buildtime;
+	}
+
+	/**
+	 * Make this project delay the calculation of the run dependencies.
+	 * 
+	 * The run dependencies calculation can be done in prepare or until the
+	 * dependencies are actually needed.
+	 */
+	public void setDelayRunDependencies(boolean x) {
+		delayRunDependencies = x;
+	}
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Project.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Project.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,73 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+
+import aQute.lib.osgi.*;
+
+public class ProjectBuilder extends Builder {
+    Project project;
+    boolean initialized;
+
+    public ProjectBuilder(Project project) {
+        super(project);
+        this.project = project;
+    }
+
+    public ProjectBuilder(ProjectBuilder builder) {
+        super(builder);
+        this.project = builder.project;
+    }
+
+    @Override
+    public long lastModified() {
+        return Math.max(project.lastModified(), super.lastModified());
+    }
+
+    /**
+     * We put our project and our workspace on the macro path.
+     */
+    protected Object[] getMacroDomains() {
+        return new Object[] { project, project.getWorkspace() };
+    }
+
+    public Builder getSubBuilder() throws Exception {
+        return project.getBuilder(this);
+    }
+
+    public Project getProject() {
+        return project;
+    }
+
+    public void init() {
+        try {
+            if (!initialized) {
+                initialized = true;
+                for (Container file : project.getBuildpath()) {
+                    addClasspath(file.getFile());
+                }
+
+                for (Container file : project.getBootclasspath()) {
+                    addClasspath(file.getFile());
+                }
+
+                for (File file : project.getAllsourcepath()) {
+                    addSourcepath(file);
+                }
+
+            }
+        } catch (Exception e) {
+            error("init project builder fails", e);
+        }
+    }
+
+    public List<Jar> getClasspath() {
+        init();
+        return super.getClasspath();
+    }
+
+    @Override
+    protected void changedFile(File f) {
+        project.getWorkspace().changedFile(f);
+    }
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectBuilder.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,327 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.RepositoryPlugin.Strategy;
+import aQute.lib.osgi.*;
+import aQute.libg.command.*;
+import aQute.libg.generics.*;
+
+/**
+ * A Project Launcher is a base class to be extended by launchers. Launchers are
+ * JARs that launch a framework and install a number of bundles and then run the
+ * framework. A launcher jar must specify a Launcher-Class manifest header. This
+ * class is instantiated and cast to a LauncherPlugin. This plug in is then
+ * asked to provide a ProjectLauncher. This project launcher is then used by the
+ * project to run the code. Launchers must extend this class.
+ * 
+ */
+public abstract class ProjectLauncher {
+	private final Project						project;
+	private long								timeout				= 0;
+	private final Collection<String>			classpath			= new ArrayList<String>();
+	private List<String>						runbundles			= Create.list();
+	private final List<String>					runvm				= new ArrayList<String>();
+	private Map<String, String>					runproperties;
+	private Command								java;
+	private Map<String, Map<String, String>>	runsystempackages;
+	private final List<String>					activators			= Create.list();
+	private File								storageDir;
+
+	private boolean								trace;
+	private boolean								keep;
+	private int									framework;
+
+	public final static int						SERVICES			= 10111;
+	public final static int						NONE				= 20123;
+
+	// MUST BE ALIGNED WITH LAUNCHER
+	public final static int						OK					= 0;
+	public final static int						WARNING				= -1;
+	public final static int						ERROR				= -2;
+	public final static int						TIMEDOUT			= -3;
+	public final static int						UPDATE_NEEDED		= -4;
+	public final static int						CANCELED			= -5;
+	public final static int						DUPLICATE_BUNDLE	= -6;
+	public final static int						RESOLVE_ERROR		= -7;
+	public final static int						ACTIVATOR_ERROR		= -8;
+	public final static int						CUSTOM_LAUNCHER		= -128;
+
+	public final static String					EMBEDDED_ACTIVATOR	= "Embedded-Activator";
+
+	public ProjectLauncher(Project project) throws Exception {
+		this.project = project;
+
+		updateFromProject();
+	}
+
+	/**
+	 * Collect all the aspect from the project and set the local fields from
+	 * them. Should be called
+	 * 
+	 * @throws Exception
+	 */
+	protected void updateFromProject() throws Exception {
+		// pkr: could not use this because this is killing the runtests.
+		// project.refresh();
+		runbundles.clear();
+		Collection<Container> run = project.getRunbundles();
+		
+		for (Container container : run) {
+			File file = container.getFile();
+			if (file != null && (file.isFile() || file.isDirectory()))
+				runbundles.add(file.getAbsolutePath());
+		}
+
+		if (project.getRunBuilds()) {
+			File[] builds = project.build();
+			if (builds != null)
+				for (File file : builds)
+					runbundles.add(file.getAbsolutePath());
+		}
+
+		Collection<Container> runpath = project.getRunpath();
+		runsystempackages = project.parseHeader(project.getProperty(Constants.RUNSYSTEMPACKAGES));
+		framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+		trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+		timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+		trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+		// For backward compatibility with bndtools launcher
+		List<Container> fws = project.getBundles(Strategy.HIGHEST, project.getProperty("-runfw"), "-runfw");
+		runpath.addAll(fws);
+
+		for (Container c : runpath) {
+			addClasspath(c);
+		}
+
+		runvm.addAll(project.getRunVM());
+		runproperties = project.getRunProperties();
+
+		storageDir = project.getRunStorage();
+		if (storageDir == null) {
+			storageDir = new File(project.getTarget(), "fw");
+		}
+	}
+
+	private int getRunframework(String property) {
+		if (Constants.RUNFRAMEWORK_NONE.equalsIgnoreCase(property))
+			return NONE;
+		else if (Constants.RUNFRAMEWORK_SERVICES.equalsIgnoreCase(property))
+			return SERVICES;
+
+		return SERVICES;
+	}
+
+	public void addClasspath(Container container) throws Exception {
+		if (container.getError() != null) {
+			project.error("Cannot launch because %s has reported %s", container.getProject(),
+					container.getError());
+		} else {
+			Collection<Container> members = container.getMembers();
+			for (Container m : members) {
+				String path = m.getFile().getAbsolutePath();
+				if (!classpath.contains(path)) {
+					classpath.add(path);
+
+					Manifest manifest = m.getManifest();
+					if (manifest != null) {
+						Map<String, Map<String, String>> exports = project.parseHeader(manifest
+								.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+						for (Map.Entry<String, Map<String, String>> e : exports.entrySet()) {
+							if (!runsystempackages.containsKey(e.getKey()))
+								runsystempackages.put(e.getKey(), e.getValue());
+						}
+
+						// Allow activators on the runpath. They are called
+						// after
+						// the framework is completely initialized wit the
+						// system
+						// context.
+						String activator = manifest.getMainAttributes()
+								.getValue(EMBEDDED_ACTIVATOR);
+						if (activator != null)
+							activators.add(activator);
+					}
+				}
+			}
+		}
+	}
+
+	public void addRunBundle(String f) {
+		runbundles.add(f);
+	}
+
+	public Collection<String> getRunBundles() {
+		return runbundles;
+	}
+
+	public void addRunVM(String arg) {
+		runvm.add(arg);
+	}
+
+	public Collection<String> getRunpath() {
+		return classpath;
+	}
+
+	public Collection<String> getClasspath() {
+		return classpath;
+	}
+
+	public Collection<String> getRunVM() {
+		return runvm;
+	}
+
+	public Collection<String> getArguments() {
+		return Collections.emptySet();
+	}
+
+	public Map<String, String> getRunProperties() {
+		return runproperties;
+	}
+	
+	public File getStorageDir() {
+		return storageDir;
+	}
+
+	public abstract String getMainTypeName();
+
+	public abstract void update() throws Exception;
+
+	public int launch() throws Exception {
+		prepare();
+		java = new Command();
+		java.add(project.getProperty("java", "java"));
+		java.add("-cp");
+		java.add(Processor.join(getClasspath(), File.pathSeparator));
+		java.addAll(getRunVM());
+		java.add(getMainTypeName());
+		java.addAll(getArguments());
+		if (timeout != 0)
+			java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+		int result = java.execute(System.in, System.out, System.err);
+		if (result == Integer.MIN_VALUE)
+			return TIMEDOUT;
+
+		reportResult(result);
+		return result;
+	}
+
+	protected void reportResult(int result) {
+		switch (result) {
+		case OK:
+			project.trace("Command terminated normal %s", java);
+			break;
+		case TIMEDOUT:
+			project.error("Launch timedout: %s", java);
+			break;
+
+		case ERROR:
+			project.error("Launch errored: %s", java);
+			break;
+
+		case WARNING:
+			project.warning("Launch had a warning %s", java);
+			break;
+		default:
+			project.warning("Unknown code %d from launcher: %s", result, java);
+			break;
+		}
+	}
+
+	public void setTimeout(long timeout, TimeUnit unit) {
+		this.timeout = unit.convert(timeout, TimeUnit.MILLISECONDS);
+	}
+
+	public long getTimeout() {
+		return this.timeout;
+	}
+
+	public void cancel() {
+		java.cancel();
+	}
+
+	public Map<String, Map<String, String>> getSystemPackages() {
+		return runsystempackages;
+	}
+
+	public void setKeep(boolean keep) {
+		this.keep = keep;
+	}
+
+	public boolean isKeep() {
+		return keep;
+	}
+
+	public void setTrace(boolean level) {
+		this.trace = level;
+	}
+
+	public boolean getTrace() {
+		return this.trace;
+	}
+
+	/**
+	 * Should be called when all the changes to the launchers are set. Will
+	 * calculate whatever is necessary for the launcher.
+	 * 
+	 * @throws Exception
+	 */
+	public abstract void prepare() throws Exception;
+
+	public Project getProject() {
+		return project;
+	}
+
+	public boolean addActivator(String e) {
+		return activators.add(e);
+	}
+
+	public Collection<String> getActivators() {
+		return Collections.unmodifiableCollection(activators);
+	}
+
+	/**
+	 * Either NONE or SERVICES to indicate how the remote end launches. NONE
+	 * means it should not use the classpath to run a framework. This likely
+	 * requires some dummy framework support. SERVICES means it should load the
+	 * framework from the claspath.
+	 * 
+	 * @return
+	 */
+	public int getRunFramework() {
+		return framework;
+	}
+
+	public void setRunFramework(int n) {
+		assert n == NONE || n == SERVICES;
+		this.framework = n;
+	}
+
+	/**
+	 * Add the specification for a set of bundles the runpath if it does not
+	 * already is included. This can be used by subclasses to ensure the proper
+	 * jars are on the classpath.
+	 * 
+	 * @param defaultSpec
+	 *            The default spec for default jars
+	 */
+	public void addDefault(String defaultSpec) throws Exception {
+		Collection<Container> deflts = project.getBundles(Strategy.HIGHEST, defaultSpec, null);
+		for (Container c : deflts)
+			addClasspath(c);
+	}
+
+	/**
+	 * Create a self executable.
+	 */
+
+	public Jar executable() throws Exception {
+		throw new UnsupportedOperationException();
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectLauncher.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,75 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.util.*;
+
+public abstract class ProjectTester {
+	final Project				project;
+	final Collection<Container>	testbundles;
+	final ProjectLauncher		launcher;
+	final List<String>			tests		= new ArrayList<String>();
+	File						reportDir;
+	boolean						continuous	= true;
+	
+	public ProjectTester(Project project) throws Exception {
+		this.project = project;
+		launcher = project.getProjectLauncher();
+		testbundles = project.getTestpath();
+		for (Container c : testbundles) {
+			launcher.addClasspath(c);
+		}
+		reportDir = new File(project.getTarget(), project.getProperty("test-reports",
+				"test-reports"));
+	}
+
+	public ProjectLauncher getProjectLauncher() {
+		return launcher;
+	}
+
+	public void addTest(String test) {
+		tests.add(test);
+	}
+
+	public Collection<String> getTests() {
+		return tests;
+	}
+
+	public Collection<File> getReports() {
+		List<File> reports = new ArrayList<File>();
+		for (File report : reportDir.listFiles()) {
+			if (report.isFile() )
+				reports.add(report);
+		}
+		return reports;
+	}
+
+	public File getReportDir() {
+		return reportDir;
+	}
+
+	public void setReportDir(File reportDir) {
+		this.reportDir = reportDir;
+	}
+
+	public Project getProject() {
+		return project;
+	}
+
+	public boolean getContinuous() {
+		return continuous;
+	}
+
+	public void setContinuous(boolean b) {
+		this.continuous = b;
+	}
+
+	public boolean prepare() throws Exception {
+		reportDir.mkdirs();
+		for ( File file : reportDir.listFiles() ) {
+			file.delete();
+		}
+		return true;
+	}
+
+	public abstract int test() throws Exception;
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ProjectTester.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,22 @@
+package aQute.bnd.build;
+
+import java.lang.reflect.*;
+
+import aQute.bnd.service.action.*;
+
+public class ReflectAction implements Action {
+    String  what;
+    
+    public ReflectAction(String what) {
+        this.what = what;
+    }
+    
+    public void execute(Project project, String action) throws Exception {
+        Method m = project.getClass().getMethod(what);
+        m.invoke(project);
+    }
+
+    public String toString() {
+        return "ra:" + what;
+    }
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ReflectAction.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,5 @@
+package aQute.bnd.build;
+
+public enum ResolverMode {
+	build, runtime
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ResolverMode.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,18 @@
+package aQute.bnd.build;
+
+import aQute.bnd.service.action.*;
+
+public class ScriptAction implements Action {
+    final String script;
+    final String type;
+    
+    public ScriptAction(String type, String script) {
+        this.script = script;
+        this.type = type;
+    }
+
+    public void execute(Project project, String action) throws Exception {
+        project.script(type, script);
+    }
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/ScriptAction.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,294 @@
+package aQute.bnd.build;
+
+import java.io.*;
+import java.lang.ref.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+import java.util.jar.*;
+
+import javax.naming.*;
+
+import aQute.bnd.maven.support.*;
+import aQute.bnd.service.*;
+import aQute.bnd.service.action.*;
+import aQute.lib.deployer.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+
+public class Workspace extends Processor {
+	public static final String					BUILDFILE	= "build.bnd";
+	public static final String					CNFDIR		= "cnf";
+	public static final String					BNDDIR		= "bnd";
+	public static final String					CACHEDIR	= "cache";
+
+	static Map<File, WeakReference<Workspace>>	cache		= newHashMap();
+	final Map<String, Project>					models		= newHashMap();
+	final Map<String, Action>					commands	= newMap();
+	final CachedFileRepo						cachedRepo;
+	final File									buildDir;
+	final Maven									maven		= new Maven(Processor.getExecutor());
+	private boolean	postpone;
+
+	/**
+	 * This static method finds the workspace and creates a project (or returns
+	 * an existing project)
+	 * 
+	 * @param projectDir
+	 * @return
+	 */
+	public static Project getProject(File projectDir) throws Exception {
+		projectDir = projectDir.getAbsoluteFile();
+		assert projectDir.isDirectory();
+
+		Workspace ws = getWorkspace(projectDir.getParentFile());
+		return ws.getProject(projectDir.getName());
+	}
+
+	public static Workspace getWorkspace(File parent) throws Exception {
+		File workspaceDir = parent.getAbsoluteFile();
+
+		// the cnf directory can actually be a
+		// file that redirects
+		while (workspaceDir.isDirectory()) {
+			File test = new File(workspaceDir, CNFDIR);
+
+			if (!test.exists())
+				test = new File(workspaceDir, BNDDIR);
+
+			if (test.isDirectory())
+				break;
+
+			if (test.isFile()) {
+				String redirect = IO.collect(test).trim();
+				test = getFile(test.getParentFile(), redirect).getAbsoluteFile();
+				workspaceDir = test;
+			}
+			if (!test.exists())
+				throw new IllegalArgumentException("No Workspace found from: " + parent);
+		}
+
+		synchronized (cache) {
+			WeakReference<Workspace> wsr = cache.get(workspaceDir);
+			Workspace ws;
+			if (wsr == null || (ws = wsr.get()) == null) {
+				ws = new Workspace(workspaceDir);
+				cache.put(workspaceDir, new WeakReference<Workspace>(ws));
+			}
+			return ws;
+		}
+	}
+
+	public Workspace(File dir) throws Exception {
+		dir = dir.getAbsoluteFile();
+		dir.mkdirs();
+		assert dir.isDirectory();
+
+		File buildDir = new File(dir, BNDDIR).getAbsoluteFile();
+		if (!buildDir.isDirectory())
+			buildDir = new File(dir, CNFDIR).getAbsoluteFile();
+
+		this.buildDir = buildDir;
+
+		File buildFile = new File(buildDir, BUILDFILE).getAbsoluteFile();
+		if (!buildFile.isFile())
+			warning("No Build File in " + dir);
+
+		setProperties(buildFile, dir);
+		propertiesChanged();
+		
+		cachedRepo = new CachedFileRepo();
+	}
+
+	public Project getProject(String bsn) throws Exception {
+		synchronized (models) {
+			Project project = models.get(bsn);
+			if (project != null)
+				return project;
+
+			File projectDir = getFile(bsn);
+			project = new Project(this, projectDir);
+			if (!project.isValid())
+				return null;
+
+			models.put(bsn, project);
+			return project;
+		}
+	}
+
+	public boolean isPresent(String name) {
+		return models.containsKey(name);
+	}
+
+	public Collection<Project> getCurrentProjects() {
+		return models.values();
+	}
+
+	public boolean refresh() {
+		if (super.refresh()) {
+			for (Project project : getCurrentProjects()) {
+				project.propertiesChanged();
+			}
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	public void propertiesChanged() {
+		super.propertiesChanged();
+		File extDir = new File(this.buildDir, "ext");
+		File[] extensions = extDir.listFiles();
+		if (extensions != null) {
+			for (File extension : extensions) {
+				if (extension.getName().endsWith(".bnd")) {
+					try {
+						doIncludeFile(extension, true, getProperties());
+					} catch (Exception e) {
+						error("PropertiesChanged: " + e.getMessage());
+					}
+				}
+			}
+		}
+	}
+
+	public String _workspace(String args[]) {
+		return getBase().getAbsolutePath();
+	}
+
+	public void addCommand(String menu, Action action) {
+		commands.put(menu, action);
+	}
+
+	public void removeCommand(String menu) {
+		commands.remove(menu);
+	}
+
+	public void fillActions(Map<String, Action> all) {
+		all.putAll(commands);
+	}
+
+	public Collection<Project> getAllProjects() throws Exception {
+		List<Project> projects = new ArrayList<Project>();
+		for (File file : getBase().listFiles()) {
+			if (new File(file, Project.BNDFILE).isFile())
+				projects.add(getProject(file));
+		}
+		return projects;
+	}
+
+	/**
+	 * Inform any listeners that we changed a file (created/deleted/changed).
+	 * 
+	 * @param f
+	 *            The changed file
+	 */
+	public void changedFile(File f) {
+		List<BndListener> listeners = getPlugins(BndListener.class);
+		for (BndListener l : listeners)
+			try {
+				l.changed(f);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+	}
+
+	public void bracket(boolean begin) {
+		List<BndListener> listeners = getPlugins(BndListener.class);
+		for (BndListener l : listeners)
+			try {
+				if ( begin )
+					l.begin();
+				else
+					l.end();
+			} catch (Exception e) {
+				// who cares?
+			}
+	}
+
+
+	private void copy(InputStream in, OutputStream out) throws Exception {
+		byte data[] = new byte[10000];
+		int size = in.read(data);
+		while (size > 0) {
+			out.write(data, 0, size);
+			size = in.read(data);
+		}
+	}
+
+	class CachedFileRepo extends FileRepo {
+		final Lock	lock	= new ReentrantLock();
+		boolean		inited;
+
+		CachedFileRepo() {
+			super("cache", getFile(buildDir, CACHEDIR), false);
+		}
+
+		protected void init() throws Exception {
+			if (lock.tryLock(50, TimeUnit.SECONDS) == false)
+				throw new TimeLimitExceededException(
+						"Cached File Repo is locked and can't acquire it");
+			try {
+				if (!inited) {
+					inited = true;
+					root.mkdirs();
+					if (!root.isDirectory())
+						throw new IllegalArgumentException("Cannot create cache dir " + root);
+
+					InputStream in = getClass().getResourceAsStream(EMBEDDED_REPO);
+					if (in != null)
+						unzip(in, root);
+					else
+						System.out.println("!!!! WTF Couldn't find embedded-repo.jar in bundle ");
+				}
+			} finally {
+				lock.unlock();
+			}
+		}
+
+		void unzip(InputStream in, File dir) throws Exception {
+			try {
+				JarInputStream jin = new JarInputStream(in);
+				JarEntry jentry = jin.getNextJarEntry();
+				while (jentry != null) {
+					if (!jentry.isDirectory()) {
+						File dest = Processor.getFile(dir, jentry.getName());
+						if (!dest.isFile() || dest.lastModified() < jentry.getTime()
+								|| jentry.getTime() == 0) {
+							dest.getParentFile().mkdirs();
+							FileOutputStream out = new FileOutputStream(dest);
+							try {
+								copy(jin, out);
+							} finally {
+								out.close();
+							}
+						}
+					}
+					jentry = jin.getNextJarEntry();
+				}
+			} finally {
+				in.close();
+			}
+		}
+	}
+
+	public List<RepositoryPlugin> getRepositories() {
+		return getPlugins(RepositoryPlugin.class);
+	}
+
+	public static Workspace getWorkspace(String path) throws Exception {
+		File file = IO.getFile(new File(""), path);
+		return getWorkspace(file);
+	}
+
+	public Maven getMaven() {
+		return maven;
+	}
+
+	@Override
+	protected void setTypeSpecificPlugins( Set<Object> list) {
+		super.setTypeSpecificPlugins(list);
+		list.add(maven);
+		list.add(cachedRepo);
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/Workspace.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/packageinfo
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/packageinfo?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/packageinfo (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/build/packageinfo Mon Oct 17 10:31:43 2011
@@ -0,0 +1 @@
+version 1.43.1

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,25 @@
+package aQute.bnd.compatibility;
+
+import java.lang.reflect.*;
+
+/**
+ * Access modifier
+ */
+public enum Access {
+	PUBLIC, PROTECTED, PACKAGE, PRIVATE, UNKNOWN;
+
+	public static Access modifier(int mod) {
+		if (Modifier.isPublic(mod))
+			return PUBLIC;
+		if (Modifier.isProtected(mod))
+			return PROTECTED;
+		if (Modifier.isPrivate(mod))
+			return PRIVATE;
+
+		return PACKAGE;
+	}
+
+	public String toString() {
+		return super.toString().toLowerCase();
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Access.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,25 @@
+package aQute.bnd.compatibility;
+
+public class GenericParameter {
+	String name;
+	GenericType bounds[];
+	
+	public GenericParameter(String name, GenericType[] bounds) {
+		this.name = name;
+		this.bounds = bounds;
+		if (bounds == null || bounds.length == 0)
+			bounds = new GenericType[] { new GenericType( Object.class) };
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(name);
+		if ( bounds != null && bounds.length > 0) {
+			for ( GenericType gtype : bounds ) {
+				sb.append( ":");
+				sb.append(gtype);
+			}
+		}
+		return sb.toString();
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericParameter.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,37 @@
+package aQute.bnd.compatibility;
+
+
+public class GenericType {
+	public GenericType(Class<Object> class1) {
+		// TODO Auto-generated constructor stub
+	}
+
+	final static GenericType	EMPTY[]	= new GenericType[0];
+	Scope						reference;
+	GenericType[]				a;
+	GenericType[]				b;
+	int							array;
+	
+	Scope	scope;
+	
+		static public class GenericWildcard extends GenericType{
+
+		public GenericWildcard(Class<Object> class1) {
+			super(class1);
+			// TODO Auto-generated constructor stub
+		}
+		
+	}
+	
+	static public class GenericArray extends GenericType {
+
+		public GenericArray(Class<Object> class1) {
+			super(class1);
+			// TODO Auto-generated constructor stub
+		}
+		
+	}
+	
+	
+
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/GenericType.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Added: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java
URL: http://svn.apache.org/viewvc/felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java?rev=1185095&view=auto
==============================================================================
--- felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java (added)
+++ felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java Mon Oct 17 10:31:43 2011
@@ -0,0 +1,13 @@
+package aQute.bnd.compatibility;
+
+/**
+ * The kind of thing we scope
+ * 
+ */
+public enum Kind {
+	ROOT, CLASS, FIELD, CONSTRUCTOR, METHOD, UNKNOWN;
+
+	public String toString() {
+		return super.toString().toLowerCase();
+	}
+}

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: felix/trunk/bundleplugin/src/main/java/aQute/bnd/compatibility/Kind.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision