aQute.bnd.build.Workspace Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of biz.aQute.bndlib Show documentation
Show all versions of biz.aQute.bndlib Show documentation
bndlib: A Swiss Army Knife for OSGi
package aQute.bnd.build;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.TimeLimitExceededException;
import aQute.bnd.annotation.plugin.BndPlugin;
import aQute.bnd.connection.settings.ConnectionSettings;
import aQute.bnd.exporter.subsystem.SubsystemExporter;
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
import aQute.bnd.http.HttpClient;
import aQute.bnd.maven.support.Maven;
import aQute.bnd.osgi.About;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Macro;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.Verifier;
import aQute.bnd.resource.repository.ResourceRepositoryImpl;
import aQute.bnd.service.BndListener;
import aQute.bnd.service.RepositoryPlugin;
import aQute.bnd.service.action.Action;
import aQute.bnd.service.extension.ExtensionActivator;
import aQute.bnd.service.lifecycle.LifeCyclePlugin;
import aQute.bnd.service.repository.Prepare;
import aQute.bnd.service.repository.RepositoryDigest;
import aQute.bnd.service.repository.SearchableRepository.ResourceDescriptor;
import aQute.bnd.url.MultiURLConnectionHandler;
import aQute.bnd.version.Version;
import aQute.bnd.version.VersionRange;
import aQute.lib.deployer.FileRepo;
import aQute.lib.hex.Hex;
import aQute.lib.io.IO;
import aQute.lib.io.IOConstants;
import aQute.lib.settings.Settings;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.lib.zip.ZipUtil;
import aQute.libg.uri.URIUtil;
import aQute.service.reporter.Reporter;
public class Workspace extends Processor {
public static final File BND_DEFAULT_WS = IO.getFile("~/.bnd/default-ws");
public static final String BND_CACHE_REPONAME = "bnd-cache";
public static final String EXT = "ext";
public static final String BUILDFILE = "build.bnd";
public static final String CNFDIR = "cnf";
public static final String BNDDIR = "bnd";
public static final String CACHEDIR = "cache/" + About.CURRENT;
public static final String STANDALONE_REPO_CLASS = "aQute.bnd.deployer.repository.FixedIndexedRepo";
static final int BUFFER_SIZE = IOConstants.PAGE_SIZE * 16;
private static final String PLUGIN_STANDALONE = "-plugin.standalone_";
private final Pattern EMBEDDED_REPO_TESTING_PATTERN = Pattern
.compile(".*biz\\.aQute\\.bnd\\.embedded-repo(-.*)?\\.jar");
static class WorkspaceData {
List repositories;
}
private final static Map> cache = newHashMap();
static Processor defaults = null;
final Map models = newHashMap();
private final Set modelsUnderConstruction = newSet();
final Map commands = newMap();
final Maven maven = new Maven(Processor.getExecutor());
private volatile boolean hasBndListeners = false;
private final AtomicBoolean offline = new AtomicBoolean();
Settings settings = new Settings();
WorkspaceRepository workspaceRepo = new WorkspaceRepository(this);
static String overallDriver = "unset";
static Parameters overallGestalt = new Parameters();
/**
* Signal a BndListener plugin. We ran an infinite bug loop :-(
*/
final ThreadLocal signalBusy = new ThreadLocal();
ResourceRepositoryImpl resourceRepositoryImpl;
private Parameters gestalt;
private String driver;
private final WorkspaceLayout layout;
final Set trail = Collections
.newSetFromMap(new ConcurrentHashMap());
private WorkspaceData data = new WorkspaceData();
private File buildDir;
/**
* This static method finds the workspace and creates a project (or returns
* an existing project)
*
* @param projectDir
*/
public static Project getProject(File projectDir) throws Exception {
projectDir = projectDir.getAbsoluteFile();
assert projectDir.isDirectory();
Workspace ws = getWorkspace(projectDir.getParentFile());
return ws.getProject(projectDir.getName());
}
static synchronized public Processor getDefaults() {
if (defaults != null)
return defaults;
UTF8Properties props = new UTF8Properties();
InputStream propStream = Workspace.class.getResourceAsStream("defaults.bnd");
if (propStream != null) {
try {
props.load(propStream);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to load bnd defaults.", e);
} finally {
IO.close(propStream);
}
} else
System.err.println("Cannot load defaults");
defaults = new Processor(props, false);
return defaults;
}
public static Workspace createDefaultWorkspace() throws Exception {
Workspace ws = new Workspace(BND_DEFAULT_WS, CNFDIR);
return ws;
}
public static Workspace getWorkspace(File workspaceDir) throws Exception {
return getWorkspace(workspaceDir, CNFDIR);
}
public static Workspace getWorkspaceWithoutException(File workspaceDir) throws Exception {
try {
return getWorkspace(workspaceDir);
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* /* Return the nearest workspace
*/
public static Workspace findWorkspace(File base) throws Exception {
File rover = base;
while (rover != null) {
File file = IO.getFile(rover, "cnf/build.bnd");
if (file.isFile())
return getWorkspace(rover);
rover = rover.getParentFile();
}
return null;
}
public static Workspace getWorkspace(File workspaceDir, String bndDir) throws Exception {
workspaceDir = workspaceDir.getAbsoluteFile();
// the cnf directory can actually be a
// file that redirects
while (workspaceDir.isDirectory()) {
File test = new File(workspaceDir, CNFDIR);
if (!test.exists())
test = new File(workspaceDir, bndDir);
if (test.isDirectory())
break;
if (test.isFile()) {
String redirect = IO.collect(test).trim();
test = getFile(test.getParentFile(), redirect).getAbsoluteFile();
workspaceDir = test;
}
if (!test.exists())
throw new IllegalArgumentException("No Workspace found from: " + workspaceDir);
}
synchronized (cache) {
WeakReference wsr = cache.get(workspaceDir);
Workspace ws;
if (wsr == null || (ws = wsr.get()) == null) {
ws = new Workspace(workspaceDir, bndDir);
cache.put(workspaceDir, new WeakReference(ws));
}
return ws;
}
}
public Workspace(File workspaceDir) throws Exception {
this(workspaceDir, CNFDIR);
}
public Workspace(File workspaceDir, String bndDir) throws Exception {
super(getDefaults());
workspaceDir = workspaceDir.getAbsoluteFile();
setBase(workspaceDir); // setBase before call to setFileSystem
this.layout = WorkspaceLayout.BND;
addBasicPlugin(new LoggingProgressPlugin());
setFileSystem(workspaceDir, bndDir);
}
public void setFileSystem(File workspaceDir, String bndDir) throws Exception {
workspaceDir = workspaceDir.getAbsoluteFile();
if (!workspaceDir.exists() && !workspaceDir.mkdirs()) {
throw new IOException("Could not create directory " + workspaceDir);
}
assert workspaceDir.isDirectory();
synchronized (cache) {
WeakReference wsr = cache.get(getBase());
if ((wsr != null) && (wsr.get() == this)) {
cache.remove(getBase());
cache.put(workspaceDir, wsr);
}
}
File buildDir = new File(workspaceDir, bndDir).getAbsoluteFile();
if (!buildDir.isDirectory())
buildDir = new File(workspaceDir, CNFDIR).getAbsoluteFile();
setBuildDir(buildDir);
File buildFile = new File(buildDir, BUILDFILE).getAbsoluteFile();
if (!buildFile.isFile())
warning("No Build File in %s", workspaceDir);
setProperties(buildFile, workspaceDir);
propertiesChanged();
//
// There is a nasty bug/feature in Java that gives errors on our
// SSL use of github. The flag jsse.enableSNIExtension should be set
// to false. So here we provide a way to set system properties
// as early as possible
//
Attrs sysProps = OSGiHeader.parseProperties(mergeProperties(SYSTEMPROPERTIES));
for (Entry e : sysProps.entrySet()) {
System.setProperty(e.getKey(), e.getValue());
}
}
private Workspace(WorkspaceLayout layout) throws Exception {
super(getDefaults());
this.layout = layout;
setBuildDir(IO.getFile(BND_DEFAULT_WS, CNFDIR));
}
public Project getProjectFromFile(File projectDir) throws Exception {
projectDir = projectDir.getAbsoluteFile();
assert projectDir.isDirectory();
if (getBase().equals(projectDir.getParentFile())) {
return getProject(projectDir.getName());
}
return null;
}
public Project getProject(String bsn) throws Exception {
synchronized (models) {
Project project = models.get(bsn);
if (project != null)
return project;
if (modelsUnderConstruction.add(bsn)) {
try {
File projectDir = getFile(bsn);
project = new Project(this, projectDir);
if (!project.isValid())
return null;
models.put(bsn, project);
} finally {
modelsUnderConstruction.remove(bsn);
}
}
return project;
}
}
void removeProject(Project p) throws Exception {
if (p.isCnf())
return;
synchronized (models) {
models.remove(p.getName());
}
for (LifeCyclePlugin lp : getPlugins(LifeCyclePlugin.class)) {
lp.delete(p);
}
}
public boolean isPresent(String name) {
return models.containsKey(name);
}
public Collection getCurrentProjects() {
return models.values();
}
@Override
public boolean refresh() {
data = new WorkspaceData();
if (super.refresh()) {
for (Project project : getCurrentProjects()) {
project.propertiesChanged();
}
return true;
}
return false;
}
@Override
public void propertiesChanged() {
data = new WorkspaceData();
File extDir = new File(getBuildDir(), EXT);
File[] extensions = extDir.listFiles();
if (extensions != null) {
for (File extension : extensions) {
String extensionName = extension.getName();
if (extensionName.endsWith(".bnd")) {
extensionName = extensionName.substring(0, extensionName.length() - ".bnd".length());
try {
doIncludeFile(extension, false, getProperties(), "ext." + extensionName);
} catch (Exception e) {
exception(e, "PropertiesChanged: %s", e);
}
}
}
}
super.propertiesChanged();
}
public String _workspace(@SuppressWarnings("unused") String args[]) {
return getBase().getAbsolutePath();
}
public void addCommand(String menu, Action action) {
commands.put(menu, action);
}
public void removeCommand(String menu) {
commands.remove(menu);
}
public void fillActions(Map all) {
all.putAll(commands);
}
public Collection getAllProjects() throws Exception {
List projects = new ArrayList();
for (File file : getBase().listFiles()) {
if (new File(file, Project.BNDFILE).isFile()) {
Project p = getProject(file.getAbsoluteFile().getName());
if (p != null) {
projects.add(p);
}
}
}
return projects;
}
/**
* Inform any listeners that we changed a file (created/deleted/changed).
*
* @param f The changed file
*/
public void changedFile(File f) {
List listeners = getPlugins(BndListener.class);
for (BndListener l : listeners)
try {
hasBndListeners = true;
l.changed(f);
} catch (Exception e) {
e.printStackTrace();
}
}
public void bracket(boolean begin) {
List listeners = getPlugins(BndListener.class);
for (BndListener l : listeners)
try {
if (begin)
l.begin();
else
l.end();
} catch (Exception e) {
// who cares?
}
}
public void signal(Reporter reporter) {
if (signalBusy.get() != null)
return;
signalBusy.set(reporter);
try {
List listeners = getPlugins(BndListener.class);
for (BndListener l : listeners)
try {
l.signal(this);
} catch (Exception e) {
// who cares?
}
} catch (Exception e) {
// Ignore
} finally {
signalBusy.set(null);
}
}
@Override
public void signal() {
signal(this);
}
class CachedFileRepo extends FileRepo {
final Lock lock = new ReentrantLock();
boolean inited;
CachedFileRepo() {
super(BND_CACHE_REPONAME, getCache(BND_CACHE_REPONAME), false);
}
@Override
public String toString() {
return BND_CACHE_REPONAME;
}
@Override
protected boolean init() throws Exception {
if (lock.tryLock(50, TimeUnit.SECONDS) == false)
throw new TimeLimitExceededException("Cached File Repo is locked and can't acquire it");
try {
if (super.init()) {
inited = true;
if (!root.exists() && !root.mkdirs()) {
throw new IOException("Could not create cache directory " + root);
}
if (!root.isDirectory())
throw new IllegalArgumentException("Cache directory " + root + " not a directory");
InputStream in = getClass().getResourceAsStream(EMBEDDED_REPO);
if (in != null)
unzip(in, root);
else {
// We may be in unit test, look for
// biz.aQute.bnd.embedded-repo.jar on the
// classpath
StringTokenizer classPathTokenizer = new StringTokenizer(
System.getProperty("java.class.path", ""), File.pathSeparator);
while (classPathTokenizer.hasMoreTokens()) {
String classPathEntry = classPathTokenizer.nextToken().trim();
if (EMBEDDED_REPO_TESTING_PATTERN.matcher(classPathEntry).matches()) {
in = new FileInputStream(classPathEntry);
unzip(in, root);
return true;
}
}
error("Couldn't find biz.aQute.bnd.embedded-repo on the classpath");
return false;
}
return true;
} else
return false;
} finally {
lock.unlock();
}
}
private void unzip(InputStream in, File dir) throws Exception {
try (JarInputStream jin = new JarInputStream(in)) {
byte[] data = new byte[BUFFER_SIZE];
for (JarEntry jentry = jin.getNextJarEntry(); jentry != null; jentry = jin.getNextJarEntry()) {
if (jentry.isDirectory()) {
continue;
}
String jentryName = jentry.getName();
if (jentryName.startsWith("META-INF/")) {
continue;
}
File dest = getFile(dir, jentryName);
long modifiedTime = ZipUtil.getModifiedTime(jentry);
if (!dest.isFile() || dest.lastModified() < modifiedTime || modifiedTime <= 0) {
File dp = dest.getParentFile();
if (!dp.exists() && !dp.mkdirs()) {
throw new IOException("Could not create directory " + dp);
}
try (FileOutputStream out = new FileOutputStream(dest)) {
for (int size = jin.read(data); size > 0; size = jin.read(data)) {
out.write(data, 0, size);
}
}
}
}
} finally {
in.close();
}
}
}
public void syncCache() throws Exception {
CachedFileRepo cf = new CachedFileRepo();
cf.init();
cf.close();
}
public List getRepositories() throws Exception {
if (data.repositories == null) {
data.repositories = getPlugins(RepositoryPlugin.class);
for (RepositoryPlugin repo : data.repositories) {
if (repo instanceof Prepare) {
((Prepare) repo).prepare();
}
}
}
return data.repositories;
}
public Collection getBuildOrder() throws Exception {
List result = new ArrayList();
for (Project project : getAllProjects()) {
Collection dependsOn = project.getDependson();
getBuildOrder(dependsOn, result);
if (!result.contains(project)) {
result.add(project);
}
}
return result;
}
private void getBuildOrder(Collection dependsOn, List result) throws Exception {
for (Project project : dependsOn) {
Collection subProjects = project.getDependson();
for (Project subProject : subProjects) {
if (!result.contains(subProject)) {
result.add(subProject);
}
}
if (!result.contains(project)) {
result.add(project);
}
}
}
public static Workspace getWorkspace(String path) throws Exception {
File file = IO.getFile(new File(""), path);
return getWorkspace(file);
}
public Maven getMaven() {
return maven;
}
@Override
protected void setTypeSpecificPlugins(Set