| package aQute.bnd.build; |
| |
| import java.io.*; |
| import java.lang.reflect.*; |
| import java.net.*; |
| import java.util.*; |
| import java.util.Map.Entry; |
| import java.util.concurrent.locks.*; |
| import java.util.jar.*; |
| import java.util.regex.*; |
| |
| import aQute.bnd.header.*; |
| import aQute.bnd.help.*; |
| import aQute.bnd.maven.support.*; |
| import aQute.bnd.osgi.*; |
| import aQute.bnd.osgi.eclipse.*; |
| import aQute.bnd.service.*; |
| import aQute.bnd.service.RepositoryPlugin.PutResult; |
| import aQute.bnd.service.action.*; |
| import aQute.bnd.version.*; |
| import aQute.lib.io.*; |
| import aQute.libg.generics.*; |
| import aQute.libg.reporter.*; |
| import aQute.libg.sed.*; |
| |
| /** |
| * This class is NOT threadsafe |
| */ |
| |
| public class Project extends Processor { |
| |
| final static Pattern VERSION_ANNOTATION = Pattern.compile("@\\s*(:?aQute\\.bnd\\.annotation\\.)?Version\\s*\\(\\s*(:?value\\s*=\\s*)?\"(\\d+(:?\\.\\d+(:?\\.\\d+(:?\\.[\\d\\w-_]+)?)?)?)\"\\s*\\)"); |
| final static String DEFAULT_ACTIONS = "build; label='Build', test; label='Test', run; label='Run', clean; label='Clean', release; label='Release', refreshAll; label=Refresh, deploy;label=Deploy"; |
| public final static String BNDFILE = "bnd.bnd"; |
| public final static String BNDCNF = "cnf"; |
| final Workspace workspace; |
| boolean preparedPaths; |
| final Collection<Project> dependson = new LinkedHashSet<Project>(); |
| final Collection<Container> classpath = new LinkedHashSet<Container>(); |
| final Collection<Container> buildpath = new LinkedHashSet<Container>(); |
| final Collection<Container> testpath = new LinkedHashSet<Container>(); |
| final Collection<Container> runpath = new LinkedHashSet<Container>(); |
| final Collection<Container> runbundles = new LinkedHashSet<Container>(); |
| File runstorage; |
| final Collection<File> sourcepath = new LinkedHashSet<File>(); |
| final Collection<File> allsourcepath = new LinkedHashSet<File>(); |
| final Collection<Container> bootclasspath = new LinkedHashSet<Container>(); |
| final Lock lock = new ReentrantLock(true); |
| volatile String lockingReason; |
| volatile Thread lockingThread; |
| File output; |
| File target; |
| boolean inPrepare; |
| int revision; |
| File files[]; |
| static List<Project> trail = new ArrayList<Project>(); |
| boolean delayRunDependencies = false; |
| final ProjectMessages msgs = ReporterMessages.base(this, ProjectMessages.class); |
| |
| public Project(Workspace workspace, File projectDir, File buildFile) throws Exception { |
| super(workspace); |
| this.workspace = workspace; |
| setFileMustExist(false); |
| setProperties(buildFile); |
| assert workspace != null; |
| // For backward compatibility reasons, we also read |
| readBuildProperties(); |
| } |
| |
| public Project(Workspace workspace, File buildDir) throws Exception { |
| this(workspace, buildDir, new File(buildDir, BNDFILE)); |
| } |
| |
| private void readBuildProperties() throws Exception { |
| try { |
| File f = getFile("build.properties"); |
| if (f.isFile()) { |
| Properties p = loadProperties(f); |
| for (Enumeration< ? > e = p.propertyNames(); e.hasMoreElements();) { |
| String key = (String) e.nextElement(); |
| String newkey = key; |
| if (key.indexOf('$') >= 0) { |
| newkey = getReplacer().process(key); |
| } |
| setProperty(newkey, p.getProperty(key)); |
| } |
| } |
| } |
| catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| public static Project getUnparented(File propertiesFile) throws Exception { |
| propertiesFile = propertiesFile.getAbsoluteFile(); |
| Workspace workspace = new Workspace(propertiesFile.getParentFile()); |
| Project project = new Project(workspace, propertiesFile.getParentFile()); |
| project.setProperties(propertiesFile); |
| project.setFileMustExist(true); |
| return project; |
| } |
| |
| public synchronized boolean isValid() { |
| return getBase().isDirectory() && getPropertiesFile().isFile(); |
| } |
| |
| /** |
| * Return a new builder that is nicely setup for this project. Please close |
| * this builder after use. |
| * |
| * @param parent |
| * The project builder to use as parent, use this project if null |
| * @return |
| * @throws Exception |
| */ |
| public synchronized ProjectBuilder getBuilder(ProjectBuilder parent) throws Exception { |
| |
| ProjectBuilder builder; |
| |
| if (parent == null) |
| builder = new ProjectBuilder(this); |
| else |
| builder = new ProjectBuilder(parent); |
| |
| builder.setBase(getBase()); |
| builder.setPedantic(isPedantic()); |
| builder.setTrace(isTrace()); |
| return builder; |
| } |
| |
| public synchronized int getChanged() { |
| return revision; |
| } |
| |
| /* |
| * Indicate a change in the external world that affects our build. This will |
| * clear any cached results. |
| */ |
| public synchronized void setChanged() { |
| // if (refresh()) { |
| preparedPaths = false; |
| files = null; |
| revision++; |
| // } |
| } |
| |
| public Workspace getWorkspace() { |
| return workspace; |
| } |
| |
| @Override |
| public String toString() { |
| return getBase().getName(); |
| } |
| |
| /** |
| * Set up all the paths |
| */ |
| |
| public synchronized void prepare() throws Exception { |
| if (!isValid()) { |
| warning("Invalid project attempts to prepare: %s", this); |
| return; |
| } |
| |
| if (inPrepare) |
| throw new CircularDependencyException(trail.toString() + "," + this); |
| |
| trail.add(this); |
| try { |
| if (!preparedPaths) { |
| inPrepare = true; |
| try { |
| dependson.clear(); |
| buildpath.clear(); |
| sourcepath.clear(); |
| allsourcepath.clear(); |
| bootclasspath.clear(); |
| testpath.clear(); |
| runpath.clear(); |
| runbundles.clear(); |
| |
| // We use a builder to construct all the properties for |
| // use. |
| setProperty("basedir", getBase().getAbsolutePath()); |
| |
| // If a bnd.bnd file exists, we read it. |
| // Otherwise, we just do the build properties. |
| if (!getPropertiesFile().isFile() && new File(getBase(), ".classpath").isFile()) { |
| // Get our Eclipse info, we might depend on other |
| // projects |
| // though ideally this should become empty and void |
| doEclipseClasspath(); |
| } |
| |
| // Calculate our source directory |
| |
| File src = getSrc(); |
| if (src.isDirectory()) { |
| sourcepath.add(src); |
| allsourcepath.add(src); |
| } else |
| sourcepath.add(getBase()); |
| |
| // Set default bin directory |
| output = getOutput0(); |
| if (!output.exists()) { |
| if (!output.mkdirs()) { |
| throw new IOException("Could not create directory " + output); |
| } |
| getWorkspace().changedFile(output); |
| } |
| if (!output.isDirectory()) |
| msgs.NoOutputDirectory_(output); |
| else { |
| Container c = new Container(this, output); |
| if (!buildpath.contains(c)) |
| buildpath.add(c); |
| } |
| |
| // 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. |
| |
| // dependencies.add( getWorkspace().getProject("cnf")); |
| |
| String dp = getProperty(Constants.DEPENDSON); |
| Set<String> requiredProjectNames = new LinkedHashSet<String>(new Parameters(dp).keySet()); |
| |
| //Allow DependencyConstributors to modify requiredProjectNames |
| List<DependencyContributor> dcs = getPlugins(DependencyContributor.class); |
| for (DependencyContributor dc : dcs) |
| dc.addDependencies(this, requiredProjectNames); |
| |
| Instructions is = new Instructions(requiredProjectNames); |
| |
| Set<Instruction> unused = new HashSet<Instruction>(); |
| Collection<Project> projects = getWorkspace().getAllProjects(); |
| Collection<Project> dependencies = 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, dependencies, parseBuildpath(), bootclasspath); |
| doPath(testpath, dependencies, parseTestpath(), bootclasspath); |
| if (!delayRunDependencies) { |
| doPath(runpath, dependencies, parseRunpath(), null); |
| doPath(runbundles, dependencies, parseRunbundles(), null); |
| } |
| |
| // We now know all dependent projects. But we also depend |
| // on whatever those projects depend on. This creates an |
| // ordered list without any duplicates. This of course |
| // assumes |
| // that there is no circularity. However, this is checked |
| // by the inPrepare flag, will throw an exception if we |
| // are circular. |
| |
| Set<Project> done = new HashSet<Project>(); |
| done.add(this); |
| allsourcepath.addAll(sourcepath); |
| |
| for (Project project : dependencies) |
| project.traverse(dependson, done); |
| |
| for (Project project : dependson) { |
| allsourcepath.addAll(project.getSourcePath()); |
| } |
| if (isOk()) |
| preparedPaths = true; |
| } |
| finally { |
| inPrepare = false; |
| } |
| } |
| } |
| finally { |
| trail.remove(this); |
| } |
| } |
| |
| /** |
| * @return |
| */ |
| private File getOutput0() { |
| return getFile(getProperty("bin", "bin")).getAbsoluteFile(); |
| } |
| |
| /** |
| * |
| */ |
| private File getTarget0() throws IOException { |
| File target = getFile(getProperty("target", "generated")); |
| if (!target.exists()) { |
| if (!target.mkdirs()) { |
| throw new IOException("Could not create directory " + target); |
| } |
| getWorkspace().changedFile(target); |
| } |
| return target; |
| } |
| |
| public File getSrc() { |
| return new File(getBase(), getProperty("src", "src")); |
| } |
| |
| private void traverse(Collection<Project> dependencies, Set<Project> visited) throws Exception { |
| if (visited.contains(this)) |
| return; |
| |
| visited.add(this); |
| |
| for (Project project : getDependson()) |
| project.traverse(dependencies, visited); |
| |
| dependencies.add(this); |
| } |
| |
| /** |
| * Iterate over the entries and place the projects on the projects list and |
| * all the files of the entries on the resultpath. |
| * |
| * @param resultpath |
| * The list that gets all the files |
| * @param projects |
| * The list that gets any projects that are entries |
| * @param entries |
| * The input list of classpath entries |
| */ |
| private void doPath(Collection<Container> resultpath, Collection<Project> projects, Collection<Container> entries, |
| Collection<Container> bootclasspath) { |
| for (Container cpe : entries) { |
| if (cpe.getError() != null) |
| error(cpe.getError()); |
| else { |
| if (cpe.getType() == Container.TYPE.PROJECT) { |
| projects.add(cpe.getProject()); |
| } |
| if (bootclasspath != null |
| && (cpe.getBundleSymbolicName().startsWith("ee.") || cpe.getAttributes().containsKey("boot"))) |
| bootclasspath.add(cpe); |
| else |
| resultpath.add(cpe); |
| } |
| } |
| } |
| |
| /** |
| * Parse the list of bundles that are a prerequisite to this project. |
| * Bundles are listed in repo specific names. So we just let our repo |
| * plugins iterate over the list of bundles and we get the highest version |
| * from them. |
| * |
| * @return |
| */ |
| |
| private List<Container> parseBuildpath() throws Exception { |
| List<Container> bundles = getBundles(Strategy.LOWEST, getProperty(Constants.BUILDPATH), Constants.BUILDPATH); |
| return bundles; |
| } |
| |
| private List<Container> parseRunpath() throws Exception { |
| return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNPATH), Constants.RUNPATH); |
| } |
| |
| private List<Container> parseRunbundles() throws Exception { |
| return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNBUNDLES), Constants.RUNBUNDLES); |
| } |
| |
| private List<Container> parseTestpath() throws Exception { |
| return getBundles(Strategy.HIGHEST, getProperty(Constants.TESTPATH), Constants.TESTPATH); |
| } |
| |
| /** |
| * Analyze the header and return a list of files that should be on the |
| * build, test or some other path. The list is assumed to be a list of bsns |
| * with a version specification. The special case of version=project |
| * indicates there is a project in the same workspace. The path to the |
| * output directory is calculated. The default directory ${bin} can be |
| * overridden with the output attribute. |
| * |
| * @param strategy |
| * STRATEGY_LOWEST or STRATEGY_HIGHEST |
| * @param spec |
| * The header |
| * @return |
| */ |
| |
| public List<Container> getBundles(Strategy strategyx, String spec, String source) throws Exception { |
| List<Container> result = new ArrayList<Container>(); |
| Parameters bundles = new Parameters(spec); |
| |
| try { |
| for (Iterator<Entry<String,Attrs>> i = bundles.entrySet().iterator(); i.hasNext();) { |
| Entry<String,Attrs> entry = i.next(); |
| String bsn = removeDuplicateMarker(entry.getKey()); |
| Map<String,String> attrs = entry.getValue(); |
| |
| Container found = null; |
| |
| String versionRange = attrs.get("version"); |
| |
| if (versionRange != null) { |
| if (versionRange.equals("latest") || versionRange.equals("snapshot")) { |
| found = getBundle(bsn, versionRange, strategyx, attrs); |
| } |
| } |
| if (found == null) { |
| if (versionRange != null && (versionRange.equals("project") || versionRange.equals("latest"))) { |
| Project project = getWorkspace().getProject(bsn); |
| if (project != null && project.exists()) { |
| File f = project.getOutput(); |
| found = new Container(project, bsn, versionRange, Container.TYPE.PROJECT, f, null, attrs, null); |
| } else { |
| msgs.NoSuchProject(bsn, spec); |
| continue; |
| } |
| } else if (versionRange != null && versionRange.equals("file")) { |
| File f = getFile(bsn); |
| String error = null; |
| if (!f.exists()) |
| error = "File does not exist: " + f.getAbsolutePath(); |
| if (f.getName().endsWith(".lib")) { |
| found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f, error, attrs, null); |
| } else { |
| found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f, error, attrs, null); |
| } |
| } else { |
| found = getBundle(bsn, versionRange, strategyx, attrs); |
| } |
| } |
| |
| if (found != null) { |
| List<Container> libs = found.getMembers(); |
| for (Container cc : libs) { |
| if (result.contains(cc)) |
| warning("Multiple bundles with the same final URL: %s, dropped duplicate", cc); |
| else |
| 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); |
| warning("Can not find URL for bsn " + bsn); |
| } |
| } |
| } |
| catch (CircularDependencyException e) { |
| String message = e.getMessage(); |
| if (source != null) |
| message = String.format("%s (from property: %s)", message, source); |
| 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<Container> getBundles(Strategy strategy, String spec) throws Exception { |
| return getBundles(strategy, spec, null); |
| } |
| |
| static void mergeNames(String names, Set<String> set) { |
| StringTokenizer tokenizer = new StringTokenizer(names, ","); |
| while (tokenizer.hasMoreTokens()) |
| set.add(tokenizer.nextToken().trim()); |
| } |
| |
| static String flatten(Set<String> names) { |
| StringBuilder builder = new StringBuilder(); |
| boolean first = true; |
| for (String name : names) { |
| if (!first) |
| builder.append(','); |
| builder.append(name); |
| first = false; |
| } |
| return builder.toString(); |
| } |
| |
| static void addToPackageList(Container container, String newPackageNames) { |
| Set<String> merged = new HashSet<String>(); |
| |
| String packageListStr = container.attributes.get("packages"); |
| if (packageListStr != null) |
| mergeNames(packageListStr, merged); |
| if (newPackageNames != null) |
| mergeNames(newPackageNames, merged); |
| |
| container.putAttribute("packages", flatten(merged)); |
| } |
| |
| /** |
| * The user selected pom in a path. This will place the pom as well as its |
| * dependencies on the list |
| * |
| * @param strategyx |
| * the strategy to use. |
| * @param result |
| * The list of result containers |
| * @param attrs |
| * The attributes |
| * @throws Exception |
| * anything goes wrong |
| */ |
| public void doMavenPom(Strategy strategyx, List<Container> result, String action) throws Exception { |
| File pomFile = getFile("pom.xml"); |
| if (!pomFile.isFile()) |
| msgs.MissingPom(); |
| else { |
| ProjectPom pom = getWorkspace().getMaven().createProjectModel(pomFile); |
| if (action == null) |
| action = "compile"; |
| Pom.Scope act = Pom.Scope.valueOf(action); |
| Set<Pom> dependencies = pom.getDependencies(act); |
| for (Pom sub : dependencies) { |
| File artifact = sub.getArtifact(); |
| Container container = new Container(artifact, null); |
| result.add(container); |
| } |
| } |
| } |
| |
| public Collection<Project> getDependson() throws Exception { |
| prepare(); |
| return dependson; |
| } |
| |
| public Collection<Container> getBuildpath() throws Exception { |
| prepare(); |
| return buildpath; |
| } |
| |
| public Collection<Container> getTestpath() throws Exception { |
| prepare(); |
| return testpath; |
| } |
| |
| /** |
| * Handle dependencies for paths that are calculated on demand. |
| * |
| * @param testpath2 |
| * @param parseTestpath |
| */ |
| private void justInTime(Collection<Container> path, List<Container> entries) { |
| if (delayRunDependencies && path.isEmpty()) |
| doPath(path, dependson, entries, null); |
| } |
| |
| public Collection<Container> getRunpath() throws Exception { |
| prepare(); |
| justInTime(runpath, parseRunpath()); |
| return runpath; |
| } |
| |
| public Collection<Container> getRunbundles() throws Exception { |
| prepare(); |
| justInTime(runbundles, parseRunbundles()); |
| return runbundles; |
| } |
| |
| public File getRunStorage() throws Exception { |
| prepare(); |
| return runstorage; |
| } |
| |
| public boolean getRunBuilds() { |
| boolean result; |
| String runBuildsStr = getProperty(Constants.RUNBUILDS); |
| if (runBuildsStr == null) |
| result = !getPropertiesFile().getName().toLowerCase().endsWith(Constants.DEFAULT_BNDRUN_EXTENSION); |
| else |
| result = Boolean.parseBoolean(runBuildsStr); |
| return result; |
| } |
| |
| public Collection<File> getSourcePath() throws Exception { |
| prepare(); |
| return sourcepath; |
| } |
| |
| public Collection<File> getAllsourcepath() throws Exception { |
| prepare(); |
| return allsourcepath; |
| } |
| |
| public Collection<Container> getBootclasspath() throws Exception { |
| prepare(); |
| return bootclasspath; |
| } |
| |
| public File getOutput() throws Exception { |
| prepare(); |
| return output; |
| } |
| |
| private void doEclipseClasspath() throws Exception { |
| EclipseClasspath eclipse = new EclipseClasspath(this, getWorkspace().getBase(), getBase()); |
| eclipse.setRecurse(false); |
| |
| // We get the file directories but in this case we need |
| // to tell ant that the project names |
| for (File dependent : eclipse.getDependents()) { |
| Project required = workspace.getProject(dependent.getName()); |
| dependson.add(required); |
| } |
| for (File f : eclipse.getClasspath()) { |
| buildpath.add(new Container(f, null)); |
| } |
| for (File f : eclipse.getBootclasspath()) { |
| bootclasspath.add(new Container(f, null)); |
| } |
| sourcepath.addAll(eclipse.getSourcepath()); |
| allsourcepath.addAll(eclipse.getAllSources()); |
| output = eclipse.getOutput(); |
| } |
| |
| public String _p_dependson(String args[]) throws Exception { |
| return list(args, toFiles(getDependson())); |
| } |
| |
| private Collection< ? > toFiles(Collection<Project> projects) { |
| List<File> files = new ArrayList<File>(); |
| for (Project p : projects) { |
| files.add(p.getBase()); |
| } |
| return files; |
| } |
| |
| public String _p_buildpath(String args[]) throws Exception { |
| return list(args, getBuildpath()); |
| } |
| |
| public String _p_testpath(String args[]) throws Exception { |
| return list(args, getRunpath()); |
| } |
| |
| public String _p_sourcepath(String args[]) throws Exception { |
| return list(args, getSourcePath()); |
| } |
| |
| public String _p_allsourcepath(String args[]) throws Exception { |
| return list(args, getAllsourcepath()); |
| } |
| |
| public String _p_bootclasspath(String args[]) throws Exception { |
| return list(args, getBootclasspath()); |
| } |
| |
| public String _p_output(String args[]) throws Exception { |
| if (args.length != 1) |
| throw new IllegalArgumentException("${output} should not have arguments"); |
| return getOutput().getAbsolutePath(); |
| } |
| |
| private String list(String[] args, Collection< ? > list) { |
| if (args.length > 3) |
| throw new IllegalArgumentException("${" + args[0] |
| + "[;<separator>]} can only take a separator as argument, has " + Arrays.toString(args)); |
| |
| String separator = ","; |
| |
| if (args.length == 2) { |
| separator = args[1]; |
| } |
| |
| return join(list, separator); |
| } |
| |
| @Override |
| protected Object[] getMacroDomains() { |
| return new Object[] { |
| workspace |
| }; |
| } |
| |
| public File release(String jarName, InputStream jarStream) throws Exception { |
| String name = getProperty(Constants.RELEASEREPO); |
| return release(name, jarName, jarStream); |
| } |
| |
| /** |
| * Release |
| * |
| * @param name |
| * The repository name |
| * @param jarName |
| * @param jarStream |
| * @return |
| * @throws Exception |
| */ |
| public File release(String name, String jarName, InputStream jarStream) throws Exception { |
| trace("release %s", name); |
| List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class); |
| RepositoryPlugin rp = null; |
| for (RepositoryPlugin plugin : plugins) { |
| if (!plugin.canWrite()) { |
| continue; |
| } |
| if (name == null) { |
| rp = plugin; |
| break; |
| } else if (name.equals(plugin.getName())) { |
| rp = plugin; |
| break; |
| } |
| } |
| |
| if (rp != null) { |
| try { |
| PutResult r = rp.put(jarStream, new RepositoryPlugin.PutOptions()); |
| trace("Released %s to %s in repository %s", jarName, r.artifact, rp); |
| } |
| catch (Exception e) { |
| msgs.Release_Into_Exception_(jarName, rp, e); |
| } |
| } else if (name == null) |
| msgs.NoNameForReleaseRepository(); |
| else |
| msgs.ReleaseRepository_NotFoundIn_(name, plugins); |
| |
| return null; |
| |
| } |
| |
| public void release(boolean test) throws Exception { |
| String name = getProperty(Constants.RELEASEREPO); |
| release(name, test); |
| } |
| |
| /** |
| * Release |
| * |
| * @param name |
| * The respository name |
| * @param test |
| * Run testcases |
| * @throws Exception |
| */ |
| public void release(String name, boolean test) throws Exception { |
| trace("release"); |
| File[] jars = build(test); |
| // If build fails jars will be null |
| if (jars == null) { |
| trace("no jars being build"); |
| return; |
| } |
| trace("build ", Arrays.toString(jars)); |
| for (File jar : jars) { |
| release(name, jar.getName(), new BufferedInputStream(new FileInputStream(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 lowest |
| * set to LOWEST or HIGHEST |
| * @return the file object that points to the bundle or null if not found |
| * @throws Exception |
| * when something goes wrong |
| */ |
| |
| public Container getBundle(String bsn, String range, Strategy strategy, Map<String,String> attrs) throws Exception { |
| |
| if (range == null) |
| range = "0"; |
| |
| if ("snapshot".equals(range)) { |
| return getBundleFromProject(bsn, attrs); |
| } |
| |
| Strategy useStrategy = strategy; |
| |
| if ("latest".equals(range)) { |
| Container c = getBundleFromProject(bsn, attrs); |
| if (c != null) |
| return c; |
| |
| useStrategy = Strategy.HIGHEST; |
| } |
| |
| useStrategy = overrideStrategy(attrs, useStrategy); |
| |
| List<RepositoryPlugin> 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 = "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<Version,RepositoryPlugin> versions = new TreeMap<Version,RepositoryPlugin>(); |
| for (RepositoryPlugin plugin : plugins) { |
| try { |
| SortedSet<Version> 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); |
| } |
| } |
| } |
| |
| // 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); |
| 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 |
| * @return |
| */ |
| protected Strategy overrideStrategy(Map<String,String> 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; |
| } |
| |
| /** |
| * @param bsn |
| * @param range |
| * @param attrs |
| * @param result |
| * @return |
| */ |
| protected Container toContainer(String bsn, String range, Map<String,String> 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 |
| * @return |
| * @throws Exception |
| */ |
| private Container getBundleFromProject(String bsn, Map<String,String> attrs) throws Exception { |
| String pname = bsn; |
| while (true) { |
| Project p = getWorkspace().getProject(pname); |
| if (p != null && p.isValid()) { |
| Container c = p.getDeliverable(bsn, attrs); |
| return c; |
| } |
| |
| int n = pname.lastIndexOf('.'); |
| if (n <= 0) |
| return null; |
| pname = pname.substring(0, n); |
| } |
| } |
| |
| /** |
| * Deploy the file (which must be a bundle) into the repository. |
| * |
| * @param name |
| * The repository name |
| * @param file |
| * bundle |
| */ |
| public void deploy(String name, File file) throws Exception { |
| List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class); |
| |
| RepositoryPlugin rp = null; |
| for (RepositoryPlugin plugin : plugins) { |
| if (!plugin.canWrite()) { |
| continue; |
| } |
| if (name == null) { |
| rp = plugin; |
| break; |
| } else if (name.equals(plugin.getName())) { |
| rp = plugin; |
| break; |
| } |
| } |
| |
| if (rp != null) { |
| try { |
| rp.put(new BufferedInputStream(new FileInputStream(file)), new RepositoryPlugin.PutOptions()); |
| return; |
| } |
| catch (Exception e) { |
| msgs.DeployingFile_On_Exception_(file, rp.getName(), e); |
| } |
| return; |
| } |
| trace("No repo found " + file); |
| throw new IllegalArgumentException("No repository found for " + file); |
| } |
| |
| /** |
| * Deploy the file (which must be a bundle) into the repository. |
| * |
| * @param file |
| * bundle |
| */ |
| public void deploy(File file) throws Exception { |
| String name = getProperty(Constants.DEPLOYREPO); |
| deploy(name, file); |
| } |
| |
| /** |
| * Deploy the current project to a repository |
| * |
| * @throws Exception |
| */ |
| public void deploy() throws Exception { |
| Parameters deploy = new Parameters(getProperty(DEPLOY)); |
| 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)) { |
| trace("Deploying %s to: %s", output.getName(), d); |
| try { |
| if (d.deploy(this, output.getName(), new BufferedInputStream(new FileInputStream(output)))) |
| trace("deployed %s successfully to %s", output, d); |
| } |
| catch (Exception e) { |
| msgs.Deploying(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Macro access to the repository ${repo;<bsn>[;<version>[;<low|high>]]} |
| */ |
| |
| static String _repoHelp = "${repo ';'<bsn> [ ; <version> [; ('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<String> parts = split(bsns); |
| List<String> paths = new ArrayList<String>(); |
| |
| for (String bsn : parts) { |
| Container container = getBundle(bsn, version, strategy, null); |
| add(paths, container); |
| } |
| return join(paths); |
| } |
| |
| private void add(List<String> paths, Container container) throws Exception { |
| if (container.getType() == Container.TYPE.LIBRARY) { |
| List<Container> members = container.getMembers(); |
| for (Container sub : members) { |
| add(paths, sub); |
| } |
| } else { |
| if (container.getError() == null) |
| paths.add(container.getFile().getAbsolutePath()); |
| else { |
| paths.add("<<${repo} = " + container.getBundleSymbolicName() + "-" + container.getVersion() + " : " |
| + container.getError() + ">>"); |
| |
| if (isPedantic()) { |
| warning("Could not expand repo path request: %s ", container); |
| } |
| } |
| |
| } |
| } |
| |
| public File getTarget() throws Exception { |
| prepare(); |
| return target; |
| } |
| |
| /** |
| * This is the external method that will pre-build any dependencies if it is |
| * out of date. |
| * |
| * @param underTest |
| * @return |
| * @throws Exception |
| */ |
| public File[] build(boolean underTest) throws Exception { |
| if (isNoBundles()) |
| return null; |
| |
| if (getProperty("-nope") != null) { |
| warning("Please replace -nope with %s", NOBUNDLES); |
| return null; |
| } |
| |
| if (isStale()) { |
| trace("building " + this); |
| files = buildLocal(underTest); |
| } |
| |
| return files; |
| } |
| |
| /** |
| * Return the files |
| */ |
| |
| public File[] getFiles() { |
| return files; |
| } |
| |
| /** |
| * Check if this project needs building. This is defined as: |
| */ |
| public boolean isStale() throws Exception { |
| if (workspace.isOffline()) { |
| trace("working %s offline, so always stale", this); |
| return true; |
| } |
| |
| Set<Project> visited = new HashSet<Project>(); |
| return isStale(visited); |
| } |
| |
| boolean isStale(Set<Project> visited) throws Exception { |
| // When we do not generate anything ... |
| if (isNoBundles()) |
| return false; |
| |
| if (visited.contains(this)) { |
| msgs.CircularDependencyContext_Message_(this.getName(), visited.toString()); |
| return false; |
| } |
| |
| visited.add(this); |
| |
| long buildTime = 0; |
| |
| 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.isStale()) |
| return true; |
| |
| if (dependency.isNoBundles()) |
| continue; |
| |
| File[] deps = dependency.getBuildFiles(); |
| for (File f : deps) { |
| if (f.lastModified() >= buildTime) |
| 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. |
| * |
| * @return |
| */ |
| |
| public File[] getBuildFiles() throws Exception { |
| return getBuildFiles(true); |
| } |
| |
| public File[] getBuildFiles(boolean buildIfAbsent) throws Exception { |
| if (files != null) |
| return files; |
| |
| File f = new File(getTarget(), BUILDFILES); |
| if (f.isFile()) { |
| BufferedReader rdr = IO.reader(f); |
| try { |
| List<File> files = newList(); |
| for (String s = rdr.readLine(); s != null; s = rdr.readLine()) { |
| s = s.trim(); |
| File ff = new File(s); |
| if (!ff.isFile()) { |
| // 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(); |
| f.delete(); |
| break; |
| } |
| files.add(ff); |
| } |
| return this.files = files.toArray(new File[files.size()]); |
| } |
| finally { |
| rdr.close(); |
| } |
| } |
| if (buildIfAbsent) |
| return files = buildLocal(false); |
| return files = null; |
| } |
| |
| /** |
| * Build without doing any dependency checking. Make sure any dependent |
| * projects are built first. |
| * |
| * @param underTest |
| * @return |
| * @throws Exception |
| */ |
| public File[] buildLocal(boolean underTest) throws Exception { |
| if (isNoBundles()) |
| return null; |
| |
| File bfs = new File(getTarget(), BUILDFILES); |
| bfs.delete(); |
| |
| files = null; |
| ProjectBuilder builder = getBuilder(null); |
| try { |
| if (underTest) |
| builder.setProperty(Constants.UNDERTEST, "true"); |
| Jar jars[] = builder.builds(); |
| File[] files = new File[jars.length]; |
| |
| getInfo(builder); |
| |
| if (isOk()) { |
| this.files = files; |
| |
| for (int i = 0; i < jars.length; i++) { |
| Jar jar = jars[i]; |
| files[i] = saveBuild(jar); |
| } |
| |
| // Write out the filenames in the buildfiles file |
| // so we can get them later evenin another process |
| Writer fw = IO.writer(bfs); |
| try { |
| for (File f : files) { |
| fw.append(f.getAbsolutePath()); |
| fw.append("\n"); |
| } |
| } |
| finally { |
| fw.close(); |
| } |
| getWorkspace().changedFile(bfs); |
| return files; |
| } |
| return null; |
| } |
| finally { |
| builder.close(); |
| } |
| } |
| |
| /** |
| * Answer if this project does not have any output |
| * |
| * @return |
| */ |
| public boolean isNoBundles() { |
| return getProperty(NOBUNDLES) != null; |
| } |
| |
| public File saveBuild(Jar jar) throws Exception { |
| try { |
| String bsn = jar.getName(); |
| File f = getOutputFile(bsn); |
| String msg = ""; |
| if (!f.exists() || f.lastModified() < jar.lastModified()) { |
| reportNewer(f.lastModified(), jar); |
| f.delete(); |
| File fp = f.getParentFile(); |
| if (!fp.isDirectory()) { |
| if (!fp.exists() && !fp.mkdirs()) { |
| throw new IOException("Could not create directory " + fp); |
| } |
| } |
| jar.write(f); |
| |
| getWorkspace().changedFile(f); |
| } else { |
| msg = "(not modified since " + new Date(f.lastModified()) + ")"; |
| } |
| trace(jar.getName() + " (" + f.getName() + ") " + jar.getResources().size() + " " + msg); |
| return f; |
| } |
| finally { |
| jar.close(); |
| } |
| } |
| |
| public File getOutputFile(String bsn) throws Exception { |
| return new File(getTarget(), bsn + ".jar"); |
| } |
| |
| private void reportNewer(long lastModified, Jar jar) { |
| if (isTrue(getProperty(Constants.REPORTNEWER))) { |
| StringBuilder sb = new StringBuilder(); |
| String del = "Newer than " + new Date(lastModified); |
| for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) { |
| if (entry.getValue().lastModified() > lastModified) { |
| sb.append(del); |
| del = ", \n "; |
| sb.append(entry.getKey()); |
| } |
| } |
| if (sb.length() > 0) |
| warning(sb.toString()); |
| } |
| } |
| |
| /** |
| * Refresh if we are based on stale data. This also implies our workspace. |
| */ |
| @Override |
| public boolean refresh() { |
| boolean changed = false; |
| if (isCnf()) { |
| changed = workspace.refresh(); |
| } |
| return super.refresh() || changed; |
| } |
| |
| public boolean isCnf() { |
| return getBase().getName().equals(Workspace.CNFDIR); |
| } |
| |
| @Override |
| public void propertiesChanged() { |
| super.propertiesChanged(); |
| preparedPaths = false; |
| files = null; |
| |
| } |
| |
| public String getName() { |
| return getBase().getName(); |
| } |
| |
| public Map<String,Action> getActions() { |
| Map<String,Action> all = newMap(); |
| Map<String,Action> actions = newMap(); |
| fillActions(all); |
| getWorkspace().fillActions(all); |
| |
| for (Map.Entry<String,Action> action : all.entrySet()) { |
| String key = getReplacer().process(action.getKey()); |
| if (key != null && key.trim().length() != 0) |
| actions.put(key, action.getValue()); |
| } |
| return actions; |
| } |
| |
| public void fillActions(Map<String,Action> all) { |
| List<NamedAction> plugins = getPlugins(NamedAction.class); |
| for (NamedAction a : plugins) |
| all.put(a.getName(), a); |
| |
| Parameters actions = new Parameters(getProperty("-actions", DEFAULT_ACTIONS)); |
| for (Entry<String,Attrs> entry : actions.entrySet()) { |
| String key = Processor.removeDuplicateMarker(entry.getKey()); |
| Action action; |
| |
| if (entry.getValue().get("script") != null) { |
| // TODO check for the type |
| action = new ScriptAction(entry.getValue().get("type"), entry.getValue().get("script")); |
| } else { |
| action = new ReflectAction(key); |
| } |
| String label = entry.getValue().get("label"); |
| all.put(label.toLowerCase(), action); |
| } |
| } |
| |
| public void release() throws Exception { |
| release(false); |
| } |
| |
| /** |
| * Release. |
| * |
| * @param name |
| * The repository name |
| * @throws Exception |
| */ |
| public void release(String name) throws Exception { |
| release(name, false); |
| } |
| |
| public void clean() throws Exception { |
| File target = getTarget0(); |
| if (target.isDirectory() && target.getParentFile() != null) { |
| IO.delete(target); |
| if (!target.exists() && !target.mkdirs()) { |
| throw new IOException("Could not create directory " + target); |
| } |
| } |
| File output = getOutput0(); |
| if (getOutput().isDirectory()) |
| IO.delete(output); |
| if (!output.exists() && !output.mkdirs()) { |
| throw new IOException("Could not create directory " + output); |
| } |
| } |
| |
| public File[] build() throws Exception { |
| return build(false); |
| } |
| |
| public void run() throws Exception { |
| ProjectLauncher pl = getProjectLauncher(); |
| pl.setTrace(isTrace()); |
| pl.launch(); |
| } |
| |
| public void test() throws Exception { |
| clear(); |
| ProjectTester tester = getProjectTester(); |
| tester.setContinuous(isTrue(getProperty(Constants.TESTCONTINUOUS))); |
| tester.prepare(); |
| |
| if (!isOk()) { |
| return; |
| } |
| int errors = tester.test(); |
| if (errors == 0) { |
| System.err.println("No Errors"); |
| } else { |
| if (errors > 0) { |
| System.err.println(errors + " Error(s)"); |
| |
| } else |
| System.err.println("Error " + errors); |
| } |
| } |
| |
| /** |
| * This methods attempts to turn any jar into a valid jar. If this is a |
| * bundle with manifest, a manifest is added based on defaults. If it is a |
| * bundle, but not r4, we try to add the r4 headers. |
| * |
| * @param descriptor |
| * @param in |
| * @return |
| * @throws Exception |
| */ |
| public Jar getValidJar(File f) throws Exception { |
| Jar jar = new Jar(f); |
| return getValidJar(jar, f.getAbsolutePath()); |
| } |
| |
| public Jar getValidJar(URL url) throws Exception { |
| InputStream in = url.openStream(); |
| try { |
| Jar jar = new Jar(url.getFile().replace('/', '.'), in, System.currentTimeMillis()); |
| return getValidJar(jar, url.toString()); |
| } |
| finally { |
| in.close(); |
| } |
| } |
| |
| public Jar getValidJar(Jar jar, String id) throws Exception { |
| Manifest manifest = jar.getManifest(); |
| if (manifest == null) { |
| trace("Wrapping with all defaults"); |
| Builder b = new Builder(this); |
| b.addClasspath(jar); |
| b.setProperty("Bnd-Message", "Wrapped from " + id + "because lacked manifest"); |
| b.setProperty(Constants.EXPORT_PACKAGE, "*"); |
| b.setProperty(Constants.IMPORT_PACKAGE, "*;resolution:=optional"); |
| jar = b.build(); |
| } else if (manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION) == null) { |
| trace("Not a release 4 bundle, wrapping with manifest as source"); |
| Builder b = new Builder(this); |
| b.addClasspath(jar); |
| b.setProperty(Constants.PRIVATE_PACKAGE, "*"); |
| b.mergeManifest(manifest); |
| String imprts = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE); |
| if (imprts == null) |
| imprts = ""; |
| else |
| imprts += ","; |
| imprts += "*;resolution=optional"; |
| |
| b.setProperty(Constants.IMPORT_PACKAGE, imprts); |
| b.setProperty("Bnd-Message", "Wrapped from " + id + "because had incomplete manifest"); |
| jar = b.build(); |
| } |
| return jar; |
| } |
| |
| public String _project(@SuppressWarnings("unused") |
| String args[]) { |
| return getBase().getAbsolutePath(); |
| } |
| |
| /** |
| * 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 = "(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; |
| |
| trace("no version in bnd.bnd"); |
| |
| // Try the included filed in reverse order (last has highest |
| // priority) |
| List<File> included = getIncluded(); |
| if (included != null) { |
| List<File> copy = new ArrayList<File>(included); |
| Collections.reverse(copy); |
| |
| for (File file : copy) { |
| if (replace(file, pattern, replace)) { |
| trace("replaced version in file %s", file); |
| return; |
| } |
| } |
| } |
| trace("no version in included files"); |
| |
| boolean found = false; |
| |
| // Replace in all sub builders. |
| for (Builder sub : getSubBuilders()) { |
| found |= replace(sub.getPropertiesFile(), pattern, replace); |
| } |
| |
| if (!found) { |
| trace("no version in sub builders, add it to bnd.bnd"); |
| String bndfile = IO.collect(getPropertiesFile()); |
| bndfile += "\n# Added by by bump\nBundle-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() { |
| 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 Throwable { |
| Map<String,Action> actions = getActions(); |
| |
| Action a = actions.get(command); |
| if (a == null) |
| a = new ReflectAction(command); |
| |
| before(this, command); |
| try { |
| a.execute(this, command); |
| } |
| catch (Throwable t) { |
| after(this, command, t); |
| throw t; |
| } |
| } |
| |
| /** |
| * Run all before command plugins |
| */ |
| void before(@SuppressWarnings("unused") |
| Project p, String a) { |
| List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class); |
| for (CommandPlugin testPlugin : testPlugins) { |
| testPlugin.before(this, a); |
| } |
| } |
| |
| /** |
| * Run all after command plugins |
| */ |
| void after(@SuppressWarnings("unused") |
| Project p, String a, Throwable t) { |
| List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class); |
| for (int i = testPlugins.size() - 1; i >= 0; i--) { |
| testPlugins.get(i).after(this, a, t); |
| } |
| } |
| |
| public String _findfile(String args[]) { |
| File f = getFile(args[1]); |
| List<String> files = new ArrayList<String>(); |
| tree(files, f, "", new Instruction(args[2])); |
| return join(files); |
| } |
| |
| void tree(List<String> list, File current, String path, Instruction instr) { |
| if (path.length() > 0) |
| path = path + "/"; |
| |
| String subs[] = current.list(); |
| if (subs != null) { |
| for (String sub : subs) { |
| File f = new File(current, sub); |
| if (f.isFile()) { |
| if (instr.matches(sub) && !instr.isNegated()) |
| list.add(path + sub); |
| } else |
| tree(list, f, path + sub, instr); |
| } |
| } |
| } |
| |
| public void refreshAll() { |
| workspace.refresh(); |
| refresh(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void script(@SuppressWarnings("unused") |
| String type, String script) throws Exception { |
| // TODO check tyiping |
| List<Scripter> scripters = getPlugins(Scripter.class); |
| if (scripters.isEmpty()) { |
| msgs.NoScripters_(script); |
| return; |
| } |
| @SuppressWarnings("rawtypes") |
| Map x = getProperties(); |
| scripters.get(0).eval(x, new StringReader(script)); |
| } |
| |
| public String _repos(@SuppressWarnings("unused") |
| String args[]) throws Exception { |
| List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class); |
| List<String> names = new ArrayList<String>(); |
| for (RepositoryPlugin rp : repos) |
| names.add(rp.getName()); |
| return join(names, ", "); |
| } |
| |
| public String _help(String args[]) throws Exception { |
| if (args.length == 1) |
| return "Specify the option or header you want information for"; |
| |
| Syntax syntax = Syntax.HELP.get(args[1]); |
| if (syntax == null) |
| return "No help for " + args[1]; |
| |
| String what = null; |
| if (args.length > 2) |
| what = args[2]; |
| |
| if (what == null || what.equals("lead")) |
| return syntax.getLead(); |
| if (what.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<Container> getDeliverables() throws Exception { |
| List<Container> result = new ArrayList<Container>(); |
| Collection< ? extends Builder> builders = getSubBuilders(); |
| |
| for (Builder builder : builders) { |
| Container c = new Container(this, builder.getBsn(), builder.getVersion(), Container.TYPE.PROJECT, |
| getOutputFile(builder.getBsn()), null, null, null); |
| result.add(c); |
| } |
| return result; |
| |
| } |
| |
| /** |
| * Return the builder associated with the give bnd file or null. The bnd.bnd |
| * file can contain -sub option. This option allows specifying files in the |
| * same directory that should drive the generation of multiple deliverables. |
| * This method figures out if the bndFile is actually one of the bnd files |
| * of a deliverable. |
| * |
| * @param bndFile |
| * A file pointing to a bnd file. |
| * @return null or the builder for a sub file. |
| * @throws Exception |
| */ |
| public Builder getSubBuilder(File bndFile) throws Exception { |
| bndFile = bndFile.getCanonicalFile(); |
| |
| // Verify that we are inside the project. |
| File base = getBase().getCanonicalFile(); |
| if (!bndFile.getAbsolutePath().startsWith(base.getAbsolutePath())) |
| return null; |
| |
| Collection< ? extends Builder> builders = getSubBuilders(); |
| for (Builder sub : builders) { |
| File propertiesFile = sub.getPropertiesFile(); |
| if (propertiesFile != null) { |
| if (propertiesFile.getCanonicalFile().equals(bndFile)) { |
| // Found it! |
| return sub; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Answer the container associated with a given bsn. |
| * |
| * @param bndFile |
| * A file pointing to a bnd file. |
| * @return null or the builder for a sub file. |
| * @throws Exception |
| */ |
| public Container getDeliverable(String bsn, @SuppressWarnings("unused") |
| Map<String,String> attrs) throws Exception { |
| Collection< ? extends Builder> builders = getSubBuilders(); |
| for (Builder sub : builders) { |
| if (sub.getBsn().equals(bsn)) |
| return new Container(this, getOutputFile(bsn)); |
| } |
| return null; |
| } |
| |
| /** |
| * Get a list of the sub builders. A bnd.bnd file can contain the -sub |
| * option. This will generate multiple deliverables. This method returns the |
| * builders for each sub file. If no -sub option is present, the list will |
| * contain a builder for the bnd.bnd file. |
| * |
| * @return A list of builders. |
| * @throws Exception |
| */ |
| public Collection< ? extends Builder> getSubBuilders() throws Exception { |
| return getBuilder(null).getSubBuilders(); |
| } |
| |
| /** |
| * Calculate the classpath. We include our own runtime.jar which includes |
| * the test framework and we include the first of the test frameworks |
| * specified. |
| * |
| * @throws Exception |
| */ |
| Collection<File> toFile(Collection<Container> containers) throws Exception { |
| ArrayList<File> files = new ArrayList<File>(); |
| for (Container container : containers) { |
| container.contributeFiles(files, this); |
| } |
| return files; |
| } |
| |
| public Collection<String> getRunVM() { |
| Parameters hdr = getParameters(RUNVM); |
| return hdr.keySet(); |
| } |
| |
| public Map<String,String> getRunProperties() { |
| return OSGiHeader.parseProperties(getProperty(RUNPROPERTIES)); |
| } |
| |
| /** |
| * Get a launcher. |
| * |
| * @return |
| * @throws Exception |
| */ |
| public ProjectLauncher getProjectLauncher() throws Exception { |
| return getHandler(ProjectLauncher.class, getRunpath(), LAUNCHER_PLUGIN, "biz.aQute.launcher"); |
| } |
| |
| public ProjectTester getProjectTester() throws Exception { |
| return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN, "biz.aQute.junit"); |
| } |
| |
| private <T> T getHandler(Class<T> target, Collection<Container> containers, String header, String defaultHandler) |
| throws Exception { |
| Class< ? extends T> handlerClass = target; |
| |
| // Make sure we find at least one handler, but hope to find an earlier |
| // one |
| List<Container> withDefault = Create.list(); |
| withDefault.addAll(containers); |
| withDefault.addAll(getBundles(Strategy.HIGHEST, defaultHandler, null)); |
| trace("candidates for tester %s", 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 { |
| handlerClass = clz.asSubclass(target); |
| Constructor< ? extends T> constructor = handlerClass.getConstructor(Project.class); |
| return constructor.newInstance(this); |
| } |
| } |
| } |
| } |
| } |
| |
| throw new IllegalArgumentException("Default handler for " + header + " not found in " + defaultHandler); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * Sets the package version on an exported package |
| * |
| * @param packageName |
| * The package name |
| * @param version |
| * The new package version |
| */ |
| public void setPackageInfo(String packageName, Version version) { |
| try { |
| Version current = getPackageInfoJavaVersion(packageName); |
| boolean packageInfoJava = false; |
| if (current != null) { |
| updatePackageInfoJavaFile(packageName, version); |
| packageInfoJava = true; |
| } |
| if (!packageInfoJava || getPackageInfoFile(packageName).exists()) { |
| updatePackageInfoFile(packageName, version); |
| } |
| } |
| catch (Exception e) { |
| msgs.SettingPackageInfoException_(e); |
| } |
| } |
| |
| void updatePackageInfoJavaFile(String packageName, final Version newVersion) throws Exception { |
| File file = getPackageInfoJavaFile(packageName); |
| |
| if (!file.exists()) { |
| return; |
| } |
| |
| // If package/classes are copied into the bundle through Private-Package |
| // etc, there will be no source |
| if (!file.getParentFile().exists()) { |
| return; |
| } |
| |
| Version oldVersion = getPackageInfo(packageName); |
| |
| if (newVersion.compareTo(oldVersion) == 0) { |
| return; |
| } |
| |
| Sed sed = new Sed(new Replacer() { |
| public String process(String line) { |
| Matcher m = VERSION_ANNOTATION.matcher(line); |
| if (m.find()) { |
| return line.substring(0, m.start(3)) + newVersion.toString() + line.substring(m.end(3)); |
| } |
| return line; |
| } |
| }, file); |
| |
| sed.replace(VERSION_ANNOTATION.pattern(), "$0"); |
| sed.setBackup(false); |
| sed.doIt(); |
| } |
| |
| void updatePackageInfoFile(String packageName, Version newVersion) throws Exception { |
| |
| File file = getPackageInfoFile(packageName); |
| |
| // If package/classes are copied into the bundle through Private-Package |
| // etc, there will be no source |
| if (!file.getParentFile().exists()) { |
| return; |
| } |
| |
| Version oldVersion = getPackageInfoVersion(packageName); |
| if (oldVersion == null) { |
| oldVersion = Version.emptyVersion; |
| } |
| |
| if (newVersion.compareTo(oldVersion) == 0) { |
| return; |
| } |
| PrintWriter pw = IO.writer(file); |
| pw.println("version " + newVersion); |
| pw.flush(); |
| pw.close(); |
| |
| String path = packageName.replace('.', '/') + "/packageinfo"; |
| File binary = IO.getFile(getOutput(), path); |
| File bp = binary.getParentFile(); |
| if (!bp.exists() && !bp.mkdirs()) { |
| throw new IOException("Could not create directory " + bp); |
| } |
| IO.copy(file, binary); |
| } |
| |
| File getPackageInfoFile(String packageName) { |
| String path = packageName.replace('.', '/') + "/packageinfo"; |
| return IO.getFile(getSrc(), path); |
| |
| } |
| |
| File getPackageInfoJavaFile(String packageName) { |
| String path = packageName.replace('.', '/') + "/package-info.java"; |
| return IO.getFile(getSrc(), path); |
| |
| } |
| |
| public Version getPackageInfo(String packageName) throws IOException { |
| |
| Version version = getPackageInfoJavaVersion(packageName); |
| if (version != null) { |
| return version; |
| } |
| |
| version = getPackageInfoVersion(packageName); |
| if (version != null) { |
| return version; |
| } |
| |
| return Version.emptyVersion; |
| } |
| |
| Version getPackageInfoVersion(String packageName) throws IOException { |
| File packageInfoFile = getPackageInfoFile(packageName); |
| if (!packageInfoFile.exists()) { |
| return null; |
| } |
| BufferedReader reader = null; |
| try { |
| reader = IO.reader(packageInfoFile); |
| String line; |
| while ((line = reader.readLine()) != null) { |
| line = line.trim(); |
| if (line.startsWith("version ")) { |
| return Version.parseVersion(line.substring(8)); |
| } |
| } |
| } |
| finally { |
| if (reader != null) { |
| IO.close(reader); |
| } |
| } |
| return null; |
| } |
| |
| Version getPackageInfoJavaVersion(String packageName) throws IOException { |
| File packageInfoJavaFile = getPackageInfoJavaFile(packageName); |
| if (!packageInfoJavaFile.exists()) { |
| return null; |
| } |
| BufferedReader reader = null; |
| try { |
| reader = IO.reader(packageInfoJavaFile); |
| String line; |
| while ((line = reader.readLine()) != null) { |
| Matcher matcher = VERSION_ANNOTATION.matcher(line); |
| if (matcher.find()) { |
| return Version.parseVersion(matcher.group(3)); |
| } |
| } |
| } |
| finally { |
| if (reader != null) { |
| IO.close(reader); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| public Collection<Container> getClasspath() { |
| return classpath; |
| } |
| } |