All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.bnd.build.Project Maven / Gradle / Ivy

The newest version!
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 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 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 constructor = handlerClass.getConstructor(Project.class, Container.class); return constructor.newInstance(this, c); } catch (Exception e) { // ignore } Constructor 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); } } } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy