aQute.bnd.build.Project Maven / Gradle / Ivy
package aQute.bnd.build;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
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.SortedMap;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.service.repository.ContentNamespace;
import org.osgi.service.repository.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.build.Container.TYPE;
import aQute.bnd.exporter.executable.ExecutableJarExporter;
import aQute.bnd.exporter.runbundles.RunbundlesExporter;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.help.Syntax;
import aQute.bnd.maven.support.Pom;
import aQute.bnd.maven.support.ProjectPom;
import aQute.bnd.osgi.About;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Instruction;
import aQute.bnd.osgi.Instructions;
import aQute.bnd.osgi.Jar;
import aQute.bnd.osgi.JarResource;
import aQute.bnd.osgi.Macro;
import aQute.bnd.osgi.Packages;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.Resource;
import aQute.bnd.osgi.Verifier;
import aQute.bnd.osgi.eclipse.EclipseClasspath;
import aQute.bnd.osgi.resource.CapReqBuilder;
import aQute.bnd.osgi.resource.ResourceUtils;
import aQute.bnd.osgi.resource.ResourceUtils.IdentityCapability;
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.PutOptions;
import aQute.bnd.service.RepositoryPlugin.PutResult;
import aQute.bnd.service.Scripter;
import aQute.bnd.service.Strategy;
import aQute.bnd.service.action.Action;
import aQute.bnd.service.action.NamedAction;
import aQute.bnd.service.export.Exporter;
import aQute.bnd.service.release.ReleaseBracketingPlugin;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
import aQute.lib.collections.ExtList;
import aQute.lib.converter.Converter;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.command.Command;
import aQute.libg.generics.Create;
import aQute.libg.glob.Glob;
import aQute.libg.qtokens.QuotedTokenizer;
import aQute.libg.reporter.ReporterMessages;
import aQute.libg.sed.Replacer;
import aQute.libg.sed.Sed;
import aQute.libg.tuple.Pair;
/**
* This class is NOT threadsafe
*/
public class Project extends Processor {
private final static Logger logger = LoggerFactory.getLogger(Project.class);
static class RefreshData {
Parameters installRepositories;
}
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";
final static Path BNDPATH = Paths.get(BNDFILE);
public final static String BNDCNF = "cnf";
public final static String SHA_256 = "SHA-256";
final Workspace workspace;
private final AtomicBoolean preparedPaths = new AtomicBoolean();
private final Set dependenciesFull = new LinkedHashSet<>();
private final Set dependenciesBuild = new LinkedHashSet<>();
private final Set dependenciesTest = new LinkedHashSet<>();
private final Set dependents = new LinkedHashSet<>();
final Collection classpath = new LinkedHashSet<>();
final Collection buildpath = new LinkedHashSet<>();
final Collection testpath = new LinkedHashSet<>();
final Collection runpath = new LinkedHashSet<>();
final Collection runbundles = new LinkedHashSet<>();
final Collection runfw = new LinkedHashSet<>();
File runstorage;
final Map sourcepath = new LinkedHashMap<>();
final Collection allsourcepath = new LinkedHashSet<>();
final Collection bootclasspath = new LinkedHashSet<>();
final Map versionMap = new LinkedHashMap<>();
File output;
File target;
private final AtomicInteger revision = new AtomicInteger();
private File files[];
boolean delayRunDependencies = true;
final ProjectMessages msgs = ReporterMessages.base(this,
ProjectMessages.class);
private Properties ide;
final Packages exportedPackages = new Packages();
final Packages importedPackages = new Packages();
final Packages containedPackages = new Packages();
final PackageInfo packageInfo = new PackageInfo(this);
private Makefile makefile;
private volatile RefreshData data = new RefreshData();
public Map unreferencedClasspathEntries = new HashMap<>();
public Project(Workspace workspace, File unused, File buildFile) {
super(workspace);
this.workspace = workspace;
setFileMustExist(false);
if (buildFile != null)
setProperties(buildFile);
// For backward compatibility reasons, we also read
readBuildProperties();
}
public Project(Workspace workspace, File buildDir) {
this(workspace, buildDir, new File(buildDir, BNDFILE));
}
private void readBuildProperties() {
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 boolean isValid() {
if (getBase() == null || !getBase().isDirectory())
return false;
return getPropertiesFile() == null || 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
* @throws Exception
*/
public ProjectBuilder getBuilder(ProjectBuilder parent) throws Exception {
ProjectBuilder builder;
if (parent == null)
builder = new ProjectBuilder(this);
else
builder = new ProjectBuilder(parent);
builder.setBase(getBase());
builder.use(this);
return builder;
}
public int getChanged() {
return revision.get();
}
/*
* Indicate a change in the external world that affects our build. This will
* clear any cached results.
*/
public void setChanged() {
// if (refresh()) {
preparedPaths.set(false);
files = null;
revision.getAndIncrement();
// }
}
public Workspace getWorkspace() {
return workspace;
}
@Override
public String toString() {
return getName();
}
/**
* Set up all the paths
*/
public void prepare() throws Exception {
if (!isValid()) {
warning("Invalid project attempts to prepare: %s", this);
return;
}
synchronized (preparedPaths) {
if (preparedPaths.get()) {
// ensure output folders exist
getSrcOutput0();
getTarget0();
return;
}
if (!workspace.trail.add(this)) {
throw new CircularDependencyException(workspace.trail.toString() + "," + this);
}
try {
String basePath = IO.absolutePath(getBase());
dependenciesFull.clear();
dependenciesBuild.clear();
dependenciesTest.clear();
dependents.clear();
buildpath.clear();
testpath.clear();
sourcepath.clear();
allsourcepath.clear();
bootclasspath.clear();
// JIT
runpath.clear();
runbundles.clear();
runfw.clear();
// We use a builder to construct all the properties for
// use.
setProperty("basedir", basePath);
// 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 directories
Parameters srces = new Parameters(mergeProperties(Constants.DEFAULT_PROP_SRC_DIR), this);
if (srces.isEmpty())
srces.add(Constants.DEFAULT_PROP_SRC_DIR, new Attrs());
for (Entry e : srces.entrySet()) {
File dir = getFile(removeDuplicateMarker(e.getKey()));
if (!IO.absolutePath(dir)
.startsWith(basePath)) {
error("The source directory lies outside the project %s directory: %s", this, dir)
.header(Constants.DEFAULT_PROP_SRC_DIR)
.context(e.getKey());
continue;
}
if (!dir.exists()) {
try {
IO.mkdirs(dir);
} catch (Exception ex) {
exception(ex, "could not create src directory (in src property) %s", dir)
.header(Constants.DEFAULT_PROP_SRC_DIR)
.context(e.getKey());
continue;
}
if (!dir.exists()) {
error("could not create src directory (in src property) %s", dir)
.header(Constants.DEFAULT_PROP_SRC_DIR)
.context(e.getKey());
continue;
}
}
if (dir.isDirectory()) {
sourcepath.put(dir, new Attrs(e.getValue()));
allsourcepath.add(dir);
} else
error("the src path (src property) contains an entry that is not a directory %s", dir)
.header(Constants.DEFAULT_PROP_SRC_DIR)
.context(e.getKey());
}
// Set default bin directory
output = getSrcOutput0();
if (!output.isDirectory()) {
msgs.NoOutputDirectory_(output);
}
// Where we store all our generated stuff.
target = getTarget0();
// 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.
// The values are possibly negated globbing patterns.
Set requiredProjectNames = new LinkedHashSet<>(
getMergedParameters(Constants.DEPENDSON).keySet());
// Allow DependencyConstributors to modify requiredProjectNames
List dcs = getPlugins(DependencyContributor.class);
for (DependencyContributor dc : dcs)
dc.addDependencies(this, requiredProjectNames);
Instructions is = new Instructions(requiredProjectNames);
Collection projects = getWorkspace().getAllProjects();
projects.remove(this); // since -dependson could use a wildcard
Set unused = new HashSet<>();
Set buildDeps = new LinkedHashSet<>(is.select(projects, unused, false));
for (Instruction u : unused)
msgs.MissingDependson_(u.getInput());
// 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, buildDeps, parseBuildpath(), bootclasspath, false, BUILDPATH);
Set testDeps = new LinkedHashSet<>(buildDeps);
doPath(testpath, testDeps, parseTestpath(), bootclasspath, false, TESTPATH);
if (!delayRunDependencies) {
doPath(runfw, testDeps, parseRunFw(), null, false, RUNFW);
doPath(runpath, testDeps, parseRunpath(), null, false, RUNPATH);
doPath(runbundles, testDeps, parseRunbundles(), null, true, RUNBUNDLES);
}
// 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 visited = new HashSet<>();
visited.add(this);
for (Project project : testDeps) {
project.traverse(dependenciesFull, this, visited);
}
dependenciesBuild.addAll(dependenciesFull);
dependenciesBuild.retainAll(buildDeps);
dependenciesTest.addAll(dependenciesFull);
dependenciesTest.retainAll(testDeps);
for (Project project : dependenciesFull) {
allsourcepath.addAll(project.getSourcePath());
}
preparedPaths.set(true);
} finally {
workspace.trail.remove(this);
}
}
}
/*
*
*/
private File getSrcOutput0() throws IOException {
File output = getSrcOutput().getAbsoluteFile();
if (!output.exists()) {
IO.mkdirs(output);
getWorkspace().changedFile(output);
}
return output;
}
private File getTarget0() throws IOException {
File target = getTargetDir();
if (!target.exists()) {
IO.mkdirs(target);
getWorkspace().changedFile(target);
}
return target;
}
/**
* This method is deprecated because this can handle only one source dir.
* Use getSourcePath. For backward compatibility we will return the first
* entry on the source path.
*
* @return first entry on the {@link #getSourcePath()}
*/
@Deprecated
public File getSrc() throws Exception {
prepare();
if (sourcepath.isEmpty())
return getFile("src");
return sourcepath.keySet()
.iterator()
.next();
}
public File getSrcOutput() {
return getFile(getProperty(Constants.DEFAULT_PROP_BIN_DIR));
}
public File getTestSrc() {
return getFile(getProperty(Constants.DEFAULT_PROP_TESTSRC_DIR));
}
public File getTestOutput() {
return getFile(getProperty(Constants.DEFAULT_PROP_TESTBIN_DIR));
}
public File getTargetDir() {
return getFile(getProperty(Constants.DEFAULT_PROP_TARGET_DIR));
}
private void traverse(Set dependencies, Project dependent, Set visited) throws Exception {
if (visited.add(this)) {
for (Project project : getTestDependencies()) {
project.traverse(dependencies, this, visited);
}
dependencies.add(this);
}
dependents.add(dependent);
}
/**
* 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 resultpath, Collection projects, Collection entries,
Collection bootclasspath, boolean noproject, String name) {
for (Container cpe : entries) {
if (cpe.getError() != null)
error("%s", cpe.getError()).header(name)
.context(cpe.getBundleSymbolicName());
else {
if (cpe.getType() == Container.TYPE.PROJECT) {
projects.add(cpe.getProject());
if (noproject //
&& since(About._2_3) //
&& VERSION_ATTR_PROJECT.equals(cpe.getAttributes()
.get(VERSION_ATTRIBUTE))) {
//
// we're trying to put a project's output directory on
// -runbundles list
//
error(
"%s is specified with version=project on %s. This version uses the project's output directory, which is not allowed since it must be an actual JAR file for this list.",
cpe.getBundleSymbolicName(), name).header(name)
.context(cpe.getBundleSymbolicName());
}
}
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.
*/
private List parseBuildpath() throws Exception {
List bundles = getBundles(Strategy.LOWEST, mergeProperties(Constants.BUILDPATH),
Constants.BUILDPATH);
return bundles;
}
private List parseRunpath() throws Exception {
return getBundles(Strategy.HIGHEST, mergeProperties(Constants.RUNPATH), Constants.RUNPATH);
}
private List parseRunbundles() throws Exception {
return getBundles(Strategy.HIGHEST, mergeProperties(Constants.RUNBUNDLES), Constants.RUNBUNDLES);
}
private List parseRunFw() throws Exception {
return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNFW), Constants.RUNFW);
}
private List parseTestpath() throws Exception {
return getBundles(Strategy.HIGHEST, mergeProperties(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 strategyx STRATEGY_LOWEST or STRATEGY_HIGHEST
* @param spec The header
*/
public List getBundles(Strategy strategyx, String spec, String source) throws Exception {
List result = new ArrayList<>();
Parameters bundles = new Parameters(spec, this);
try {
for (Iterator> i = bundles.entrySet()
.iterator(); i.hasNext();) {
Entry entry = i.next();
String bsn = removeDuplicateMarker(entry.getKey());
Map attrs = entry.getValue();
Container found = null;
String versionRange = attrs.get("version");
boolean triedGetBundle = false;
if (bsn.indexOf('*') >= 0) {
return getBundlesWildcard(bsn, versionRange, strategyx, attrs);
}
if (versionRange != null) {
if (versionRange.equals(VERSION_ATTR_LATEST) || versionRange.equals(VERSION_ATTR_SNAPSHOT)) {
found = getBundle(bsn, versionRange, strategyx, attrs);
triedGetBundle = true;
}
}
if (found == null) {
//
// TODO This looks like a duplicate
// of what is done in getBundle??
//
if (versionRange != null
&& (versionRange.equals(VERSION_ATTR_PROJECT) || versionRange.equals(VERSION_ATTR_LATEST))) {
//
// Use the bin directory ...
//
Project project = getWorkspace().getProject(bsn);
if (project != null && project.exists()) {
File f = project.getOutput();
found = new Container(project, bsn, versionRange, Container.TYPE.PROJECT, f, null, attrs,
null);
} else {
msgs.NoSuchProject(bsn, spec)
.context(bsn)
.header(source);
continue;
}
} else if (versionRange != null && versionRange.equals("file")) {
File f = getFile(bsn);
String error = null;
if (!f.exists())
error = "File does not exist: " + IO.absolutePath(f);
if (f.getName()
.endsWith(".lib")) {
found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f, error, attrs, null);
} else {
found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f, error, attrs, null);
}
} else if (!triedGetBundle) {
found = getBundle(bsn, versionRange, strategyx, attrs);
}
}
if (found != null) {
List libs = found.getMembers();
for (Container cc : libs) {
if (result.contains(cc)) {
if (isPedantic())
warning("Multiple bundles with the same final URL: %s, dropped duplicate", cc);
} else {
if (cc.getError() != null) {
error("Cannot find %s", cc).context(bsn)
.header(source);
}
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, null);
result.add(x);
error("Can not find URL for bsn %s", bsn).context(bsn)
.header(source);
}
}
} catch (CircularDependencyException e) {
String message = e.getMessage();
if (source != null)
message = String.format("%s (from property: %s)", message, source);
msgs.CircularDependencyContext_Message_(getName(), message);
} catch (Exception e) {
msgs.Unexpected_Error_(spec, e);
}
return result;
}
/**
* Just calls a new method with a default parm.
*
* @throws Exception
*/
Collection getBundles(Strategy strategy, String spec) throws Exception {
return getBundles(strategy, spec, null);
}
/**
* Get all bundles matching a wildcard expression.
*
* @param bsnPattern A bsn wildcard, e.g. "osgi*" or just "*".
* @param range A range to narrow the versions of bundles found, or null to
* return any version.
* @param strategyx The version selection strategy, which may be 'HIGHEST'
* or 'LOWEST' only -- 'EXACT' is not permitted.
* @param attrs Additional search attributes.
* @throws Exception
*/
public List getBundlesWildcard(String bsnPattern, String range, Strategy strategyx,
Map attrs) throws Exception {
if (VERSION_ATTR_SNAPSHOT.equals(range) || VERSION_ATTR_PROJECT.equals(range))
return Collections.singletonList(new Container(this, bsnPattern, range, TYPE.ERROR, null,
"Cannot use snapshot or project version with wildcard matches", null, null));
if (strategyx == Strategy.EXACT)
return Collections.singletonList(new Container(this, bsnPattern, range, TYPE.ERROR, null,
"Cannot use exact version strategy with wildcard matches", null, null));
VersionRange versionRange;
if (range == null || VERSION_ATTR_LATEST.equals(range))
versionRange = new VersionRange("0");
else
versionRange = new VersionRange(range);
RepoFilter repoFilter = parseRepoFilter(attrs);
if (bsnPattern != null) {
bsnPattern = bsnPattern.trim();
if (bsnPattern.length() == 0 || bsnPattern.equals("*"))
bsnPattern = null;
}
SortedMap> providerMap = new TreeMap<>();
List plugins = workspace.getRepositories();
for (RepositoryPlugin plugin : plugins) {
if (repoFilter != null && !repoFilter.match(plugin))
continue;
List bsns = plugin.list(bsnPattern);
if (bsns != null)
for (String bsn : bsns) {
SortedSet versions = plugin.versions(bsn);
if (versions != null && !versions.isEmpty()) {
Pair currentProvider = providerMap.get(bsn);
Version candidate;
switch (strategyx) {
case HIGHEST :
candidate = versions.last();
if (currentProvider == null || candidate.compareTo(currentProvider.getFirst()) > 0) {
providerMap.put(bsn, new Pair<>(candidate, plugin));
}
break;
case LOWEST :
candidate = versions.first();
if (currentProvider == null || candidate.compareTo(currentProvider.getFirst()) < 0) {
providerMap.put(bsn, new Pair<>(candidate, plugin));
}
break;
default :
// we shouldn't have reached this point!
throw new IllegalStateException(
"Cannot use exact version strategy with wildcard matches");
}
}
}
}
List containers = new ArrayList<>(providerMap.size());
for (Entry> entry : providerMap.entrySet()) {
String bsn = entry.getKey();
Version version = entry.getValue()
.getFirst();
RepositoryPlugin repo = entry.getValue()
.getSecond();
DownloadBlocker downloadBlocker = new DownloadBlocker(this);
File bundle = repo.get(bsn, version, attrs, downloadBlocker);
if (bundle != null && !bundle.getName()
.endsWith(".lib")) {
containers
.add(new Container(this, bsn, range, Container.TYPE.REPO, bundle, null, attrs, downloadBlocker));
}
}
return containers;
}
static void mergeNames(String names, Set set) {
StringTokenizer tokenizer = new StringTokenizer(names, ",");
while (tokenizer.hasMoreTokens())
set.add(tokenizer.nextToken()
.trim());
}
static String flatten(Set 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 merged = new HashSet<>();
String packageListStr = container.getAttributes()
.get("packages");
if (packageListStr != null)
mergeNames(packageListStr, merged);
if (newPackageNames != null)
mergeNames(newPackageNames, merged);
container.putAttribute("packages", flatten(merged));
}
/**
* 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
* @throws Exception anything goes wrong
*/
public void doMavenPom(Strategy strategyx, List result, String action) throws Exception {
File pomFile = getFile("pom.xml");
if (!pomFile.isFile())
msgs.MissingPom();
else {
ProjectPom pom = getWorkspace().getMaven()
.createProjectModel(pomFile);
if (action == null)
action = "compile";
Pom.Scope act = Pom.Scope.valueOf(action);
Set dependencies = pom.getDependencies(act);
for (Pom sub : dependencies) {
File artifact = sub.getArtifact();
Container container = new Container(artifact, null);
result.add(container);
}
}
}
/**
* Return the full transitive dependencies of this project.
*
* @return A set of the full transitive dependencies of this project.
* @throws Exception
*/
public Collection getDependson() throws Exception {
prepare();
return dependenciesFull;
}
/**
* Return the direct build dependencies of this project.
*
* @return A set of the direct build dependencies of this project.
* @throws Exception
*/
public Set getBuildDependencies() throws Exception {
prepare();
return dependenciesBuild;
}
/**
* Return the direct test dependencies of this project.
*
* The result includes the direct build dependencies of this project as
* well, so the result is a super set of {@link #getBuildDependencies()}.
*
* @return A set of the test build dependencies of this project.
* @throws Exception
*/
public Set getTestDependencies() throws Exception {
prepare();
return dependenciesTest;
}
/**
* Return the full transitive dependents of this project.
*
* The result includes projects which have build and test dependencies on
* this project.
*
* Since the full transitive dependents of this project is updated during
* the computation of other project dependencies, until all projects are
* prepared, the dependents result may be partial.
*
* @return A set of the transitive set of projects which depend on this
* project.
* @throws Exception
*/
public Set getDependents() throws Exception {
prepare();
return dependents;
}
public Collection getBuildpath() throws Exception {
prepare();
return buildpath;
}
public Collection getTestpath() throws Exception {
prepare();
return testpath;
}
/**
* Handle dependencies for paths that are calculated on demand.
*
* @param testpath2
* @param parseTestpath
*/
private void justInTime(Collection path, List entries, boolean noproject, String name) {
if (delayRunDependencies && path.isEmpty())
doPath(path, dependenciesFull, entries, null, noproject, name);
}
public Collection getRunpath() throws Exception {
prepare();
justInTime(runpath, parseRunpath(), false, RUNPATH);
return runpath;
}
public Collection getRunbundles() throws Exception {
prepare();
justInTime(runbundles, parseRunbundles(), true, RUNBUNDLES);
return runbundles;
}
/**
* Return the run framework
*
* @throws Exception
*/
public Collection getRunFw() throws Exception {
prepare();
justInTime(runfw, parseRunFw(), false, RUNFW);
return runfw;
}
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 getSourcePath() throws Exception {
prepare();
return sourcepath.keySet();
}
public Collection getAllsourcepath() throws Exception {
prepare();
return allsourcepath;
}
public Collection 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());
dependenciesFull.add(required);
}
for (File f : eclipse.getClasspath()) {
buildpath.add(new Container(f, null));
}
for (File f : eclipse.getBootclasspath()) {
bootclasspath.add(new Container(f, null));
}
for (File f : eclipse.getSourcepath()) {
sourcepath.put(f, new Attrs());
}
allsourcepath.addAll(eclipse.getAllSources());
output = eclipse.getOutput();
}
public String _p_dependson(String args[]) throws Exception {
return list(args, toFiles(getDependson()));
}
private Collection> toFiles(Collection projects) {
List files = new ArrayList<>();
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 IO.absolutePath(getOutput());
}
private String list(String[] args, Collection> list) {
if (args.length > 3)
throw new IllegalArgumentException(
"${" + args[0] + "[;]} can only take a separator as argument, has " + Arrays.toString(args));
String separator = ",";
if (args.length == 2) {
separator = args[1];
}
return join(list, separator);
}
@Override
protected Object[] getMacroDomains() {
return new Object[] {
workspace
};
}
public File release(String jarName, InputStream jarStream) throws Exception {
return release(null, jarName, jarStream);
}
public URI releaseURI(String jarName, InputStream jarStream) throws Exception {
return releaseURI(null, jarName, jarStream);
}
/**
* Release
*
* @param name The repository name
* @param jarName
* @param jarStream
* @throws Exception
*/
public File release(String name, String jarName, InputStream jarStream) throws Exception {
URI uri = releaseURI(name, jarName, jarStream);
if (uri != null && uri.getScheme()
.equals("file")) {
return new File(uri);
}
return null;
}
public URI releaseURI(String name, String jarName, InputStream jarStream) throws Exception {
List releaseRepos = getReleaseRepos(name);
if (releaseRepos.isEmpty()) {
return null;
}
RepositoryPlugin releaseRepo = releaseRepos.get(0); // use only first
// release repo
return releaseRepo(releaseRepo, jarName, jarStream);
}
private URI releaseRepo(RepositoryPlugin releaseRepo, String jarName, InputStream jarStream) throws Exception {
logger.debug("release to {}", releaseRepo.getName());
try {
PutOptions putOptions = new RepositoryPlugin.PutOptions();
// TODO find sub bnd that is associated with this thing
putOptions.context = this;
PutResult r = releaseRepo.put(jarStream, putOptions);
logger.debug("Released {} to {} in repository {}", jarName, r.artifact, releaseRepo);
return r.artifact;
} catch (Exception e) {
msgs.Release_Into_Exception_(jarName, releaseRepo, e);
return null;
}
}
private List getReleaseRepos(String names) {
Parameters repoNames = parseReleaseRepos(names);
List plugins = getPlugins(RepositoryPlugin.class);
List result = new ArrayList<>();
if (repoNames == null) { // -releaserepo unspecified
for (RepositoryPlugin plugin : plugins) {
if (plugin.canWrite()) {
result.add(plugin);
break;
}
}
if (result.isEmpty()) {
msgs.NoNameForReleaseRepository();
}
return result;
}
repoNames: for (String repoName : repoNames.keySet()) {
for (RepositoryPlugin plugin : plugins) {
if (plugin.canWrite() && repoName.equals(plugin.getName())) {
result.add(plugin);
continue repoNames;
}
}
msgs.ReleaseRepository_NotFoundIn_(repoName, plugins);
}
return result;
}
private Parameters parseReleaseRepos(String names) {
if (names == null) {
names = mergeProperties(RELEASEREPO);
if (names == null) {
return null; // -releaserepo unspecified
}
}
return new Parameters(names, this);
}
public void release(boolean test) throws Exception {
release(null, test);
}
/**
* Release
*
* @param name The respository name
* @param test Run testcases
* @throws Exception
*/
public void release(String name, boolean test) throws Exception {
List releaseRepos = getReleaseRepos(name);
if (releaseRepos.isEmpty()) {
return;
}
logger.debug("release");
File[] jars = getBuildFiles(false);
if (jars == null) {
jars = build(test);
// If build fails jars will be null
if (jars == null) {
logger.debug("no jars built");
return;
}
}
logger.debug("releasing {} - {}", jars, releaseRepos);
for (RepositoryPlugin releaseRepo : releaseRepos) {
for (File jar : jars) {
releaseRepo(releaseRepo, jar.getName(), new BufferedInputStream(IO.stream(jar)));
}
}
}
/**
* Get a bundle from one of the plugin repositories. If an exact version is
* required we just return the first repository found (in declaration order
* in the build.bnd file).
*
* @param bsn The bundle symbolic name
* @param range The version range
* @param strategy 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 strategy, Map attrs)
throws Exception {
if (range == null)
range = "0";
if (VERSION_ATTR_SNAPSHOT.equals(range) || VERSION_ATTR_PROJECT.equals(range)) {
return getBundleFromProject(bsn, attrs);
} else if (VERSION_ATTR_HASH.equals(range)) {
return getBundleByHash(bsn, attrs);
}
Strategy useStrategy = strategy;
if (VERSION_ATTR_LATEST.equals(range)) {
Container c = getBundleFromProject(bsn, attrs);
if (c != null)
return c;
useStrategy = Strategy.HIGHEST;
}
useStrategy = overrideStrategy(attrs, useStrategy);
RepoFilter repoFilter = parseRepoFilter(attrs);
List plugins = workspace.getRepositories();
if (useStrategy == Strategy.EXACT) {
if (!Verifier.isVersion(range))
return new Container(this, bsn, range, Container.TYPE.ERROR, null,
bsn + ";version=" + range + " Invalid version", null, null);
// For an exact range we just iterate over the repos
// and return the first we find.
Version version = new Version(range);
for (RepositoryPlugin plugin : plugins) {
DownloadBlocker blocker = new DownloadBlocker(this);
File result = plugin.get(bsn, version, attrs, blocker);
if (result != null)
return toContainer(bsn, range, attrs, result, blocker);
}
} else {
VersionRange versionRange = VERSION_ATTR_LATEST.equals(range) ? new VersionRange("0")
: new VersionRange(range);
// We have a range search. Gather all the versions in all the repos
// and make a decision on that choice. If the same version is found
// in
// multiple repos we take the first
SortedMap versions = new TreeMap<>();
for (RepositoryPlugin plugin : plugins) {
if (repoFilter != null && !repoFilter.match(plugin))
continue;
try {
SortedSet vs = plugin.versions(bsn);
if (vs != null) {
for (Version v : vs) {
if (!versions.containsKey(v) && versionRange.includes(v))
versions.put(v, plugin);
}
}
} catch (UnsupportedOperationException ose) {
// We have a plugin that cannot list versions, try
// if it has this specific version
// The main reaosn for this code was the Maven Remote
// Repository
// To query, we must have a real version
if (!versions.isEmpty() && Verifier.isVersion(range)) {
Version version = new Version(range);
DownloadBlocker blocker = new DownloadBlocker(this);
File file = plugin.get(bsn, version, attrs, blocker);
// and the entry must exist
// if it does, return this as a result
if (file != null)
return toContainer(bsn, range, attrs, file, blocker);
}
}
}
//
// We have to augment the list of returned versions
// with info from the workspace. We use null as a marker
// to indicate that it is a workspace project
//
SortedSet localVersions = getWorkspace().getWorkspaceRepository()
.versions(bsn);
for (Version v : localVersions) {
if (!versions.containsKey(v) && versionRange.includes(v))
versions.put(v, null);
}
// Verify if we found any, if so, we use the strategy to pick
// the first or last
if (!versions.isEmpty()) {
Version provider = null;
switch (useStrategy) {
case HIGHEST :
provider = versions.lastKey();
break;
case LOWEST :
provider = versions.firstKey();
break;
case EXACT :
// TODO need to handle exact better
break;
}
if (provider != null) {
RepositoryPlugin repo = versions.get(provider);
if (repo == null) {
// A null provider indicates that we have a local
// project
return getBundleFromProject(bsn, attrs);
}
String version = provider.toString();
DownloadBlocker blocker = new DownloadBlocker(this);
File result = repo.get(bsn, provider, attrs, blocker);
if (result != null)
return toContainer(bsn, version, attrs, result, blocker);
} else {
msgs.FoundVersions_ForStrategy_ButNoProvider(versions, useStrategy);
}
}
}
//
// If we get this far we ran into an error somewhere
//
return new Container(this, bsn, range, Container.TYPE.ERROR, null,
bsn + ";version=" + range + " Not found in " + plugins, null, null);
}
/**
* @param attrs
* @param useStrategy
*/
protected Strategy overrideStrategy(Map attrs, Strategy useStrategy) {
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;
}
}
return useStrategy;
}
private static class RepoFilter {
private Pattern[] patterns;
RepoFilter(Pattern[] patterns) {
this.patterns = patterns;
}
boolean match(RepositoryPlugin repo) {
if (patterns == null)
return true;
for (Pattern pattern : patterns) {
if (pattern.matcher(repo.getName())
.matches())
return true;
}
return false;
}
}
protected RepoFilter parseRepoFilter(Map attrs) {
if (attrs == null)
return null;
String patternStr = attrs.get("repo");
if (patternStr == null)
return null;
List patterns = new LinkedList<>();
QuotedTokenizer tokenize = new QuotedTokenizer(patternStr, ",");
String token = tokenize.nextToken();
while (token != null) {
patterns.add(Glob.toPattern(token));
token = tokenize.nextToken();
}
return new RepoFilter(patterns.toArray(new Pattern[0]));
}
/**
* @param bsn
* @param range
* @param attrs
* @param result
*/
protected Container toContainer(String bsn, String range, Map attrs, File result,
DownloadBlocker db) {
File f = result;
if (f == null) {
msgs.ConfusedNoContainerFile();
f = new File("was null");
}
Container container;
if (f.getName()
.endsWith("lib"))
container = new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs, db);
else
container = new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs, db);
return container;
}
/**
* 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
* @throws Exception
*/
private Container getBundleFromProject(String bsn, Map 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);
}
}
private Container getBundleByHash(String bsn, Map attrs) throws Exception {
String hashStr = attrs.get("hash");
String algo = SHA_256;
String hash = hashStr;
int colonIndex = hashStr.indexOf(':');
if (colonIndex > -1) {
algo = hashStr.substring(0, colonIndex);
int afterColon = colonIndex + 1;
hash = (colonIndex < hashStr.length()) ? hashStr.substring(afterColon) : "";
}
for (RepositoryPlugin plugin : workspace.getRepositories()) {
// The plugin *may* understand version=hash directly
DownloadBlocker blocker = new DownloadBlocker(this);
File result = plugin.get(bsn, Version.LOWEST, Collections.unmodifiableMap(attrs), blocker);
// If not, and if the repository implements the OSGi Repository
// Service, use a capability search on the osgi.content namespace.
if (result == null && plugin instanceof Repository) {
Repository repo = (Repository) plugin;
if (!SHA_256.equals(algo))
// R5 repos only support SHA-256
continue;
Requirement contentReq = new CapReqBuilder(ContentNamespace.CONTENT_NAMESPACE)
.filter(String.format("(%s=%s)", ContentNamespace.CONTENT_NAMESPACE, hash))
.buildSyntheticRequirement();
Set reqs = Collections.singleton(contentReq);
Map> providers = repo.findProviders(reqs);
Collection caps = providers != null ? providers.get(contentReq) : null;
if (caps != null && !caps.isEmpty()) {
Capability cap = caps.iterator()
.next();
IdentityCapability idCap = ResourceUtils.getIdentityCapability(cap.getResource());
Map idAttrs = idCap.getAttributes();
String id = (String) idAttrs.get(IdentityNamespace.IDENTITY_NAMESPACE);
Object version = idAttrs.get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
Version bndVersion = version != null ? Version.parseVersion(version.toString()) : Version.LOWEST;
if (!bsn.equals(id)) {
String error = String.format("Resource with requested hash does not match ID '%s' [hash: %s]",
bsn, hashStr);
return new Container(this, bsn, "hash", Container.TYPE.ERROR, null, error, null, null);
}
result = plugin.get(id, bndVersion, null, blocker);
}
}
if (result != null)
return toContainer(bsn, "hash", attrs, result, blocker);
}
// If we reach this far, none of the repos found the resource.
return new Container(this, bsn, "hash", Container.TYPE.ERROR, null,
"Could not find resource by content hash " + hashStr, null, null);
}
/**
* 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 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 {
rp.put(new BufferedInputStream(IO.stream(file)), new RepositoryPlugin.PutOptions());
return;
} catch (Exception e) {
msgs.DeployingFile_On_Exception_(file, rp.getName(), e);
}
return;
}
logger.debug("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 {
Parameters deploy = new Parameters(getProperty(DEPLOY), this);
if (deploy.isEmpty()) {
warning("Deploying but %s is not set to any repo", DEPLOY);
return;
}
File[] outputs = getBuildFiles();
for (File output : outputs) {
for (Deploy d : getPlugins(Deploy.class)) {
logger.debug("Deploying {} to: {}", output.getName(), d);
try {
if (d.deploy(this, output.getName(), new BufferedInputStream(IO.stream(output))))
logger.debug("deployed {} successfully to {}", output, d);
} catch (Exception e) {
msgs.Deploying(e);
}
}
}
}
/**
* Macro access to the repository ${repo;[;[;]]}
*/
static String _repoHelp = "${repo ';' [ ; [; ('HIGHEST'|'LOWEST')]}";
public String _repo(String args[]) throws Exception {
if (args.length < 2) {
msgs.RepoTooFewArguments(_repoHelp, args);
return null;
}
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
msgs.InvalidStrategy(_repoHelp, args);
}
}
Collection parts = split(bsns);
List paths = new ArrayList<>();
for (String bsn : parts) {
Container container = getBundle(bsn, version, strategy, null);
if (container.getError() != null) {
error("${repo} macro refers to an artifact %s-%s (%s) that has an error: %s", bsn, version, strategy,
container.getError());
} else
add(paths, container);
}
return join(paths);
}
private void add(List paths, Container container) throws Exception {
if (container.getType() == Container.TYPE.LIBRARY) {
List members = container.getMembers();
for (Container sub : members) {
add(paths, sub);
}
} else {
if (container.getError() == null)
paths.add(IO.absolutePath(container.getFile()));
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
* @throws Exception
*/
public File[] build(boolean underTest) throws Exception {
if (isNoBundles())
return null;
if (getProperty("-nope") != null) {
warning("Please replace -nope with %s", NOBUNDLES);
return null;
}
logger.debug("building {}", this);
File[] files = buildLocal(underTest);
install(files);
return files;
}
private void install(File[] files) throws Exception {
if (files == null)
return;
Parameters p = getInstallRepositories();
for (Map.Entry e : p.entrySet()) {
RepositoryPlugin rp = getWorkspace().getRepository(e.getKey());
if (rp != null) {
for (File f : files) {
install(f, rp, e.getValue());
}
} else
warning("No such repository to install into: %s", e.getKey());
}
}
public Parameters getInstallRepositories() {
if (data.installRepositories == null) {
data.installRepositories = new Parameters(mergeProperties(BUILDREPO), this);
}
return data.installRepositories;
}
private void install(File f, RepositoryPlugin repo, Attrs value) throws Exception {
try (Processor p = new Processor()) {
p.getProperties()
.putAll(value);
PutOptions options = new PutOptions();
options.context = p;
try (InputStream in = IO.stream(f)) {
repo.put(in, options);
} catch (Exception e) {
exception(e, "Cannot install %s into %s because %s", f, repo.getName(), e);
}
}
}
/**
* Return the files
*/
public File[] getFiles() {
return files;
}
/**
* Check if this project needs building. This is defined as:
*/
public boolean isStale() throws Exception {
Set visited = new HashSet<>();
return isStale(visited);
}
boolean isStale(Set visited) throws Exception {
// When we do not generate anything ...
if (isNoBundles())
return false;
if (!visited.add(this)) {
return false;
}
long buildTime = 0;
File[] files = getBuildFiles(false);
if (files == null)
return true;
for (File f : files) {
if (f.lastModified() < lastModified())
return true;
if (buildTime < f.lastModified())
buildTime = f.lastModified();
}
for (Project dependency : getDependson()) {
if (dependency == this)
continue;
if (dependency.isNoBundles()) {
continue;
}
if (dependency.isStale(visited)) {
return true;
}
File[] deps = dependency.getBuildFiles(false);
if (deps == null) {
return true;
}
for (File f : deps) {
if (buildTime < f.lastModified()) {
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.
* This method remembers the files in the appropriate instance vars.
*/
public File[] getBuildFiles() throws Exception {
return getBuildFiles(true);
}
public File[] getBuildFiles(boolean buildIfAbsent) throws Exception {
File[] current = files;
if (current != null) {
return current;
}
File bfs = new File(getTarget(), BUILDFILES);
if (bfs.isFile()) {
try (BufferedReader rdr = IO.reader(bfs)) {
List list = newList();
for (String s = rdr.readLine(); s != null; s = rdr.readLine()) {
s = s.trim();
File ff = new File(s);
if (!ff.isFile()) {
// Originally we warned the user
// but lets just rebuild. That way
// the error is not noticed but
// it seems better to correct,
// See #154
rdr.close();
IO.delete(bfs);
return files = buildIfAbsent ? buildLocal(false) : null;
}
list.add(ff);
}
return files = list.toArray(new File[0]);
}
}
return files = buildIfAbsent ? buildLocal(false) : null;
}
/**
* Build without doing any dependency checking. Make sure any dependent
* projects are built first.
*
* @param underTest
* @throws Exception
*/
public File[] buildLocal(boolean underTest) throws Exception {
if (isNoBundles()) {
return files = null;
}
versionMap.clear();
getMakefile().make();
File[] buildfiles = getBuildFiles(false);
File bfs = new File(getTarget(), BUILDFILES);
files = null;
//
// #761 tstamp can vary between invocations in one build
// Macro can handle a @tstamp time so we freeze the time at
// the start of the build. We do this carefully so someone higher
// up the chain can actually freeze the time longer
//
boolean tstamp = false;
if (getProperty(TSTAMP) == null) {
setProperty(TSTAMP, Long.toString(System.currentTimeMillis()));
tstamp = true;
}
try (ProjectBuilder builder = getBuilder(null)) {
if (underTest)
builder.setProperty(Constants.UNDERTEST, "true");
Jar jars[] = builder.builds();
getInfo(builder);
if (isPedantic() && !unreferencedClasspathEntries.isEmpty()) {
warning("Unreferenced class path entries %s", unreferencedClasspathEntries.keySet());
}
if (!isOk()) {
return null;
}
Set builtFiles = Create.set();
long lastModified = 0L;
for (Jar jar : jars) {
File file = saveBuild(jar);
if (file == null) {
getInfo(builder);
error("Could not save %s", jar.getName());
return null;
}
builtFiles.add(file);
if (lastModified < file.lastModified()) {
lastModified = file.lastModified();
}
}
boolean bfsWrite = !bfs.exists() || (lastModified > bfs.lastModified());
if (buildfiles != null) {
Set removed = Create.set(buildfiles);
if (!removed.equals(builtFiles)) {
bfsWrite = true;
removed.removeAll(builtFiles);
for (File remove : removed) {
IO.delete(remove);
getWorkspace().changedFile(remove);
}
}
}
// Write out the filenames in the buildfiles file
// so we can get them later even in another process
if (bfsWrite) {
try (PrintWriter fw = IO.writer(bfs)) {
for (File f : builtFiles) {
fw.write(IO.absolutePath(f));
fw.write('\n');
}
}
getWorkspace().changedFile(bfs);
}
bfs = null; // avoid delete in finally block
return files = builtFiles.toArray(new File[0]);
} finally {
if (tstamp)
unsetProperty(TSTAMP);
if (bfs != null) {
IO.delete(bfs); // something went wrong, so delete
getWorkspace().changedFile(bfs);
}
}
}
/**
* Answer if this project does not have any output
*/
public boolean isNoBundles() {
return isTrue(getProperty(NOBUNDLES));
}
public File saveBuild(Jar jar) throws Exception {
try {
File outputFile = getOutputFile(jar.getName(), jar.getVersion());
File logicalFile = outputFile;
String msg = "";
if (!outputFile.exists() || outputFile.lastModified() < jar.lastModified()) {
reportNewer(outputFile.lastModified(), jar);
File fp = outputFile.getParentFile();
if (!fp.isDirectory()) {
IO.mkdirs(fp);
}
// On windows we sometimes cannot delete a file because
// someone holds a lock in our or another process. So if
// we set the -overwritestrategy flag we use an avoiding
// strategy.
// We will always write to a temp file name. Basically the
// calculated name + a variable suffix. We then create
// a link with the constant name to this variable name.
// This allows us to pick a different suffix when we cannot
// delete the file. Yuck, but better than the alternative.
String overwritestrategy = getProperty("-x-overwritestrategy", "classic");
swtch: switch (overwritestrategy) {
case "delay" :
for (int i = 0; i < 10; i++) {
try {
IO.deleteWithException(outputFile);
jar.write(outputFile);
break swtch;
} catch (Exception e) {
Thread.sleep(500);
}
}
// Execute normal case to get classic behavior
// FALL THROUGH
case "classic" :
IO.deleteWithException(outputFile);
jar.write(outputFile);
break swtch;
case "gc" :
try {
IO.deleteWithException(outputFile);
} catch (Exception e) {
System.gc();
System.runFinalization();
IO.deleteWithException(outputFile);
}
jar.write(outputFile);
break swtch;
case "windows-only-disposable-names" :
boolean isWindows = File.separatorChar == '\\';
if (!isWindows) {
IO.deleteWithException(outputFile);
jar.write(outputFile);
break;
}
// Fall through
case "disposable-names" :
int suffix = 0;
while (true) {
outputFile = new File(outputFile.getParentFile(), outputFile.getName() + "-" + suffix);
IO.delete(outputFile);
if (!outputFile.isFile()) {
// Succeeded to delete the file
jar.write(outputFile);
Files.createSymbolicLink(logicalFile.toPath(), outputFile.toPath());
break;
} else {
warning("Could not delete build file {} ", overwritestrategy);
logger.warn("Cannot delete file {} but that should be ok", outputFile);
}
suffix++;
}
break swtch;
default :
error(
"Invalid value for -x-overwritestrategy: %s, expected classic, delay, gc, windows-only-disposable-names, disposable-names",
overwritestrategy);
IO.deleteWithException(outputFile);
jar.write(outputFile);
break swtch;
}
//
// For maven we've got the shitty situation that the
// files in the generated directories have an ever changing
// version number so it is hard to refer to them in test cases
// and from for example bndtools if you want to refer to the
// latest so the following code attempts to create a link to the
// output file if this is using some other naming scheme,
// creating a constant name. Would probably be more logical to
// always output in the canonical name and then create a link to
// the desired name but not sure how much that would break BJ's
// maven handling that caused these versioned JARs
//
File canonical = new File(getTarget(), jar.getName() + ".jar");
if (!canonical.equals(logicalFile)) {
IO.delete(canonical);
if (!IO.createSymbolicLink(canonical, outputFile)) {
//
// As alternative, we copy the file
//
IO.copy(outputFile, canonical);
}
getWorkspace().changedFile(canonical);
}
getWorkspace().changedFile(outputFile);
if (!outputFile.equals(logicalFile))
getWorkspace().changedFile(logicalFile);
} else {
msg = "(not modified since " + new Date(outputFile.lastModified()) + ")";
}
logger.debug("{} ({}) {} {}", jar.getName(), outputFile.getName(), jar.getResources()
.size(), msg);
return logicalFile;
} finally
{
jar.close();
}
}
/**
* Calculate the file for a JAR. The default name is bsn.jar, but this can
* be overridden with an
*
* @throws Exception
*/
public File getOutputFile(String bsn, String version) throws Exception {
if (version == null)
version = "0";
try (Processor scoped = new Processor(this)) {
scoped.setProperty("@bsn", bsn);
scoped.setProperty("@version", version);
String path = scoped.getProperty(OUTPUTMASK, bsn + ".jar");
return IO.getFile(getTarget(), path);
}
}
public File getOutputFile(String bsn) throws Exception {
return getOutputFile(bsn, "0.0.0");
}
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 entry : jar.getResources()
.entrySet()) {
if (entry.getValue()
.lastModified() > lastModified) {
sb.append(del);
del = ", \n ";
sb.append(entry.getKey());
}
}
if (sb.length() > 0)
warning("%s", sb.toString());
}
}
/**
* Refresh if we are based on stale data. This also implies our workspace.
*/
@Override
public boolean refresh() {
versionMap.clear();
data = new RefreshData();
boolean changed = false;
if (isCnf()) {
changed = workspace.refresh();
}
return super.refresh() || changed;
}
public boolean isCnf() {
try {
return getBase().getCanonicalPath()
.equals(getWorkspace().getBuildDir()
.getCanonicalPath());
} catch (IOException e) {
return false;
}
}
@Override
public void propertiesChanged() {
super.propertiesChanged();
preparedPaths.set(false);
files = null;
makefile = null;
versionMap.clear();
data = new RefreshData();
}
public String getName() {
return getBase().getName();
}
public Map getActions() {
Map all = newMap();
Map actions = newMap();
fillActions(all);
getWorkspace().fillActions(all);
for (Map.Entry 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 all) {
List plugins = getPlugins(NamedAction.class);
for (NamedAction a : plugins)
all.put(a.getName(), a);
Parameters actions = new Parameters(getProperty("-actions", DEFAULT_ACTIONS), this);
for (Entry 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);
}
public Map.Entry export(String type, Map options) throws Exception {
Exporter exporter = getExporter(type);
if (exporter == null) {
error("No exporter for %s", type);
return null;
}
if (options == null) {
options = Collections.emptyMap();
}
Parameters exportTypes = parseHeader(getProperty(Constants.EXPORTTYPE));
Map attrs = exportTypes.get(type);
if (attrs != null) {
attrs.putAll(options);
options = attrs;
}
return exporter.export(type, this, options);
}
private Exporter getExporter(String type) {
List exporters = getPlugins(Exporter.class);
for (Exporter e : exporters) {
for (String exporterType : e.getTypes()) {
if (type.equals(exporterType)) {
return e;
}
}
}
return null;
}
public void export(String runFilePath, boolean keep, File output) throws Exception {
Map options = Collections.singletonMap("keep", Boolean.toString(keep));
Entry export;
if (runFilePath == null || runFilePath.length() == 0 || ".".equals(runFilePath)) {
clear();
export = export(ExecutableJarExporter.EXECUTABLE_JAR, options);
} else {
File runFile = IO.getFile(getBase(), runFilePath);
if (!runFile.isFile())
throw new IOException(
String.format("Run file %s does not exist (or is not a file).", IO.absolutePath(runFile)));
try (Run run = new Run(getWorkspace(), getBase(), runFile)) {
export = run.export(ExecutableJarExporter.EXECUTABLE_JAR, options);
getInfo(run);
}
}
if (export != null) {
try (JarResource r = (JarResource) export.getValue()) {
r.getJar()
.write(output);
}
}
}
/**
* @since 2.4
*/
public void exportRunbundles(String runFilePath, File outputDir) throws Exception {
Map options = Collections.emptyMap();
Entry export;
if (runFilePath == null || runFilePath.length() == 0 || ".".equals(runFilePath)) {
clear();
export = export(RunbundlesExporter.RUNBUNDLES, options);
} else {
File runFile = IO.getFile(getBase(), runFilePath);
if (!runFile.isFile())
throw new IOException(
String.format("Run file %s does not exist (or is not a file).", IO.absolutePath(runFile)));
try (Run run = new Run(getWorkspace(), getBase(), runFile)) {
export = run.export(RunbundlesExporter.RUNBUNDLES, options);
getInfo(run);
}
}
if (export != null) {
try (JarResource r = (JarResource) export.getValue()) {
r.getJar()
.writeFolder(outputDir);
}
}
}
/**
* Release.
*
* @param name The repository name
* @throws Exception
*/
public void release(String name) throws Exception {
release(name, false);
}
public void clean() throws Exception {
clean(getTargetDir(), "target");
clean(getSrcOutput(), "source output");
clean(getTestOutput(), "test output");
}
void clean(File dir, String type) throws IOException {
if (!dir.exists())
return;
String basePath = getBase().getCanonicalPath();
String dirPath = dir.getCanonicalPath();
if (!dirPath.startsWith(basePath)) {
logger.debug("path outside the project dir {}", type);
return;
}
if (dirPath.length() == basePath.length()) {
error("Trying to delete the project directory for %s", type);
return;
}
IO.delete(dir);
if (dir.exists()) {
error("Trying to delete %s (%s), but failed", dir, type);
return;
}
IO.mkdirs(dir);
}
public File[] build() throws Exception {
return build(false);
}
private Makefile getMakefile() {
if (makefile == null) {
makefile = new Makefile(this);
}
return makefile;
}
public void run() throws Exception {
try (ProjectLauncher pl = getProjectLauncher()) {
pl.setTrace(isTrace() || isTrue(getProperty(RUNTRACE)));
pl.launch();
}
}
public void runLocal() throws Exception {
try (ProjectLauncher pl = getProjectLauncher()) {
pl.setTrace(isTrace() || isTrue(getProperty(RUNTRACE)));
pl.start(null);
}
}
public void test() throws Exception {
test(null);
}
public void test(List tests) throws Exception {
String testcases = getProperties().getProperty(Constants.TESTCASES);
if (testcases == null) {
warning("No %s set", Constants.TESTCASES);
return;
}
clear();
test(null, tests);
}
public void test(File reportDir, List tests) throws Exception {
ProjectTester tester = getProjectTester();
if (reportDir != null) {
logger.debug("Setting reportDir {}", reportDir);
IO.delete(reportDir);
tester.setReportDir(reportDir);
}
if (tests != null) {
logger.debug("Adding tests {}", tests);
for (String test : tests) {
tester.addTest(test);
}
}
tester.prepare();
if (!isOk()) {
logger.error("Tests not run because project has errors");
return;
}
int errors = tester.test();
if (errors == 0) {
logger.info("No Errors");
} else {
if (errors > 0) {
logger.info("{} Error(s)", errors);
} else {
logger.info("Error {}", errors);
}
}
}
/**
* Run JUnit
*
* @throws Exception
*/
public void junit() throws Exception {
@SuppressWarnings("resource")
JUnitLauncher launcher = new JUnitLauncher(this);
launcher.launch();
}
/**
* 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.
*
* @throws Exception
*/
public Jar getValidJar(File f) throws Exception {
Jar jar = new Jar(f);
return getValidJar(jar, IO.absolutePath(f));
}
public Jar getValidJar(URL url) throws Exception {
try (Resource resource = Resource.fromURL(url)) {
Jar jar = Jar.fromResource(url.getFile()
.replace('/', '.'), resource);
return getValidJar(jar, url.toString());
}
}
public Jar getValidJar(Jar jar, String id) throws Exception {
Manifest manifest = jar.getManifest();
if (manifest == null) {
logger.debug("Wrapping with all defaults");
Builder b = new Builder(this);
this.addClose(b);
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) {
logger.debug("Not a release 4 bundle, wrapping with manifest as source");
Builder b = new Builder(this);
this.addClose(b);
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(@SuppressWarnings("unused") String args[]) {
return IO.absolutePath(getBase());
}
/**
* Bump the version of this project. First check the main bnd file. If this
* does not contain a version, check the include files. If they still do not
* contain a version, then check ALL the sub builders. If not, add a version
* to the main bnd file.
*
* @param mask the mask for bumping, see {@link Macro#_version(String[])}
* @throws Exception
*/
public void bump(String mask) throws Exception {
String pattern = "(" + Constants.BUNDLE_VERSION + "\\s*(:|=)\\s*)(([0-9]+(\\.[0-9]+(\\.[0-9]+)?)?))";
String replace = "$1${version;" + mask + ";$3}";
try {
// First try our main bnd file
if (replace(getPropertiesFile(), pattern, replace))
return;
logger.debug("no version in bnd.bnd");
// Try the included filed in reverse order (last has highest
// priority)
for (Iterator iter = new ArrayDeque<>(getIncluded()).descendingIterator(); iter.hasNext();) {
File file = iter.next();
if (replace(file, pattern, replace)) {
logger.debug("replaced version in file {}", file);
return;
}
}
logger.debug("no version in included files");
boolean found = false;
// Replace in all sub builders.
try (ProjectBuilder b = getBuilder(null)) {
for (Builder sub : b.getSubBuilders()) {
found |= replace(sub.getPropertiesFile(), pattern, replace);
}
}
if (!found) {
logger.debug("no version in sub builders, add it to bnd.bnd");
String bndfile = IO.collect(getPropertiesFile());
bndfile += "\n# Added by by bump\n" + Constants.BUNDLE_VERSION + ": 0.0.0\n";
IO.store(bndfile, getPropertiesFile());
}
} finally {
forceRefresh();
}
}
boolean replace(File f, String pattern, String replacement) throws IOException {
final Macro macro = getReplacer();
Sed sed = new Sed(new Replacer() {
@Override
public String process(String line) {
return macro.process(line);
}
}, f);
sed.replace(pattern, replacement);
return sed.doIt() > 0;
}
public void bump() throws Exception {
bump(getProperty(BUMPPOLICY, "=+0"));
}
public void action(String command) throws Exception {
action(command, new Object[0]);
}
public void action(String command, Object... args) throws Exception {
Map actions = getActions();
Action a = actions.get(command);
if (a == null)
a = new ReflectAction(command);
before(this, command);
try {
if (args.length == 0)
a.execute(this, command);
else
a.execute(this, args);
} catch (Exception t) {
after(this, command, t);
throw t;
}
}
/**
* Run all before command plugins
*/
void before(@SuppressWarnings("unused") Project p, String a) {
List testPlugins = getPlugins(CommandPlugin.class);
for (CommandPlugin testPlugin : testPlugins) {
testPlugin.before(this, a);
}
}
/**
* Run all after command plugins
*/
void after(@SuppressWarnings("unused") Project p, String a, Throwable t) {
List testPlugins = getPlugins(CommandPlugin.class);
for (int i = testPlugins.size() - 1; i >= 0; i--) {
testPlugins.get(i)
.after(this, a, t);
}
}
public void refreshAll() {
workspace.refresh();
refresh();
}
@SuppressWarnings("unchecked")
public void script(@SuppressWarnings("unused") String type, String script) throws Exception {
script(type, script, new Object[0]);
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
public void script(String type, String script, Object... args) throws Exception {
// TODO check tyiping
List scripters = getPlugins(Scripter.class);
if (scripters.isEmpty()) {
msgs.NoScripters_(script);
return;
}
Properties p = new UTF8Properties(getProperties());
for (int i = 0; i < args.length; i++)
p.setProperty("" + i, Converter.cnv(String.class, args[i]));
scripters.get(0)
.eval((Map) p, new StringReader(script));
}
public String _repos(@SuppressWarnings("unused") String args[]) throws Exception {
List repos = getPlugins(RepositoryPlugin.class);
List names = new ArrayList<>();
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.equals("example"))
return syntax.getExample();
if (what.equals("pattern"))
return syntax.getPattern();
if (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 getDeliverables() throws Exception {
List result = new ArrayList<>();
try (ProjectBuilder pb = getBuilder(null)) {
for (Builder builder : pb.getSubBuilders()) {
Container c = new Container(this, builder.getBsn(), builder.getVersion(), Container.TYPE.PROJECT,
getOutputFile(builder.getBsn(), builder.getVersion()), null, null, null);
result.add(c);
}
return result;
}
}
/**
* Return a 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 a builder for a sub file, the caller must close this
* builder
* @throws Exception
*/
public Builder getSubBuilder(File bndFile) throws Exception {
bndFile = bndFile.getAbsoluteFile();
// Verify that we are inside the project.
if (!bndFile.toPath()
.startsWith(getBase().toPath()))
return null;
ProjectBuilder pb = getBuilder(null);
boolean close = true;
try {
for (Builder b : pb.getSubBuilders()) {
File propertiesFile = b.getPropertiesFile();
if (propertiesFile != null) {
if (propertiesFile.equals(bndFile)) {
// Found it!
// disconnect from its parent life cycle
if (b == pb) {
close = false;
} else {
pb.removeClose(b);
}
return b;
}
}
}
return null;
} finally {
if (close) {
pb.close();
}
}
}
/**
* Return a build that maps to the sub file.
*
* @param string
* @throws Exception
*/
public ProjectBuilder getSubBuilder(String string) throws Exception {
ProjectBuilder pb = getBuilder(null);
boolean close = true;
try {
for (Builder b : pb.getSubBuilders()) {
if (b.getBsn()
.equals(string)
|| b.getBsn()
.endsWith("." + string)) {
// disconnect from its parent life cycle
if (b == pb) {
close = false;
} else {
pb.removeClose(b);
}
return (ProjectBuilder) b;
}
}
return null;
} finally {
if (close) {
pb.close();
}
}
}
/**
* Answer the container associated with a given bsn.
*
* @throws Exception
*/
public Container getDeliverable(String bsn, Map attrs) throws Exception {
try (ProjectBuilder pb = getBuilder(null)) {
for (Builder b : pb.getSubBuilders()) {
if (b.getBsn()
.equals(bsn))
return new Container(this, getOutputFile(bsn, b.getVersion()), attrs);
}
}
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
* @deprecated As of 3.4. Replace with
*
*
* try (ProjectBuilder pb = getBuilder(null)) {
* for (Builder b : pb.getSubBuilders()) {
* ...
* }
* }
*
*/
@Deprecated
public Collection extends Builder> getSubBuilders() throws Exception {
ProjectBuilder pb = getBuilder(null);
boolean close = true;
try {
List builders = pb.getSubBuilders();
for (Builder b : builders) {
if (b == pb) {
close = false;
} else {
pb.removeClose(b);
}
}
return builders;
} finally {
if (close) {
pb.close();
}
}
}
/**
* 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 toFile(Collection containers) throws Exception {
ArrayList files = new ArrayList<>();
for (Container container : containers) {
container.contributeFiles(files, this);
}
return files;
}
public Collection getRunVM() {
Parameters hdr = getMergedParameters(RUNVM);
return hdr.keyList();
}
public Collection getRunProgramArgs() {
Parameters hdr = getMergedParameters(RUNPROGRAMARGS);
return hdr.keyList();
}
public Map getRunProperties() {
return OSGiHeader.parseProperties(mergeProperties(RUNPROPERTIES));
}
/**
* Get a launcher.
*
* @throws Exception
*/
public ProjectLauncher getProjectLauncher() throws Exception {
return getHandler(ProjectLauncher.class, getRunpath(), LAUNCHER_PLUGIN, "biz.aQute.launcher");
}
public ProjectTester getProjectTester() throws Exception {
String defaultDefault = since(About._3_0) ? "biz.aQute.tester" : "biz.aQute.junit";
return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN,
getProperty(Constants.TESTER, defaultDefault));
}
private T getHandler(Class target, Collection 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 withDefault = Create.list();
withDefault.addAll(containers);
withDefault.addAll(getBundles(Strategy.HIGHEST, defaultHandler, null));
logger.debug("candidates for handler {}: {}", target, withDefault);
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)) {
msgs.IncompatibleHandler_For_(launcher, defaultHandler);
} else {
logger.debug("found handler {} from {}", defaultHandler, c);
handlerClass = clz.asSubclass(target);
try {
Constructor extends T> constructor = handlerClass.getConstructor(Project.class,
Container.class);
return constructor.newInstance(this, c);
} catch (Exception e) {
// ignore
}
Constructor extends T> constructor = handlerClass.getConstructor(Project.class);
return constructor.newInstance(this);
}
}
}
}
}
throw new IllegalArgumentException("Default handler for " + header + " not found in " + defaultHandler);
}
/**
* 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;
}
/**
* bnd maintains a class path that is set by the environment, i.e. bnd is
* not in charge of it.
*/
public void addClasspath(File f) {
if (!f.isFile() && !f.isDirectory()) {
msgs.AddingNonExistentFileToClassPath_(f);
}
Container container = new Container(f, null);
classpath.add(container);
}
public void clearClasspath() {
classpath.clear();
unreferencedClasspathEntries.clear();
}
public Collection getClasspath() {
return classpath;
}
/**
* Pack the project (could be a bndrun file) and save it on disk. Report
* errors if they happen.
*/
static List ignore = new ExtList<>(BUNDLE_SPECIFIC_HEADERS);
/**
* Caller must close this JAR
*
* @param profile
* @return a jar with the executable code
* @throws Exception
*/
public Jar pack(String profile) throws Exception {
try (ProjectBuilder pb = getBuilder(null)) {
List subBuilders = pb.getSubBuilders();
if (subBuilders.size() != 1) {
error("Project has multiple bnd files, please select one of the bnd files").header(EXPORT)
.context(profile);
return null;
}
Builder b = subBuilders.iterator()
.next();
ignore.remove(BUNDLE_SYMBOLICNAME);
ignore.remove(BUNDLE_VERSION);
ignore.add(SERVICE_COMPONENT);
try (ProjectLauncher launcher = getProjectLauncher()) {
launcher.getRunProperties()
.put("profile", profile); // TODO
// remove
launcher.getRunProperties()
.put(PROFILE, profile);
Jar jar = launcher.executable();
Manifest m = jar.getManifest();
Attributes main = m.getMainAttributes();
for (String key : this) {
if (Character.isUpperCase(key.charAt(0)) && !ignore.contains(key)) {
String value = getProperty(key);
if (value == null)
continue;
Name name = new Name(key);
String trimmed = value.trim();
if (trimmed.isEmpty())
main.remove(name);
else if (EMPTY_HEADER.equals(trimmed))
main.put(name, "");
else
main.put(name, value);
}
}
if (main.getValue(BUNDLE_SYMBOLICNAME) == null)
main.putValue(BUNDLE_SYMBOLICNAME, b.getBsn());
if (main.getValue(BUNDLE_SYMBOLICNAME) == null)
main.putValue(BUNDLE_SYMBOLICNAME, getName());
if (main.getValue(BUNDLE_VERSION) == null) {
main.putValue(BUNDLE_VERSION, Version.LOWEST.toString());
warning("No version set, uses 0.0.0");
}
jar.setManifest(m);
jar.calcChecksums(new String[] {
"SHA1", "MD5"
});
launcher.removeClose(jar);
return jar;
}
}
}
/**
* Do a baseline for this project
*
* @throws Exception
*/
public void baseline() throws Exception {
try (ProjectBuilder pb = getBuilder(null)) {
for (Builder b : pb.getSubBuilders()) {
@SuppressWarnings("resource")
Jar build = b.build();
getInfo(b);
}
getInfo(pb);
}
}
/**
* Method to verify that the paths are correct, ie no missing dependencies
*
* @param test for test cases, also adds -testpath
* @throws Exception
*/
public void verifyDependencies(boolean test) throws Exception {
verifyDependencies(RUNBUNDLES, getRunbundles());
verifyDependencies(RUNPATH, getRunpath());
if (test)
verifyDependencies(TESTPATH, getTestpath());
verifyDependencies(BUILDPATH, getBuildpath());
}
private void verifyDependencies(String title, Collection path) throws Exception {
List msgs = new ArrayList<>();
for (Container c : new ArrayList<>(path)) {
for (Container cc : c.getMembers()) {
if (cc.getError() != null)
msgs.add(cc + " - " + cc.getError());
else if (!cc.getFile()
.isFile()
&& !cc.getFile()
.equals(cc.getProject()
.getOutput())
&& !cc.getFile()
.equals(cc.getProject()
.getTestOutput()))
msgs.add(cc + " file does not exists: " + cc.getFile());
}
}
if (msgs.isEmpty())
return;
error("%s: has errors: %s", title, Strings.join(msgs));
}
/**
* Report detailed info from this project
*
* @throws Exception
*/
@Override
public void report(Map table) throws Exception {
super.report(table);
report(table, true);
}
protected void report(Map table, boolean isProject) throws Exception {
if (isProject) {
table.put("Target", getTarget());
table.put("Source", getSrc());
table.put("Output", getOutput());
File[] buildFiles = getBuildFiles();
if (buildFiles != null)
table.put("BuildFiles", Arrays.asList(buildFiles));
table.put("Classpath", getClasspath());
table.put("Actions", getActions());
table.put("AllSourcePath", getAllsourcepath());
table.put("BootClassPath", getBootclasspath());
table.put("BuildPath", getBuildpath());
table.put("Deliverables", getDeliverables());
table.put("DependsOn", getDependson());
table.put("SourcePath", getSourcePath());
}
table.put("RunPath", getRunpath());
table.put("TestPath", getTestpath());
table.put("RunProgramArgs", getRunProgramArgs());
table.put("RunVM", getRunVM());
table.put("Runfw", getRunFw());
table.put("Runbundles", getRunbundles());
}
// TODO test format parametsr
public void compile(boolean test) throws Exception {
Command javac = getCommonJavac(false);
javac.add("-d", IO.absolutePath(getOutput()));
StringBuilder buildpath = new StringBuilder();
String buildpathDel = "";
Collection bp = Container.flatten(getBuildpath());
logger.debug("buildpath {}", getBuildpath());
for (Container c : bp) {
buildpath.append(buildpathDel)
.append(IO.absolutePath(c.getFile()));
buildpathDel = File.pathSeparator;
}
if (buildpath.length() != 0) {
javac.add("-classpath", buildpath.toString());
}
List sp = new ArrayList<>(getAllsourcepath());
StringBuilder sourcepath = new StringBuilder();
String sourcepathDel = "";
for (File sourceDir : sp) {
sourcepath.append(sourcepathDel)
.append(IO.absolutePath(sourceDir));
sourcepathDel = File.pathSeparator;
}
javac.add("-sourcepath", sourcepath.toString());
Glob javaFiles = new Glob("*.java");
List files = javaFiles.getFiles(getSrc(), true, false);
for (File file : files) {
javac.add(IO.absolutePath(file));
}
if (files.isEmpty()) {
logger.debug("Not compiled, no source files");
} else
compile(javac, "src");
if (test) {
javac = getCommonJavac(true);
javac.add("-d", IO.absolutePath(getTestOutput()));
Collection tp = Container.flatten(getTestpath());
for (Container c : tp) {
buildpath.append(buildpathDel)
.append(IO.absolutePath(c.getFile()));
buildpathDel = File.pathSeparator;
}
if (buildpath.length() != 0) {
javac.add("-classpath", buildpath.toString());
}
sourcepath.append(sourcepathDel)
.append(IO.absolutePath(getTestSrc()));
javac.add("-sourcepath", sourcepath.toString());
javaFiles.getFiles(getTestSrc(), files, true, false);
for (File file : files) {
javac.add(IO.absolutePath(file));
}
if (files.isEmpty()) {
logger.debug("Not compiled for test, no test src files");
} else
compile(javac, "test");
}
}
private void compile(Command javac, String what) throws Exception {
logger.debug("compile {} {}", what, javac);
StringBuilder stdout = new StringBuilder();
StringBuilder stderr = new StringBuilder();
int n = javac.execute(stdout, stderr);
logger.debug("javac stdout: {}", stdout);
logger.debug("javac stderr: {}", stderr);
if (n != 0) {
error("javac failed %s", stderr);
}
}
private Command getCommonJavac(boolean test) throws Exception {
Command javac = new Command();
javac.add(getProperty("javac", "javac"));
String target = getProperty("javac.target", "1.6");
String profile = getProperty("javac.profile", "");
String source = getProperty("javac.source", "1.6");
String debug = getProperty("javac.debug");
if ("on".equalsIgnoreCase(debug) || "true".equalsIgnoreCase(debug))
debug = "vars,source,lines";
Parameters options = new Parameters(getProperty("java.options"), this);
boolean deprecation = isTrue(getProperty("java.deprecation"));
javac.add("-encoding", "UTF-8");
javac.add("-source", source);
javac.add("-target", target);
if (!profile.isEmpty())
javac.add("-profile", profile);
if (deprecation)
javac.add("-deprecation");
if (test || debug == null) {
javac.add("-g:source,lines,vars");
} else {
javac.add("-g:" + debug);
}
javac.addAll(options.keyList());
StringBuilder bootclasspath = new StringBuilder();
String bootclasspathDel = "-Xbootclasspath/p:";
Collection bcp = Container.flatten(getBootclasspath());
for (Container c : bcp) {
bootclasspath.append(bootclasspathDel)
.append(IO.absolutePath(c.getFile()));
bootclasspathDel = File.pathSeparator;
}
if (bootclasspath.length() != 0) {
javac.add(bootclasspath.toString());
}
return javac;
}
public String _ide(String[] args) throws IOException {
if (args.length < 2) {
error("The ${ide;<>} macro needs an argument");
return null;
}
if (ide == null) {
ide = new UTF8Properties();
File file = getFile(".settings/org.eclipse.jdt.core.prefs");
if (!file.isFile()) {
error("The ${ide;<>} macro requires a .settings/org.eclipse.jdt.core.prefs file in the project");
return null;
}
try (InputStream in = IO.stream(file)) {
ide.load(in);
}
}
String deflt = args.length > 2 ? args[2] : null;
if ("javac.target".equals(args[1])) {
return ide.getProperty("org.eclipse.jdt.core.compiler.codegen.targetPlatform", deflt);
}
if ("javac.source".equals(args[1])) {
return ide.getProperty("org.eclipse.jdt.core.compiler.source", deflt);
}
return null;
}
public Map getVersions() throws Exception {
if (versionMap.isEmpty()) {
try (ProjectBuilder pb = getBuilder(null)) {
for (Builder builder : pb.getSubBuilders()) {
String v = builder.getVersion();
if (v == null)
v = "0";
else {
v = Analyzer.cleanupVersion(v);
if (!Verifier.isVersion(v))
continue; // skip
}
Version version = new Version(v);
versionMap.put(builder.getBsn(), version);
}
}
}
return new LinkedHashMap<>(versionMap);
}
public Collection getBsns() throws Exception {
return new ArrayList<>(getVersions().keySet());
}
public Version getVersion(String bsn) throws Exception {
Version version = getVersions().get(bsn);
if (version == null) {
throw new IllegalArgumentException("Bsn " + bsn + " does not exist in project " + getName());
}
return version;
}
/**
* Get the exported packages form all builders calculated from the last
* build
*/
public Packages getExports() {
return exportedPackages;
}
/**
* Get the imported packages from all builders calculated from the last
* build
*/
public Packages getImports() {
return importedPackages;
}
/**
* Get the contained packages calculated from all builders from the last
* build
*/
public Packages getContained() {
return containedPackages;
}
public void remove() throws Exception {
getWorkspace().removeProject(this);
IO.delete(getBase());
}
public boolean getRunKeep() {
return is(Constants.RUNKEEP);
}
public void setPackageInfo(String packageName, Version newVersion) throws Exception {
packageInfo.setPackageInfo(packageName, newVersion);
}
public Version getPackageInfo(String packageName) throws Exception {
return packageInfo.getPackageInfo(packageName);
}
/**
* Actions to perform before a full workspace release. This is executed for
* projects that describe the distribution
*/
public void preRelease() {
for (ReleaseBracketingPlugin rp : getWorkspace().getPlugins(ReleaseBracketingPlugin.class)) {
rp.begin(this);
}
}
/**
* Actions to perform after a full workspace release. This is executed for
* projects that describe the distribution
*/
public void postRelease() {
for (ReleaseBracketingPlugin rp : getWorkspace().getPlugins(ReleaseBracketingPlugin.class)) {
rp.end(this);
}
}
/**
* Copy a repository to another repository
*
* @throws Exception
*/
public void copy(RepositoryPlugin source, String filter, RepositoryPlugin destination) throws Exception {
copy(source, filter == null ? null : new Instructions(filter), destination);
}
public void copy(RepositoryPlugin source, Instructions filter, RepositoryPlugin destination) throws Exception {
assert source != null;
assert destination != null;
logger.info("copy from repo {} to {} with filter {}", source, destination, filter);
for (String bsn : source.list(null)) {
for (Version version : source.versions(bsn)) {
if (filter == null || filter.matches(bsn)) {
logger.info("copy {}:{}", bsn, version);
File file = source.get(bsn, version, null);
if (file.getName()
.endsWith(".jar")) {
try (InputStream in = IO.stream(file)) {
PutOptions po = new PutOptions();
po.bsn = bsn;
po.context = null;
po.type = "bundle";
po.version = version;
PutResult put = destination.put(in, po);
} catch (Exception e) {
logger.error("Failed to copy {}-{}", e, bsn, version);
error("Failed to copy %s:%s from %s to %s, error: %s", bsn, version, source, destination,
e);
}
}
}
}
}
}
}