aQute.bnd.build.Workspace Maven / Gradle / Ivy
The newest version!
package aQute.bnd.build;
import static java.util.stream.Collectors.toList;
import java.io.File;
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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
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.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
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 java.util.stream.Stream;
import javax.naming.TimeLimitExceededException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.annotation.plugin.BndPlugin;
import aQute.bnd.connection.settings.ConnectionSettings;
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.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.io.NonClosingInputStream;
import aQute.lib.settings.Settings;
import aQute.lib.strings.Strings;
import aQute.lib.utf8properties.UTF8Properties;
import aQute.libg.uri.URIUtil;
import aQute.service.reporter.Reporter;
public class Workspace extends Processor {
private final static Logger logger = LoggerFactory.getLogger(Workspace.class);
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.repository.osgi.OSGiRepository";
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;
private final Map models = new HashMap<>();
private final Set modelsUnderConstruction = new HashSet<>();
final Map commands = newMap();
final Maven maven = new Maven(
Processor.getExecutor());
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();
try (InputStream propStream = Workspace.class.getResourceAsStream("defaults.bnd")) {
if (propStream != null) {
props.load(propStream);
} else {
System.err.println("Cannot load defaults");
}
} catch (IOException e) {
throw new IllegalArgumentException("Unable to load bnd defaults.", e);
}
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();
IO.mkdirs(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) {
projectDir = projectDir.getAbsoluteFile();
assert projectDir.isDirectory();
if (getBase().equals(projectDir.getParentFile())) {
return getProject(projectDir.getName());
}
return null;
}
public Project getProject(String bsn) {
synchronized (models) {
Project project = models.get(bsn);
if (project != null) {
return project;
}
if (!modelsUnderConstruction.add(bsn)) {
return null;
}
try {
File projectDir = getFile(bsn);
File bnd = getFile(projectDir, Project.BNDFILE);
if (!bnd.isFile()) {
return null;
}
project = new Project(this, projectDir);
if (!project.isValid()) {
return null;
}
models.put(bsn, project);
return project;
} finally {
modelsUnderConstruction.remove(bsn);
}
}
}
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 IO.absolutePath(getBase());
}
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 {
try (Stream paths = Files.list(getBase().toPath())) {
List projects = paths
.filter(p -> Files.isDirectory(p) && Files.isRegularFile(p.resolve(Project.BNDPATH)))
.map(p -> getProject(p.getFileName()
.toString()))
.filter(Objects::nonNull)
.collect(toList());
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 {
l.changed(f);
} catch (Exception e) {
logger.debug("Exception in a BndListener changedFile method call", e);
}
}
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) {
if (begin)
logger.debug("Exception in a BndListener begin method call", e);
else
logger.debug("Exception in a BndListener end method call", e);
}
}
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) {
logger.debug("Exception in a BndListener signal method call", e);
}
} 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
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;
IO.mkdirs(root);
if (!root.isDirectory())
throw new IllegalArgumentException("Cache directory " + root + " not a directory");
try (InputStream in = getClass().getResourceAsStream(EMBEDDED_REPO)) {
if (in != null) {
unzip(in, root.toPath());
return true;
}
}
// 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()) {
try (InputStream in = IO.stream(Paths.get(classPathEntry))) {
unzip(in, root.toPath());
return true;
}
}
}
error("Couldn't find biz.aQute.bnd.embedded-repo on the classpath");
return false;
} else
return false;
} finally {
lock.unlock();
}
}
private void unzip(InputStream in, Path dir) throws Exception {
try (JarInputStream jin = new JarInputStream(in)) {
FileTime modifiedTime = FileTime.fromMillis(System.currentTimeMillis());
Manifest manifest = jin.getManifest();
if (manifest != null) {
String timestamp = manifest.getMainAttributes()
.getValue("Timestamp");
if (timestamp != null) {
// truncate to seconds since file system can discard
// milliseconds
long seconds = TimeUnit.MILLISECONDS.toSeconds(Long.parseLong(timestamp));
modifiedTime = FileTime.from(seconds, TimeUnit.SECONDS);
}
}
for (JarEntry jentry = jin.getNextJarEntry(); jentry != null; jentry = jin.getNextJarEntry()) {
if (jentry.isDirectory()) {
continue;
}
String jentryName = jentry.getName();
if (jentryName.startsWith("META-INF/")) {
continue;
}
Path dest = dir.resolve(jentryName);
if (!Files.isRegularFile(dest) || Files.getLastModifiedTime(dest)
.compareTo(modifiedTime) < 0) {
IO.mkdirs(dest.getParent());
IO.copy(new NonClosingInputStream(jin), dest);
Files.setLastModifiedTime(dest, modifiedTime);
}
}
}
}
}
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 {
Set result = new LinkedHashSet<>();
for (Project project : getAllProjects()) {
Collection dependsOn = project.getDependson();
getBuildOrder(dependsOn, result);
result.add(project);
}
return result;
}
private void getBuildOrder(Collection dependsOn, Set result) throws Exception {
for (Project project : dependsOn) {
Collection subProjects = project.getDependson();
for (Project subProject : subProjects) {
result.add(subProject);
}
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