blob: d05ba0be92f8bc89a20045dd9c69924a0c3349e9 [file] [log] [blame]
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;
}
}