blob: 99c904e3f3e8b15c398e12ec061e6ac79c14b1dc [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.bnd.build;
2
3import java.io.*;
4import java.lang.reflect.*;
5import java.net.*;
6import java.util.*;
7import java.util.Map.Entry;
Stuart McCullochf3173222012-06-07 21:57:32 +00008import java.util.concurrent.locks.*;
9import java.util.jar.*;
10
Stuart McCulloch42151ee2012-07-16 13:43:38 +000011import aQute.bnd.header.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000012import aQute.bnd.help.*;
13import aQute.bnd.maven.support.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000014import aQute.bnd.osgi.*;
15import aQute.bnd.osgi.eclipse.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000016import aQute.bnd.service.*;
17import aQute.bnd.service.RepositoryPlugin.Strategy;
18import aQute.bnd.service.action.*;
19import aQute.lib.io.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000020import aQute.libg.generics.*;
Stuart McCulloch4482c702012-06-15 13:27:53 +000021import aQute.libg.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000022import aQute.libg.sed.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000023
24/**
25 * This class is NOT threadsafe
Stuart McCullochf3173222012-06-07 21:57:32 +000026 */
27
28public class Project extends Processor {
29
30 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";
31 public final static String BNDFILE = "bnd.bnd";
32 public final static String BNDCNF = "cnf";
33 final Workspace workspace;
34 boolean preparedPaths;
35 final Collection<Project> dependson = new LinkedHashSet<Project>();
36 final Collection<Container> classpath = new LinkedHashSet<Container>();
37 final Collection<Container> buildpath = new LinkedHashSet<Container>();
38 final Collection<Container> testpath = new LinkedHashSet<Container>();
39 final Collection<Container> runpath = new LinkedHashSet<Container>();
40 final Collection<Container> runbundles = new LinkedHashSet<Container>();
41 File runstorage;
42 final Collection<File> sourcepath = new LinkedHashSet<File>();
43 final Collection<File> allsourcepath = new LinkedHashSet<File>();
44 final Collection<Container> bootclasspath = new LinkedHashSet<Container>();
45 final Lock lock = new ReentrantLock(true);
46 volatile String lockingReason;
47 volatile Thread lockingThread;
48 File output;
49 File target;
50 boolean inPrepare;
51 int revision;
52 File files[];
53 static List<Project> trail = new ArrayList<Project>();
54 boolean delayRunDependencies = false;
Stuart McCulloch4482c702012-06-15 13:27:53 +000055 final ProjectMessages msgs = ReporterMessages.base(this, ProjectMessages.class);
Stuart McCullochf3173222012-06-07 21:57:32 +000056
Stuart McCulloch669423b2012-06-26 16:34:24 +000057 public Project(Workspace workspace, @SuppressWarnings("unused") File projectDir, File buildFile) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000058 super(workspace);
59 this.workspace = workspace;
60 setFileMustExist(false);
61 setProperties(buildFile);
62 assert workspace != null;
63 // For backward compatibility reasons, we also read
64 readBuildProperties();
65 }
66
67 public Project(Workspace workspace, File buildDir) throws Exception {
68 this(workspace, buildDir, new File(buildDir, BNDFILE));
69 }
70
71 private void readBuildProperties() throws Exception {
72 try {
73 File f = getFile("build.properties");
74 if (f.isFile()) {
75 Properties p = loadProperties(f);
Stuart McCulloch4482c702012-06-15 13:27:53 +000076 for (Enumeration< ? > e = p.propertyNames(); e.hasMoreElements();) {
Stuart McCullochf3173222012-06-07 21:57:32 +000077 String key = (String) e.nextElement();
78 String newkey = key;
79 if (key.indexOf('$') >= 0) {
80 newkey = getReplacer().process(key);
81 }
82 setProperty(newkey, p.getProperty(key));
83 }
84 }
Stuart McCulloch4482c702012-06-15 13:27:53 +000085 }
86 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +000087 e.printStackTrace();
88 }
89 }
90
91 public static Project getUnparented(File propertiesFile) throws Exception {
92 propertiesFile = propertiesFile.getAbsoluteFile();
93 Workspace workspace = new Workspace(propertiesFile.getParentFile());
94 Project project = new Project(workspace, propertiesFile.getParentFile());
95 project.setProperties(propertiesFile);
96 project.setFileMustExist(true);
97 return project;
98 }
99
100 public synchronized boolean isValid() {
101 return getBase().isDirectory() && getPropertiesFile().isFile();
102 }
103
104 /**
105 * Return a new builder that is nicely setup for this project. Please close
106 * this builder after use.
107 *
108 * @param parent
109 * The project builder to use as parent, use this project if null
110 * @return
111 * @throws Exception
112 */
113 public synchronized ProjectBuilder getBuilder(ProjectBuilder parent) throws Exception {
114
115 ProjectBuilder builder;
116
117 if (parent == null)
118 builder = new ProjectBuilder(this);
119 else
120 builder = new ProjectBuilder(parent);
121
122 builder.setBase(getBase());
Stuart McCulloch669423b2012-06-26 16:34:24 +0000123 builder.setPedantic(isPedantic());
124 builder.setTrace(isTrace());
Stuart McCullochf3173222012-06-07 21:57:32 +0000125 return builder;
126 }
127
128 public synchronized int getChanged() {
129 return revision;
130 }
131
132 /*
133 * Indicate a change in the external world that affects our build. This will
134 * clear any cached results.
135 */
136 public synchronized void setChanged() {
137 // if (refresh()) {
138 preparedPaths = false;
139 files = null;
140 revision++;
141 // }
142 }
143
144 public Workspace getWorkspace() {
145 return workspace;
146 }
147
148 public String toString() {
149 return getBase().getName();
150 }
151
152 /**
153 * Set up all the paths
154 */
155
156 public synchronized void prepare() throws Exception {
157 if (!isValid()) {
158 warning("Invalid project attempts to prepare: %s", this);
159 return;
160 }
161
162 if (inPrepare)
163 throw new CircularDependencyException(trail.toString() + "," + this);
164
165 trail.add(this);
166 try {
167 if (!preparedPaths) {
168 inPrepare = true;
169 try {
170 dependson.clear();
171 buildpath.clear();
172 sourcepath.clear();
173 allsourcepath.clear();
174 bootclasspath.clear();
175 testpath.clear();
176 runpath.clear();
177 runbundles.clear();
178
179 // We use a builder to construct all the properties for
180 // use.
181 setProperty("basedir", getBase().getAbsolutePath());
182
183 // If a bnd.bnd file exists, we read it.
184 // Otherwise, we just do the build properties.
185 if (!getPropertiesFile().isFile() && new File(getBase(), ".classpath").isFile()) {
186 // Get our Eclipse info, we might depend on other
187 // projects
188 // though ideally this should become empty and void
189 doEclipseClasspath();
190 }
191
192 // Calculate our source directory
193
194 File src = getSrc();
195 if (src.isDirectory()) {
196 sourcepath.add(src);
197 allsourcepath.add(src);
198 } else
199 sourcepath.add(getBase());
200
201 // Set default bin directory
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000202 output = getOutput0();
Stuart McCullochf3173222012-06-07 21:57:32 +0000203 if (!output.exists()) {
204 output.mkdirs();
205 getWorkspace().changedFile(output);
206 }
207 if (!output.isDirectory())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000208 msgs.NoOutputDirectory_(output);
Stuart McCullochf3173222012-06-07 21:57:32 +0000209 else {
210 Container c = new Container(this, output);
211 if (!buildpath.contains(c))
212 buildpath.add(c);
213 }
214
215 // Where we store all our generated stuff.
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000216 target = getTarget0();
Stuart McCullochf3173222012-06-07 21:57:32 +0000217
218 // Where the launched OSGi framework stores stuff
219 String runStorageStr = getProperty(Constants.RUNSTORAGE);
220 runstorage = runStorageStr != null ? getFile(runStorageStr) : null;
221
222 // We might have some other projects we want build
223 // before we do anything, but these projects are not in
224 // our path. The -dependson allows you to build them before.
225
226 List<Project> dependencies = new ArrayList<Project>();
227 // dependencies.add( getWorkspace().getProject("cnf"));
228
229 String dp = getProperty(Constants.DEPENDSON);
230 Set<String> requiredProjectNames = new Parameters(dp).keySet();
231 List<DependencyContributor> dcs = getPlugins(DependencyContributor.class);
232 for (DependencyContributor dc : dcs)
233 dc.addDependencies(this, requiredProjectNames);
234
235 for (String p : requiredProjectNames) {
236 Project required = getWorkspace().getProject(p);
237 if (required == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000238 msgs.MissingDependson_(p);
Stuart McCullochf3173222012-06-07 21:57:32 +0000239 else {
240 dependencies.add(required);
241 }
242
243 }
244
245 // We have two paths that consists of repo files, projects,
246 // or some other stuff. The doPath routine adds them to the
247 // path and extracts the projects so we can build them
248 // before.
249
250 doPath(buildpath, dependencies, parseBuildpath(), bootclasspath);
251 doPath(testpath, dependencies, parseTestpath(), bootclasspath);
252 if (!delayRunDependencies) {
253 doPath(runpath, dependencies, parseRunpath(), null);
254 doPath(runbundles, dependencies, parseRunbundles(), null);
255 }
256
257 // We now know all dependent projects. But we also depend
258 // on whatever those projects depend on. This creates an
259 // ordered list without any duplicates. This of course
260 // assumes
261 // that there is no circularity. However, this is checked
262 // by the inPrepare flag, will throw an exception if we
263 // are circular.
264
265 Set<Project> done = new HashSet<Project>();
266 done.add(this);
267 allsourcepath.addAll(sourcepath);
268
269 for (Project project : dependencies)
270 project.traverse(dependson, done);
271
272 for (Project project : dependson) {
273 allsourcepath.addAll(project.getSourcePath());
274 }
275 if (isOk())
276 preparedPaths = true;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000277 }
278 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000279 inPrepare = false;
280 }
281 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000282 }
283 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000284 trail.remove(this);
285 }
286 }
287
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000288 /**
289 * @return
290 */
291 private File getOutput0() {
292 return getFile(getProperty("bin", "bin")).getAbsoluteFile();
293 }
294
295 /**
296 *
297 */
298 private File getTarget0() {
299 File target = getFile(getProperty("target", "generated"));
300 if (!target.exists()) {
301 target.mkdirs();
302 getWorkspace().changedFile(target);
303 }
304 return target;
305 }
306
Stuart McCullochf3173222012-06-07 21:57:32 +0000307 public File getSrc() {
308 return new File(getBase(), getProperty("src", "src"));
309 }
310
311 private void traverse(Collection<Project> dependencies, Set<Project> visited) throws Exception {
312 if (visited.contains(this))
313 return;
314
315 visited.add(this);
316
317 for (Project project : getDependson())
318 project.traverse(dependencies, visited);
319
320 dependencies.add(this);
321 }
322
323 /**
324 * Iterate over the entries and place the projects on the projects list and
325 * all the files of the entries on the resultpath.
326 *
327 * @param resultpath
328 * The list that gets all the files
329 * @param projects
330 * The list that gets any projects that are entries
331 * @param entries
332 * The input list of classpath entries
333 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000334 private void doPath(Collection<Container> resultpath, Collection<Project> projects, Collection<Container> entries,
335 Collection<Container> bootclasspath) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000336 for (Container cpe : entries) {
337 if (cpe.getError() != null)
338 error(cpe.getError());
339 else {
340 if (cpe.getType() == Container.TYPE.PROJECT) {
341 projects.add(cpe.getProject());
342 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000343 if (bootclasspath != null
344 && (cpe.getBundleSymbolicName().startsWith("ee.") || cpe.getAttributes().containsKey("boot")))
Stuart McCullochf3173222012-06-07 21:57:32 +0000345 bootclasspath.add(cpe);
346 else
347 resultpath.add(cpe);
348 }
349 }
350 }
351
352 /**
353 * Parse the list of bundles that are a prerequisite to this project.
Stuart McCullochf3173222012-06-07 21:57:32 +0000354 * Bundles are listed in repo specific names. So we just let our repo
355 * plugins iterate over the list of bundles and we get the highest version
356 * from them.
357 *
358 * @return
359 */
360
361 private List<Container> parseBuildpath() throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000362 List<Container> bundles = getBundles(Strategy.LOWEST, getProperty(Constants.BUILDPATH), Constants.BUILDPATH);
363 appendPackages(Strategy.LOWEST, getProperty(Constants.BUILDPACKAGES), bundles, ResolverMode.build);
Stuart McCullochf3173222012-06-07 21:57:32 +0000364 return bundles;
365 }
366
367 private List<Container> parseRunpath() throws Exception {
368 return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNPATH), Constants.RUNPATH);
369 }
370
371 private List<Container> parseRunbundles() throws Exception {
372 return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNBUNDLES), Constants.RUNBUNDLES);
373 }
374
375 private List<Container> parseTestpath() throws Exception {
376 return getBundles(Strategy.HIGHEST, getProperty(Constants.TESTPATH), Constants.TESTPATH);
377 }
378
379 /**
380 * Analyze the header and return a list of files that should be on the
381 * build, test or some other path. The list is assumed to be a list of bsns
382 * with a version specification. The special case of version=project
383 * indicates there is a project in the same workspace. The path to the
384 * output directory is calculated. The default directory ${bin} can be
385 * overridden with the output attribute.
386 *
387 * @param strategy
388 * STRATEGY_LOWEST or STRATEGY_HIGHEST
389 * @param spec
390 * The header
391 * @return
392 */
393
Stuart McCulloch4482c702012-06-15 13:27:53 +0000394 public List<Container> getBundles(Strategy strategyx, String spec, String source) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000395 List<Container> result = new ArrayList<Container>();
396 Parameters bundles = new Parameters(spec);
397
398 try {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000399 for (Iterator<Entry<String,Attrs>> i = bundles.entrySet().iterator(); i.hasNext();) {
400 Entry<String,Attrs> entry = i.next();
401 String bsn = removeDuplicateMarker(entry.getKey());
402 Map<String,String> attrs = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +0000403
404 Container found = null;
405
406 String versionRange = attrs.get("version");
407
408 if (versionRange != null) {
409 if (versionRange.equals("latest") || versionRange.equals("snapshot")) {
410 found = getBundle(bsn, versionRange, strategyx, attrs);
411 }
412 }
413 if (found == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000414 if (versionRange != null && (versionRange.equals("project") || versionRange.equals("latest"))) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000415 Project project = getWorkspace().getProject(bsn);
416 if (project != null && project.exists()) {
417 File f = project.getOutput();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000418 found = new Container(project, bsn, versionRange, Container.TYPE.PROJECT, f, null, attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +0000419 } else {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000420 msgs.NoSuchProject(bsn, spec);
Stuart McCullochf3173222012-06-07 21:57:32 +0000421 continue;
422 }
423 } else if (versionRange != null && versionRange.equals("file")) {
424 File f = getFile(bsn);
425 String error = null;
426 if (!f.exists())
427 error = "File does not exist: " + f.getAbsolutePath();
428 if (f.getName().endsWith(".lib")) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000429 found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f, error, attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +0000430 } else {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000431 found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f, error, attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +0000432 }
433 } else {
434 found = getBundle(bsn, versionRange, strategyx, attrs);
435 }
436 }
437
438 if (found != null) {
439 List<Container> libs = found.getMembers();
440 for (Container cc : libs) {
441 if (result.contains(cc))
Stuart McCulloch4482c702012-06-15 13:27:53 +0000442 warning("Multiple bundles with the same final URL: %s, dropped duplicate", cc);
443 else
444 result.add(cc);
Stuart McCullochf3173222012-06-07 21:57:32 +0000445 }
446 } else {
447 // Oops, not a bundle in sight :-(
Stuart McCulloch4482c702012-06-15 13:27:53 +0000448 Container x = new Container(this, bsn, versionRange, Container.TYPE.ERROR, null, bsn + ";version="
449 + versionRange + " not found", attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +0000450 result.add(x);
451 warning("Can not find URL for bsn " + bsn);
452 }
453 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000454 }
455 catch (CircularDependencyException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000456 String message = e.getMessage();
457 if (source != null)
458 message = String.format("%s (from property: %s)", message, source);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000459 msgs.CircularDependencyContext_Message_(getName(), message);
460 }
461 catch (Exception e) {
462 msgs.Unexpected_Error_(spec, e);
Stuart McCullochf3173222012-06-07 21:57:32 +0000463 }
464 return result;
465 }
466
467 /**
468 * Just calls a new method with a default parm.
469 *
470 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000471 */
472 Collection<Container> getBundles(Strategy strategy, String spec) throws Exception {
473 return getBundles(strategy, spec, null);
474 }
475
476 /**
477 * Calculates the containers required to fulfil the {@code -buildpackages}
478 * instruction, and appends them to the existing list of containers.
479 *
480 * @param strategyx
481 * The package-version disambiguation strategy.
482 * @param spec
483 * The value of the @{code -buildpackages} instruction.
484 * @throws Exception
485 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000486 public void appendPackages(Strategy strategyx, String spec, List<Container> resolvedBundles, ResolverMode mode)
487 throws Exception {
488 Map<File,Container> pkgResolvedBundles = new HashMap<File,Container>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000489
Stuart McCulloch4482c702012-06-15 13:27:53 +0000490 List<Entry<String,Attrs>> queue = new LinkedList<Map.Entry<String,Attrs>>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000491 queue.addAll(new Parameters(spec).entrySet());
492
493 while (!queue.isEmpty()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000494 Entry<String,Attrs> entry = queue.remove(0);
Stuart McCullochf3173222012-06-07 21:57:32 +0000495
496 String pkgName = entry.getKey();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000497 Map<String,String> attrs = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +0000498
499 Container found = null;
500
501 String versionRange = attrs.get(Constants.VERSION_ATTRIBUTE);
502 if ("latest".equals(versionRange) || "snapshot".equals(versionRange))
503 found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
504
505 if (found == null)
506 found = getPackage(pkgName, versionRange, strategyx, attrs, mode);
507
508 if (found != null) {
509 if (resolvedBundles.contains(found)) {
510 // Don't add his bundle because it was already included
511 // using -buildpath
512 } else {
513 List<Container> libs = found.getMembers();
514 for (Container cc : libs) {
515 Container existing = pkgResolvedBundles.get(cc.file);
516 if (existing != null)
517 addToPackageList(existing, attrs.get("packages"));
518 else {
519 addToPackageList(cc, attrs.get("packages"));
520 pkgResolvedBundles.put(cc.file, cc);
521 }
522
523 String importUses = cc.getAttributes().get("import-uses");
524 if (importUses != null)
525 queue.addAll(0, new Parameters(importUses).entrySet());
526 }
527 }
528 } else {
529 // Unable to resolve
Stuart McCulloch4482c702012-06-15 13:27:53 +0000530 Container x = new Container(this, "X", versionRange, Container.TYPE.ERROR, null, "package " + pkgName
531 + ";version=" + versionRange + " not found", attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +0000532 resolvedBundles.add(x);
533 warning("Can not find URL for package " + pkgName);
534 }
535 }
536
537 for (Container container : pkgResolvedBundles.values()) {
538 resolvedBundles.add(container);
539 }
540 }
541
542 static void mergeNames(String names, Set<String> set) {
543 StringTokenizer tokenizer = new StringTokenizer(names, ",");
544 while (tokenizer.hasMoreTokens())
545 set.add(tokenizer.nextToken().trim());
546 }
547
548 static String flatten(Set<String> names) {
549 StringBuilder builder = new StringBuilder();
550 boolean first = true;
551 for (String name : names) {
552 if (!first)
553 builder.append(',');
554 builder.append(name);
555 first = false;
556 }
557 return builder.toString();
558 }
559
560 static void addToPackageList(Container container, String newPackageNames) {
561 Set<String> merged = new HashSet<String>();
562
563 String packageListStr = container.attributes.get("packages");
564 if (packageListStr != null)
565 mergeNames(packageListStr, merged);
566 if (newPackageNames != null)
567 mergeNames(newPackageNames, merged);
568
569 container.putAttribute("packages", flatten(merged));
570 }
571
572 /**
573 * Find a container to fulfil a package requirement
574 *
575 * @param packageName
576 * The package required
577 * @param range
578 * The package version range required
579 * @param strategyx
580 * The package-version disambiguation strategy
581 * @param attrs
582 * Other attributes specified by the search.
583 * @return
584 * @throws Exception
585 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000586 public Container getPackage(String packageName, String range, Strategy strategyx, Map<String,String> attrs,
587 ResolverMode mode) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000588 if ("snapshot".equals(range))
589 return new Container(this, "", range, Container.TYPE.ERROR, null,
590 "snapshot not supported for package lookups", null);
591
592 if (attrs == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000593 attrs = new HashMap<String,String>(2);
Stuart McCullochf3173222012-06-07 21:57:32 +0000594 attrs.put("package", packageName);
595 attrs.put("mode", mode.name());
596
597 Strategy useStrategy = findStrategy(attrs, strategyx, range);
598
599 List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
600 for (RepositoryPlugin plugin : plugins) {
601 try {
602 File result = plugin.get(null, range, useStrategy, attrs);
603 if (result != null) {
604 if (result.getName().endsWith("lib"))
Stuart McCulloch4482c702012-06-15 13:27:53 +0000605 return new Container(this, result.getName(), range, Container.TYPE.LIBRARY, result, null, attrs);
606 return new Container(this, result.getName(), range, Container.TYPE.REPO, result, null, attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +0000607 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000608 }
609 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000610 // Ignore... lots of repos will fail here
611 }
612 }
613
Stuart McCulloch4482c702012-06-15 13:27:53 +0000614 return new Container(this, "X", range, Container.TYPE.ERROR, null, "package " + packageName + ";version="
615 + range + " Not found in " + plugins, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000616 }
617
Stuart McCulloch4482c702012-06-15 13:27:53 +0000618 private Strategy findStrategy(Map<String,String> attrs, Strategy defaultStrategy, String versionRange) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000619 Strategy useStrategy = defaultStrategy;
620 String overrideStrategy = attrs.get("strategy");
621 if (overrideStrategy != null) {
622 if ("highest".equalsIgnoreCase(overrideStrategy))
623 useStrategy = Strategy.HIGHEST;
624 else if ("lowest".equalsIgnoreCase(overrideStrategy))
625 useStrategy = Strategy.LOWEST;
626 else if ("exact".equalsIgnoreCase(overrideStrategy))
627 useStrategy = Strategy.EXACT;
628 }
629 if ("latest".equals(versionRange))
630 useStrategy = Strategy.HIGHEST;
631 return useStrategy;
632 }
633
634 /**
635 * The user selected pom in a path. This will place the pom as well as its
636 * dependencies on the list
637 *
638 * @param strategyx
639 * the strategy to use.
640 * @param result
641 * The list of result containers
642 * @param attrs
643 * The attributes
644 * @throws Exception
645 * anything goes wrong
646 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000647 public void doMavenPom(Strategy strategyx, List<Container> result, String action) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000648 File pomFile = getFile("pom.xml");
649 if (!pomFile.isFile())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000650 msgs.MissingPom();
Stuart McCullochf3173222012-06-07 21:57:32 +0000651 else {
652 ProjectPom pom = getWorkspace().getMaven().createProjectModel(pomFile);
653 if (action == null)
654 action = "compile";
655 Pom.Scope act = Pom.Scope.valueOf(action);
656 Set<Pom> dependencies = pom.getDependencies(act);
657 for (Pom sub : dependencies) {
658 File artifact = sub.getArtifact();
659 Container container = new Container(artifact);
660 result.add(container);
661 }
662 }
663 }
664
665 public Collection<Project> getDependson() throws Exception {
666 prepare();
667 return dependson;
668 }
669
670 public Collection<Container> getBuildpath() throws Exception {
671 prepare();
672 return buildpath;
673 }
674
675 public Collection<Container> getTestpath() throws Exception {
676 prepare();
677 return testpath;
678 }
679
680 /**
681 * Handle dependencies for paths that are calculated on demand.
682 *
683 * @param testpath2
684 * @param parseTestpath
685 */
686 private void justInTime(Collection<Container> path, List<Container> entries) {
687 if (delayRunDependencies && path.isEmpty())
688 doPath(path, dependson, entries, null);
689 }
690
691 public Collection<Container> getRunpath() throws Exception {
692 prepare();
693 justInTime(runpath, parseRunpath());
694 return runpath;
695 }
696
697 public Collection<Container> getRunbundles() throws Exception {
698 prepare();
699 justInTime(runbundles, parseRunbundles());
700 return runbundles;
701 }
702
703 public File getRunStorage() throws Exception {
704 prepare();
705 return runstorage;
706 }
707
708 public boolean getRunBuilds() {
709 boolean result;
710 String runBuildsStr = getProperty(Constants.RUNBUILDS);
711 if (runBuildsStr == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000712 result = !getPropertiesFile().getName().toLowerCase().endsWith(Constants.DEFAULT_BNDRUN_EXTENSION);
Stuart McCullochf3173222012-06-07 21:57:32 +0000713 else
714 result = Boolean.parseBoolean(runBuildsStr);
715 return result;
716 }
717
718 public Collection<File> getSourcePath() throws Exception {
719 prepare();
720 return sourcepath;
721 }
722
723 public Collection<File> getAllsourcepath() throws Exception {
724 prepare();
725 return allsourcepath;
726 }
727
728 public Collection<Container> getBootclasspath() throws Exception {
729 prepare();
730 return bootclasspath;
731 }
732
733 public File getOutput() throws Exception {
734 prepare();
735 return output;
736 }
737
738 private void doEclipseClasspath() throws Exception {
739 EclipseClasspath eclipse = new EclipseClasspath(this, getWorkspace().getBase(), getBase());
740 eclipse.setRecurse(false);
741
742 // We get the file directories but in this case we need
743 // to tell ant that the project names
744 for (File dependent : eclipse.getDependents()) {
745 Project required = workspace.getProject(dependent.getName());
746 dependson.add(required);
747 }
748 for (File f : eclipse.getClasspath()) {
749 buildpath.add(new Container(f));
750 }
751 for (File f : eclipse.getBootclasspath()) {
752 bootclasspath.add(new Container(f));
753 }
754 sourcepath.addAll(eclipse.getSourcepath());
755 allsourcepath.addAll(eclipse.getAllSources());
756 output = eclipse.getOutput();
757 }
758
759 public String _p_dependson(String args[]) throws Exception {
760 return list(args, toFiles(getDependson()));
761 }
762
Stuart McCulloch4482c702012-06-15 13:27:53 +0000763 private Collection< ? > toFiles(Collection<Project> projects) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000764 List<File> files = new ArrayList<File>();
765 for (Project p : projects) {
766 files.add(p.getBase());
767 }
768 return files;
769 }
770
771 public String _p_buildpath(String args[]) throws Exception {
772 return list(args, getBuildpath());
773 }
774
775 public String _p_testpath(String args[]) throws Exception {
776 return list(args, getRunpath());
777 }
778
779 public String _p_sourcepath(String args[]) throws Exception {
780 return list(args, getSourcePath());
781 }
782
783 public String _p_allsourcepath(String args[]) throws Exception {
784 return list(args, getAllsourcepath());
785 }
786
787 public String _p_bootclasspath(String args[]) throws Exception {
788 return list(args, getBootclasspath());
789 }
790
791 public String _p_output(String args[]) throws Exception {
792 if (args.length != 1)
793 throw new IllegalArgumentException("${output} should not have arguments");
794 return getOutput().getAbsolutePath();
795 }
796
Stuart McCulloch4482c702012-06-15 13:27:53 +0000797 private String list(String[] args, Collection< ? > list) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000798 if (args.length > 3)
799 throw new IllegalArgumentException("${" + args[0]
Stuart McCulloch4482c702012-06-15 13:27:53 +0000800 + "[;<separator>]} can only take a separator as argument, has " + Arrays.toString(args));
Stuart McCullochf3173222012-06-07 21:57:32 +0000801
802 String separator = ",";
803
804 if (args.length == 2) {
805 separator = args[1];
806 }
807
808 return join(list, separator);
809 }
810
811 protected Object[] getMacroDomains() {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000812 return new Object[] {
813 workspace
814 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000815 }
816
817 public File release(Jar jar) throws Exception {
818 String name = getProperty(Constants.RELEASEREPO);
819 return release(name, jar);
820 }
821
822 /**
823 * Release
824 *
825 * @param name
826 * The repository name
827 * @param jar
828 * @return
829 * @throws Exception
830 */
831 public File release(String name, Jar jar) throws Exception {
832 trace("release %s", name);
833 List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
834 RepositoryPlugin rp = null;
835 for (RepositoryPlugin plugin : plugins) {
836 if (!plugin.canWrite()) {
837 continue;
838 }
839 if (name == null) {
840 rp = plugin;
841 break;
842 } else if (name.equals(plugin.getName())) {
843 rp = plugin;
844 break;
845 }
846 }
847
848 if (rp != null) {
849 try {
850 File file = rp.put(jar);
851 trace("Released %s to file %s in repository %s", jar.getName(), file, rp);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000852 }
853 catch (Exception e) {
854 msgs.Release_Into_Exception_(jar, rp, e);
855 }
856 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000857 jar.close();
858 }
859 } else if (name == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000860 msgs.NoNameForReleaseRepository();
Stuart McCullochf3173222012-06-07 21:57:32 +0000861 else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000862 msgs.ReleaseRepository_NotFoundIn_(name, plugins);
Stuart McCullochf3173222012-06-07 21:57:32 +0000863
864 return null;
865
866 }
867
868 public void release(boolean test) throws Exception {
869 String name = getProperty(Constants.RELEASEREPO);
870 release(name, test);
871 }
872
873 /**
874 * Release
875 *
876 * @param name
877 * The respository name
878 * @param test
879 * Run testcases
880 * @throws Exception
881 */
882 public void release(String name, boolean test) throws Exception {
883 trace("release");
884 File[] jars = build(test);
885 // If build fails jars will be null
886 if (jars == null) {
887 trace("no jars being build");
888 return;
889 }
890 trace("build ", Arrays.toString(jars));
891 for (File jar : jars) {
892 Jar j = new Jar(jar);
893 try {
894 release(name, j);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000895 }
896 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000897 j.close();
898 }
899 }
900
901 }
902
903 /**
904 * Get a bundle from one of the plugin repositories. If an exact version is
905 * required we just return the first repository found (in declaration order
906 * in the build.bnd file).
907 *
908 * @param bsn
909 * The bundle symbolic name
910 * @param range
911 * The version range
912 * @param lowest
913 * set to LOWEST or HIGHEST
914 * @return the file object that points to the bundle or null if not found
915 * @throws Exception
916 * when something goes wrong
917 */
918
Stuart McCulloch4482c702012-06-15 13:27:53 +0000919 public Container getBundle(String bsn, String range, Strategy strategy, Map<String,String> attrs) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000920
921 if (range == null)
922 range = "0";
923
924 if ("snapshot".equals(range)) {
925 return getBundleFromProject(bsn, attrs);
926 }
927
928 Strategy useStrategy = strategy;
929
930 if ("latest".equals(range)) {
931 Container c = getBundleFromProject(bsn, attrs);
932 if (c != null)
933 return c;
934
935 useStrategy = Strategy.HIGHEST;
936 }
937
938 useStrategy = overrideStrategy(attrs, useStrategy);
939
940 List<RepositoryPlugin> plugins = workspace.getRepositories();
941
942 if (useStrategy == Strategy.EXACT) {
943
944 // For an exact range we just iterate over the repos
945 // and return the first we find.
946
947 for (RepositoryPlugin plugin : plugins) {
948 File result = plugin.get(bsn, range, Strategy.EXACT, attrs);
949 if (result != null)
950 return toContainer(bsn, range, attrs, result);
951 }
952 } else {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000953 VersionRange versionRange = "latest".equals(range) ? new VersionRange("0") : new VersionRange(range);
Stuart McCullochf3173222012-06-07 21:57:32 +0000954
955 // We have a range search. Gather all the versions in all the repos
956 // and make a decision on that choice. If the same version is found
957 // in
958 // multiple repos we take the first
959
Stuart McCulloch4482c702012-06-15 13:27:53 +0000960 SortedMap<Version,RepositoryPlugin> versions = new TreeMap<Version,RepositoryPlugin>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000961 for (RepositoryPlugin plugin : plugins) {
962 try {
963 List<Version> vs = plugin.versions(bsn);
964 if (vs != null) {
965 for (Version v : vs) {
966 if (!versions.containsKey(v) && versionRange.includes(v))
967 versions.put(v, plugin);
968 }
969 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000970 }
971 catch (UnsupportedOperationException ose) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000972 // We have a plugin that cannot list versions, try
973 // if it has this specific version
974 // The main reaosn for this code was the Maven Remote
975 // Repository
976 // To query, we must have a real version
977 if (!versions.isEmpty() && Verifier.isVersion(range)) {
978 File file = plugin.get(bsn, range, useStrategy, attrs);
979 // and the entry must exist
980 // if it does, return this as a result
981 if (file != null)
982 return toContainer(bsn, range, attrs, file);
983 }
984 }
985 }
986
987 // Verify if we found any, if so, we use the strategy to pick
988 // the first or last
989
990 if (!versions.isEmpty()) {
991 Version provider = null;
992
993 switch (useStrategy) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000994 case HIGHEST :
995 provider = versions.lastKey();
996 break;
Stuart McCullochf3173222012-06-07 21:57:32 +0000997
Stuart McCulloch4482c702012-06-15 13:27:53 +0000998 case LOWEST :
999 provider = versions.firstKey();
1000 break;
Stuart McCulloch1b98aa02012-06-18 11:15:15 +00001001 case EXACT :
1002 // TODO need to handle exact better
1003 break;
Stuart McCullochf3173222012-06-07 21:57:32 +00001004 }
1005 if (provider != null) {
1006 RepositoryPlugin repo = versions.get(provider);
1007 String version = provider.toString();
1008 File result = repo.get(bsn, version, Strategy.EXACT, attrs);
1009 if (result != null)
1010 return toContainer(bsn, version, attrs, result);
1011 } else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001012 msgs.FoundVersions_ForStrategy_ButNoProvider(versions, useStrategy);
Stuart McCullochf3173222012-06-07 21:57:32 +00001013 }
1014 }
1015
1016 //
1017 // If we get this far we ran into an error somewhere
1018
Stuart McCulloch4482c702012-06-15 13:27:53 +00001019 return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version=" + range + " Not found in "
1020 + plugins, null);
Stuart McCullochf3173222012-06-07 21:57:32 +00001021
1022 }
1023
1024 /**
1025 * @param attrs
1026 * @param useStrategy
1027 * @return
1028 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001029 protected Strategy overrideStrategy(Map<String,String> attrs, Strategy useStrategy) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001030 if (attrs != null) {
1031 String overrideStrategy = attrs.get("strategy");
1032
1033 if (overrideStrategy != null) {
1034 if ("highest".equalsIgnoreCase(overrideStrategy))
1035 useStrategy = Strategy.HIGHEST;
1036 else if ("lowest".equalsIgnoreCase(overrideStrategy))
1037 useStrategy = Strategy.LOWEST;
1038 else if ("exact".equalsIgnoreCase(overrideStrategy))
1039 useStrategy = Strategy.EXACT;
1040 }
1041 }
1042 return useStrategy;
1043 }
1044
1045 /**
1046 * @param bsn
1047 * @param range
1048 * @param attrs
1049 * @param result
1050 * @return
1051 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001052 protected Container toContainer(String bsn, String range, Map<String,String> attrs, File result) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001053 File f = result;
1054 if (f == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001055 msgs.ConfusedNoContainerFile();
Stuart McCullochf3173222012-06-07 21:57:32 +00001056 f = new File("was null");
1057 }
1058 if (f.getName().endsWith("lib"))
1059 return new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs);
Stuart McCulloch669423b2012-06-26 16:34:24 +00001060 return new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs);
Stuart McCullochf3173222012-06-07 21:57:32 +00001061 }
1062
1063 /**
1064 * Look for the bundle in the workspace. The premise is that the bsn must
1065 * start with the project name.
1066 *
1067 * @param bsn
1068 * The bsn
1069 * @param attrs
1070 * Any attributes
1071 * @return
1072 * @throws Exception
1073 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001074 private Container getBundleFromProject(String bsn, Map<String,String> attrs) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001075 String pname = bsn;
1076 while (true) {
1077 Project p = getWorkspace().getProject(pname);
1078 if (p != null && p.isValid()) {
1079 Container c = p.getDeliverable(bsn, attrs);
1080 return c;
1081 }
1082
1083 int n = pname.lastIndexOf('.');
1084 if (n <= 0)
1085 return null;
1086 pname = pname.substring(0, n);
1087 }
1088 }
1089
1090 /**
1091 * Deploy the file (which must be a bundle) into the repository.
1092 *
1093 * @param name
1094 * The repository name
1095 * @param file
1096 * bundle
1097 */
1098 public void deploy(String name, File file) throws Exception {
1099 List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
1100
1101 RepositoryPlugin rp = null;
1102 for (RepositoryPlugin plugin : plugins) {
1103 if (!plugin.canWrite()) {
1104 continue;
1105 }
1106 if (name == null) {
1107 rp = plugin;
1108 break;
1109 } else if (name.equals(plugin.getName())) {
1110 rp = plugin;
1111 break;
1112 }
1113 }
1114
1115 if (rp != null) {
1116 Jar jar = new Jar(file);
1117 try {
1118 rp.put(jar);
1119 return;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001120 }
1121 catch (Exception e) {
1122 msgs.DeployingFile_On_Exception_(file, rp.getName(), e);
1123 }
1124 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001125 jar.close();
1126 }
1127 return;
1128 }
1129 trace("No repo found " + file);
1130 throw new IllegalArgumentException("No repository found for " + file);
1131 }
1132
1133 /**
1134 * Deploy the file (which must be a bundle) into the repository.
1135 *
1136 * @param file
1137 * bundle
1138 */
1139 public void deploy(File file) throws Exception {
1140 String name = getProperty(Constants.DEPLOYREPO);
1141 deploy(name, file);
1142 }
1143
1144 /**
1145 * Deploy the current project to a repository
1146 *
1147 * @throws Exception
1148 */
1149 public void deploy() throws Exception {
1150 Parameters deploy = new Parameters(getProperty(DEPLOY));
1151 if (deploy.isEmpty()) {
1152 warning("Deploying but %s is not set to any repo", DEPLOY);
1153 return;
1154 }
1155 File[] outputs = getBuildFiles();
1156 for (File output : outputs) {
1157 Jar jar = new Jar(output);
1158 try {
1159 for (Deploy d : getPlugins(Deploy.class)) {
1160 trace("Deploying %s to: %s", jar, d);
1161 try {
1162 if (d.deploy(this, jar))
1163 trace("deployed %s successfully to %s", output, d);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001164 }
1165 catch (Exception e) {
1166 msgs.Deploying(e);
Stuart McCullochf3173222012-06-07 21:57:32 +00001167 }
1168 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001169 }
1170 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001171 jar.close();
1172 }
1173 }
1174 }
1175
1176 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001177 * Macro access to the repository ${repo;<bsn>[;<version>[;<low|high>]]}
Stuart McCullochf3173222012-06-07 21:57:32 +00001178 */
1179
Stuart McCulloch4482c702012-06-15 13:27:53 +00001180 static String _repoHelp = "${repo ';'<bsn> [ ; <version> [; ('HIGHEST'|'LOWEST')]}";
1181
Stuart McCullochf3173222012-06-07 21:57:32 +00001182 public String _repo(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001183 if (args.length < 2) {
1184 msgs.RepoTooFewArguments(_repoHelp, args);
1185 return null;
1186 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001187
1188 String bsns = args[1];
1189 String version = null;
1190 Strategy strategy = Strategy.HIGHEST;
1191
1192 if (args.length > 2) {
1193 version = args[2];
1194 if (args.length == 4) {
1195 if (args[3].equalsIgnoreCase("HIGHEST"))
1196 strategy = Strategy.HIGHEST;
1197 else if (args[3].equalsIgnoreCase("LOWEST"))
1198 strategy = Strategy.LOWEST;
1199 else if (args[3].equalsIgnoreCase("EXACT"))
1200 strategy = Strategy.EXACT;
1201 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001202 msgs.InvalidStrategy(_repoHelp, args);
Stuart McCullochf3173222012-06-07 21:57:32 +00001203 }
1204 }
1205
1206 Collection<String> parts = split(bsns);
1207 List<String> paths = new ArrayList<String>();
1208
1209 for (String bsn : parts) {
1210 Container container = getBundle(bsn, version, strategy, null);
1211 add(paths, container);
1212 }
1213 return join(paths);
1214 }
1215
1216 private void add(List<String> paths, Container container) throws Exception {
1217 if (container.getType() == Container.TYPE.LIBRARY) {
1218 List<Container> members = container.getMembers();
1219 for (Container sub : members) {
1220 add(paths, sub);
1221 }
1222 } else {
1223 if (container.getError() == null)
1224 paths.add(container.getFile().getAbsolutePath());
1225 else {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001226 paths.add("<<${repo} = " + container.getBundleSymbolicName() + "-" + container.getVersion() + " : "
1227 + container.getError() + ">>");
Stuart McCullochf3173222012-06-07 21:57:32 +00001228
1229 if (isPedantic()) {
1230 warning("Could not expand repo path request: %s ", container);
1231 }
1232 }
1233
1234 }
1235 }
1236
1237 public File getTarget() throws Exception {
1238 prepare();
1239 return target;
1240 }
1241
1242 /**
1243 * This is the external method that will pre-build any dependencies if it is
1244 * out of date.
1245 *
1246 * @param underTest
1247 * @return
1248 * @throws Exception
1249 */
1250 public File[] build(boolean underTest) throws Exception {
1251 if (isNoBundles())
1252 return null;
1253
1254 if (getProperty("-nope") != null) {
1255 warning("Please replace -nope with %s", NOBUNDLES);
1256 return null;
1257 }
1258
1259 if (isStale()) {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001260 trace("building " + this);
Stuart McCullochf3173222012-06-07 21:57:32 +00001261 files = buildLocal(underTest);
1262 }
1263
1264 return files;
1265 }
1266
1267 /**
1268 * Return the files
1269 */
1270
1271 public File[] getFiles() {
1272 return files;
1273 }
1274
1275 /**
1276 * Check if this project needs building. This is defined as:
Stuart McCullochf3173222012-06-07 21:57:32 +00001277 */
1278 public boolean isStale() throws Exception {
Stuart McCulloch1b98aa02012-06-18 11:15:15 +00001279 if ( workspace.isOffline()) {
1280 trace("working %s offline, so always stale", this);
1281 return true;
1282 }
1283
Stuart McCulloch4482c702012-06-15 13:27:53 +00001284 Set<Project> visited = new HashSet<Project>();
1285 return isStale(visited);
1286 }
1287
1288 boolean isStale(Set<Project> visited) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001289 // When we do not generate anything ...
1290 if (isNoBundles())
1291 return false;
1292
Stuart McCulloch4482c702012-06-15 13:27:53 +00001293 if (visited.contains(this)) {
1294 msgs.CircularDependencyContext_Message_(this.getName(), visited.toString());
1295 return false;
1296 }
1297
1298 visited.add(this);
1299
Stuart McCullochf3173222012-06-07 21:57:32 +00001300 long buildTime = 0;
1301
1302 files = getBuildFiles(false);
1303 if (files == null)
1304 return true;
1305
1306 for (File f : files) {
1307 if (f.lastModified() < lastModified())
1308 return true;
1309
1310 if (buildTime < f.lastModified())
1311 buildTime = f.lastModified();
1312 }
1313
1314 for (Project dependency : getDependson()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001315 if (dependency == this)
1316 continue;
1317
Stuart McCullochf3173222012-06-07 21:57:32 +00001318 if (dependency.isStale())
1319 return true;
1320
1321 if (dependency.isNoBundles())
1322 continue;
1323
1324 File[] deps = dependency.getBuildFiles();
1325 for (File f : deps) {
1326 if (f.lastModified() >= buildTime)
1327 return true;
1328 }
1329 }
Stuart McCulloch1b98aa02012-06-18 11:15:15 +00001330
Stuart McCullochf3173222012-06-07 21:57:32 +00001331 return false;
1332 }
1333
1334 /**
1335 * This method must only be called when it is sure that the project has been
Stuart McCulloch4482c702012-06-15 13:27:53 +00001336 * build before in the same session. It is a bit yucky, but ant creates
1337 * different class spaces which makes it hard to detect we already build it.
Stuart McCullochf3173222012-06-07 21:57:32 +00001338 * This method remembers the files in the appropriate instance vars.
1339 *
1340 * @return
1341 */
1342
1343 public File[] getBuildFiles() throws Exception {
1344 return getBuildFiles(true);
1345 }
1346
1347 public File[] getBuildFiles(boolean buildIfAbsent) throws Exception {
1348 if (files != null)
1349 return files;
1350
1351 File f = new File(getTarget(), BUILDFILES);
1352 if (f.isFile()) {
1353 BufferedReader rdr = IO.reader(f);
1354 try {
1355 List<File> files = newList();
1356 for (String s = rdr.readLine(); s != null; s = rdr.readLine()) {
1357 s = s.trim();
1358 File ff = new File(s);
1359 if (!ff.isFile()) {
1360 // Originally we warned the user
1361 // but lets just rebuild. That way
Stuart McCulloch4482c702012-06-15 13:27:53 +00001362 // the error is not noticed but
Stuart McCullochf3173222012-06-07 21:57:32 +00001363 // it seems better to correct,
1364 // See #154
1365 rdr.close();
1366 f.delete();
1367 break;
Stuart McCulloch669423b2012-06-26 16:34:24 +00001368 }
1369 files.add(ff);
Stuart McCullochf3173222012-06-07 21:57:32 +00001370 }
1371 return this.files = files.toArray(new File[files.size()]);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001372 }
1373 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001374 rdr.close();
1375 }
1376 }
1377 if (buildIfAbsent)
1378 return files = buildLocal(false);
Stuart McCulloch669423b2012-06-26 16:34:24 +00001379 return files = null;
Stuart McCullochf3173222012-06-07 21:57:32 +00001380 }
1381
1382 /**
1383 * Build without doing any dependency checking. Make sure any dependent
1384 * projects are built first.
1385 *
1386 * @param underTest
1387 * @return
1388 * @throws Exception
1389 */
1390 public File[] buildLocal(boolean underTest) throws Exception {
1391 if (isNoBundles())
1392 return null;
1393
1394 File bfs = new File(getTarget(), BUILDFILES);
1395 bfs.delete();
1396
1397 files = null;
1398 ProjectBuilder builder = getBuilder(null);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001399 try {
1400 if (underTest)
1401 builder.setProperty(Constants.UNDERTEST, "true");
1402 Jar jars[] = builder.builds();
1403 File[] files = new File[jars.length];
Stuart McCullochf3173222012-06-07 21:57:32 +00001404
Stuart McCulloch4482c702012-06-15 13:27:53 +00001405 getInfo(builder);
Stuart McCullochf3173222012-06-07 21:57:32 +00001406
Stuart McCulloch4482c702012-06-15 13:27:53 +00001407 if (isOk()) {
1408 this.files = files;
1409
1410 for (int i = 0; i < jars.length; i++) {
1411 Jar jar = jars[i];
1412 files[i] = saveBuild(jar);
Stuart McCullochf3173222012-06-07 21:57:32 +00001413 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001414
1415 // Write out the filenames in the buildfiles file
1416 // so we can get them later evenin another process
1417 Writer fw = IO.writer(bfs);
1418 try {
1419 for (File f : files) {
1420 fw.append(f.getAbsolutePath());
1421 fw.append("\n");
1422 }
1423 }
1424 finally {
1425 fw.close();
1426 }
1427 getWorkspace().changedFile(bfs);
1428 return files;
Stuart McCulloch669423b2012-06-26 16:34:24 +00001429 }
1430 return null;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001431 }
1432 finally {
1433 builder.close();
1434 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001435 }
1436
1437 /**
1438 * Answer if this project does not have any output
1439 *
1440 * @return
1441 */
1442 public boolean isNoBundles() {
1443 return getProperty(NOBUNDLES) != null;
1444 }
1445
1446 public File saveBuild(Jar jar) throws Exception {
1447 try {
1448 String bsn = jar.getName();
1449 File f = getOutputFile(bsn);
1450 String msg = "";
1451 if (!f.exists() || f.lastModified() < jar.lastModified()) {
1452 reportNewer(f.lastModified(), jar);
1453 f.delete();
1454 if (!f.getParentFile().isDirectory())
1455 f.getParentFile().mkdirs();
1456 jar.write(f);
1457
1458 getWorkspace().changedFile(f);
1459 } else {
1460 msg = "(not modified since " + new Date(f.lastModified()) + ")";
1461 }
1462 trace(jar.getName() + " (" + f.getName() + ") " + jar.getResources().size() + " " + msg);
1463 return f;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001464 }
1465 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001466 jar.close();
1467 }
1468 }
1469
1470 public File getOutputFile(String bsn) throws Exception {
1471 return new File(getTarget(), bsn + ".jar");
1472 }
1473
1474 private void reportNewer(long lastModified, Jar jar) {
1475 if (isTrue(getProperty(Constants.REPORTNEWER))) {
1476 StringBuilder sb = new StringBuilder();
1477 String del = "Newer than " + new Date(lastModified);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001478 for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001479 if (entry.getValue().lastModified() > lastModified) {
1480 sb.append(del);
1481 del = ", \n ";
1482 sb.append(entry.getKey());
1483 }
1484 }
1485 if (sb.length() > 0)
1486 warning(sb.toString());
1487 }
1488 }
1489
1490 /**
1491 * Refresh if we are based on stale data. This also implies our workspace.
1492 */
1493 public boolean refresh() {
1494 boolean changed = false;
1495 if (isCnf()) {
1496 changed = workspace.refresh();
1497 }
1498 return super.refresh() || changed;
1499 }
1500
1501 public boolean isCnf() {
1502 return getBase().getName().equals(Workspace.CNFDIR);
1503 }
1504
1505 public void propertiesChanged() {
1506 super.propertiesChanged();
1507 preparedPaths = false;
1508 files = null;
1509
1510 }
1511
1512 public String getName() {
1513 return getBase().getName();
1514 }
1515
Stuart McCulloch4482c702012-06-15 13:27:53 +00001516 public Map<String,Action> getActions() {
1517 Map<String,Action> all = newMap();
1518 Map<String,Action> actions = newMap();
Stuart McCullochf3173222012-06-07 21:57:32 +00001519 fillActions(all);
1520 getWorkspace().fillActions(all);
1521
Stuart McCulloch4482c702012-06-15 13:27:53 +00001522 for (Map.Entry<String,Action> action : all.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001523 String key = getReplacer().process(action.getKey());
1524 if (key != null && key.trim().length() != 0)
1525 actions.put(key, action.getValue());
1526 }
1527 return actions;
1528 }
1529
Stuart McCulloch4482c702012-06-15 13:27:53 +00001530 public void fillActions(Map<String,Action> all) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001531 List<NamedAction> plugins = getPlugins(NamedAction.class);
1532 for (NamedAction a : plugins)
1533 all.put(a.getName(), a);
1534
1535 Parameters actions = new Parameters(getProperty("-actions", DEFAULT_ACTIONS));
Stuart McCulloch4482c702012-06-15 13:27:53 +00001536 for (Entry<String,Attrs> entry : actions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001537 String key = Processor.removeDuplicateMarker(entry.getKey());
1538 Action action;
1539
1540 if (entry.getValue().get("script") != null) {
1541 // TODO check for the type
Stuart McCulloch4482c702012-06-15 13:27:53 +00001542 action = new ScriptAction(entry.getValue().get("type"), entry.getValue().get("script"));
Stuart McCullochf3173222012-06-07 21:57:32 +00001543 } else {
1544 action = new ReflectAction(key);
1545 }
1546 String label = entry.getValue().get("label");
1547 all.put(label.toLowerCase(), action);
1548 }
1549 }
1550
1551 public void release() throws Exception {
1552 release(false);
1553 }
1554
1555 /**
1556 * Release.
1557 *
1558 * @param name
1559 * The repository name
1560 * @throws Exception
1561 */
1562 public void release(String name) throws Exception {
1563 release(name, false);
1564 }
1565
1566 public void clean() throws Exception {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001567 File target = getTarget0();
Stuart McCullochf3173222012-06-07 21:57:32 +00001568 if (target.isDirectory() && target.getParentFile() != null) {
1569 IO.delete(target);
1570 target.mkdirs();
1571 }
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001572 File output = getOutput0();
Stuart McCullochf3173222012-06-07 21:57:32 +00001573 if (getOutput().isDirectory())
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001574 IO.delete(output);
1575 output.mkdirs();
Stuart McCullochf3173222012-06-07 21:57:32 +00001576 }
1577
1578 public File[] build() throws Exception {
1579 return build(false);
1580 }
1581
1582 public void run() throws Exception {
1583 ProjectLauncher pl = getProjectLauncher();
1584 pl.setTrace(isTrace());
1585 pl.launch();
1586 }
1587
1588 public void test() throws Exception {
1589 clear();
1590 ProjectTester tester = getProjectTester();
1591 tester.setContinuous(isTrue(getProperty(Constants.TESTCONTINUOUS)));
1592 tester.prepare();
1593
1594 if (!isOk()) {
1595 return;
1596 }
1597 int errors = tester.test();
1598 if (errors == 0) {
1599 System.err.println("No Errors");
1600 } else {
1601 if (errors > 0) {
1602 System.err.println(errors + " Error(s)");
1603
1604 } else
1605 System.err.println("Error " + errors);
1606 }
1607 }
1608
1609 /**
1610 * This methods attempts to turn any jar into a valid jar. If this is a
1611 * bundle with manifest, a manifest is added based on defaults. If it is a
1612 * bundle, but not r4, we try to add the r4 headers.
1613 *
1614 * @param descriptor
1615 * @param in
1616 * @return
1617 * @throws Exception
1618 */
1619 public Jar getValidJar(File f) throws Exception {
1620 Jar jar = new Jar(f);
1621 return getValidJar(jar, f.getAbsolutePath());
1622 }
1623
1624 public Jar getValidJar(URL url) throws Exception {
1625 InputStream in = url.openStream();
1626 try {
1627 Jar jar = new Jar(url.getFile().replace('/', '.'), in, System.currentTimeMillis());
1628 return getValidJar(jar, url.toString());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001629 }
1630 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001631 in.close();
1632 }
1633 }
1634
1635 public Jar getValidJar(Jar jar, String id) throws Exception {
1636 Manifest manifest = jar.getManifest();
1637 if (manifest == null) {
1638 trace("Wrapping with all defaults");
1639 Builder b = new Builder(this);
1640 b.addClasspath(jar);
1641 b.setProperty("Bnd-Message", "Wrapped from " + id + "because lacked manifest");
1642 b.setProperty(Constants.EXPORT_PACKAGE, "*");
1643 b.setProperty(Constants.IMPORT_PACKAGE, "*;resolution:=optional");
1644 jar = b.build();
1645 } else if (manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION) == null) {
1646 trace("Not a release 4 bundle, wrapping with manifest as source");
1647 Builder b = new Builder(this);
1648 b.addClasspath(jar);
1649 b.setProperty(Constants.PRIVATE_PACKAGE, "*");
1650 b.mergeManifest(manifest);
1651 String imprts = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
1652 if (imprts == null)
1653 imprts = "";
1654 else
1655 imprts += ",";
1656 imprts += "*;resolution=optional";
1657
1658 b.setProperty(Constants.IMPORT_PACKAGE, imprts);
1659 b.setProperty("Bnd-Message", "Wrapped from " + id + "because had incomplete manifest");
1660 jar = b.build();
1661 }
1662 return jar;
1663 }
1664
Stuart McCulloch669423b2012-06-26 16:34:24 +00001665 public String _project(@SuppressWarnings("unused") String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001666 return getBase().getAbsolutePath();
1667 }
1668
1669 /**
1670 * Bump the version of this project. First check the main bnd file. If this
1671 * does not contain a version, check the include files. If they still do not
1672 * contain a version, then check ALL the sub builders. If not, add a version
1673 * to the main bnd file.
1674 *
1675 * @param mask
1676 * the mask for bumping, see {@link Macro#_version(String[])}
1677 * @throws Exception
1678 */
1679 public void bump(String mask) throws Exception {
1680 String pattern = "(Bundle-Version\\s*(:|=)\\s*)(([0-9]+(\\.[0-9]+(\\.[0-9]+)?)?))";
1681 String replace = "$1${version;" + mask + ";$3}";
1682 try {
1683 // First try our main bnd file
1684 if (replace(getPropertiesFile(), pattern, replace))
1685 return;
1686
1687 trace("no version in bnd.bnd");
1688
1689 // Try the included filed in reverse order (last has highest
1690 // priority)
1691 List<File> included = getIncluded();
1692 if (included != null) {
1693 List<File> copy = new ArrayList<File>(included);
1694 Collections.reverse(copy);
1695
1696 for (File file : copy) {
1697 if (replace(file, pattern, replace)) {
1698 trace("replaced version in file %s", file);
1699 return;
1700 }
1701 }
1702 }
1703 trace("no version in included files");
1704
1705 boolean found = false;
1706
1707 // Replace in all sub builders.
1708 for (Builder sub : getSubBuilders()) {
1709 found |= replace(sub.getPropertiesFile(), pattern, replace);
1710 }
1711
1712 if (!found) {
1713 trace("no version in sub builders, add it to bnd.bnd");
1714 String bndfile = IO.collect(getPropertiesFile());
1715 bndfile += "\n# Added by by bump\nBundle-Version: 0.0.0\n";
1716 IO.store(bndfile, getPropertiesFile());
1717 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001718 }
1719 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001720 forceRefresh();
1721 }
1722 }
1723
1724 boolean replace(File f, String pattern, String replacement) throws IOException {
Stuart McCullochb32291a2012-07-16 14:10:57 +00001725 final Macro macro = getReplacer();
1726 Sed sed = new Sed( new Replacer() {
1727 public String process(String line) {
1728 return macro.process(line);
1729 }
1730 }, f);
Stuart McCullochf3173222012-06-07 21:57:32 +00001731 sed.replace(pattern, replacement);
1732 return sed.doIt() > 0;
1733 }
1734
1735 public void bump() throws Exception {
1736 bump(getProperty(BUMPPOLICY, "=+0"));
1737 }
1738
1739 public void action(String command) throws Throwable {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001740 Map<String,Action> actions = getActions();
Stuart McCullochf3173222012-06-07 21:57:32 +00001741
1742 Action a = actions.get(command);
1743 if (a == null)
1744 a = new ReflectAction(command);
1745
1746 before(this, command);
1747 try {
1748 a.execute(this, command);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001749 }
1750 catch (Throwable t) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001751 after(this, command, t);
1752 throw t;
1753 }
1754 }
1755
1756 /**
1757 * Run all before command plugins
Stuart McCullochf3173222012-06-07 21:57:32 +00001758 */
Stuart McCulloch669423b2012-06-26 16:34:24 +00001759 void before(@SuppressWarnings("unused") Project p, String a) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001760 List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
1761 for (CommandPlugin testPlugin : testPlugins) {
1762 testPlugin.before(this, a);
1763 }
1764 }
1765
1766 /**
1767 * Run all after command plugins
1768 */
Stuart McCulloch669423b2012-06-26 16:34:24 +00001769 void after(@SuppressWarnings("unused") Project p, String a, Throwable t) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001770 List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
1771 for (int i = testPlugins.size() - 1; i >= 0; i--) {
1772 testPlugins.get(i).after(this, a, t);
1773 }
1774 }
1775
1776 public String _findfile(String args[]) {
1777 File f = getFile(args[1]);
1778 List<String> files = new ArrayList<String>();
1779 tree(files, f, "", new Instruction(args[2]));
1780 return join(files);
1781 }
1782
1783 void tree(List<String> list, File current, String path, Instruction instr) {
1784 if (path.length() > 0)
1785 path = path + "/";
1786
1787 String subs[] = current.list();
1788 if (subs != null) {
1789 for (String sub : subs) {
1790 File f = new File(current, sub);
1791 if (f.isFile()) {
1792 if (instr.matches(sub) && !instr.isNegated())
1793 list.add(path + sub);
1794 } else
1795 tree(list, f, path + sub, instr);
1796 }
1797 }
1798 }
1799
1800 public void refreshAll() {
1801 workspace.refresh();
1802 refresh();
1803 }
1804
Stuart McCulloch4482c702012-06-15 13:27:53 +00001805 @SuppressWarnings("unchecked")
Stuart McCulloch669423b2012-06-26 16:34:24 +00001806 public void script(@SuppressWarnings("unused") String type, String script) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001807 // TODO check tyiping
1808 List<Scripter> scripters = getPlugins(Scripter.class);
1809 if (scripters.isEmpty()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001810 msgs.NoScripters_(script);
Stuart McCullochf3173222012-06-07 21:57:32 +00001811 return;
1812 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001813 @SuppressWarnings("rawtypes")
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001814 Map x = getProperties();
1815 scripters.get(0).eval(x, new StringReader(script));
Stuart McCullochf3173222012-06-07 21:57:32 +00001816 }
1817
Stuart McCulloch669423b2012-06-26 16:34:24 +00001818 public String _repos(@SuppressWarnings("unused") String args[]) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001819 List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
1820 List<String> names = new ArrayList<String>();
1821 for (RepositoryPlugin rp : repos)
1822 names.add(rp.getName());
1823 return join(names, ", ");
1824 }
1825
1826 public String _help(String args[]) throws Exception {
1827 if (args.length == 1)
1828 return "Specify the option or header you want information for";
1829
1830 Syntax syntax = Syntax.HELP.get(args[1]);
1831 if (syntax == null)
1832 return "No help for " + args[1];
1833
1834 String what = null;
1835 if (args.length > 2)
1836 what = args[2];
1837
1838 if (what == null || what.equals("lead"))
1839 return syntax.getLead();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001840 if (what.equals("example"))
Stuart McCullochf3173222012-06-07 21:57:32 +00001841 return syntax.getExample();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001842 if (what.equals("pattern"))
Stuart McCullochf3173222012-06-07 21:57:32 +00001843 return syntax.getPattern();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001844 if (what.equals("values"))
Stuart McCullochf3173222012-06-07 21:57:32 +00001845 return syntax.getValues();
1846
1847 return "Invalid type specified for help: lead, example, pattern, values";
1848 }
1849
1850 /**
1851 * Returns containers for the deliverables of this project. The deliverables
1852 * is the project builder for this project if no -sub is specified.
1853 * Otherwise it contains all the sub bnd files.
1854 *
1855 * @return A collection of containers
Stuart McCullochf3173222012-06-07 21:57:32 +00001856 * @throws Exception
1857 */
1858 public Collection<Container> getDeliverables() throws Exception {
1859 List<Container> result = new ArrayList<Container>();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001860 Collection< ? extends Builder> builders = getSubBuilders();
Stuart McCullochf3173222012-06-07 21:57:32 +00001861
1862 for (Builder builder : builders) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001863 Container c = new Container(this, builder.getBsn(), builder.getVersion(), Container.TYPE.PROJECT,
1864 getOutputFile(builder.getBsn()), null, null);
Stuart McCullochf3173222012-06-07 21:57:32 +00001865 result.add(c);
1866 }
1867 return result;
1868
1869 }
1870
1871 /**
1872 * Return the builder associated with the give bnd file or null. The bnd.bnd
1873 * file can contain -sub option. This option allows specifying files in the
1874 * same directory that should drive the generation of multiple deliverables.
1875 * This method figures out if the bndFile is actually one of the bnd files
1876 * of a deliverable.
1877 *
1878 * @param bndFile
1879 * A file pointing to a bnd file.
1880 * @return null or the builder for a sub file.
1881 * @throws Exception
1882 */
1883 public Builder getSubBuilder(File bndFile) throws Exception {
1884 bndFile = bndFile.getCanonicalFile();
1885
1886 // Verify that we are inside the project.
1887 File base = getBase().getCanonicalFile();
1888 if (!bndFile.getAbsolutePath().startsWith(base.getAbsolutePath()))
1889 return null;
1890
Stuart McCulloch4482c702012-06-15 13:27:53 +00001891 Collection< ? extends Builder> builders = getSubBuilders();
Stuart McCullochf3173222012-06-07 21:57:32 +00001892 for (Builder sub : builders) {
1893 File propertiesFile = sub.getPropertiesFile();
1894 if (propertiesFile != null) {
1895 if (propertiesFile.getCanonicalFile().equals(bndFile)) {
1896 // Found it!
1897 return sub;
1898 }
1899 }
1900 }
1901 return null;
1902 }
1903
1904 /**
1905 * Answer the container associated with a given bsn.
1906 *
1907 * @param bndFile
1908 * A file pointing to a bnd file.
1909 * @return null or the builder for a sub file.
1910 * @throws Exception
1911 */
Stuart McCulloch669423b2012-06-26 16:34:24 +00001912 public Container getDeliverable(String bsn, @SuppressWarnings("unused") Map<String,String> attrs) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001913 Collection< ? extends Builder> builders = getSubBuilders();
Stuart McCullochf3173222012-06-07 21:57:32 +00001914 for (Builder sub : builders) {
1915 if (sub.getBsn().equals(bsn))
1916 return new Container(this, getOutputFile(bsn));
1917 }
1918 return null;
1919 }
1920
1921 /**
1922 * Get a list of the sub builders. A bnd.bnd file can contain the -sub
1923 * option. This will generate multiple deliverables. This method returns the
1924 * builders for each sub file. If no -sub option is present, the list will
1925 * contain a builder for the bnd.bnd file.
1926 *
1927 * @return A list of builders.
1928 * @throws Exception
1929 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001930 public Collection< ? extends Builder> getSubBuilders() throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001931 return getBuilder(null).getSubBuilders();
1932 }
1933
1934 /**
1935 * Calculate the classpath. We include our own runtime.jar which includes
1936 * the test framework and we include the first of the test frameworks
1937 * specified.
1938 *
1939 * @throws Exception
1940 */
1941 Collection<File> toFile(Collection<Container> containers) throws Exception {
1942 ArrayList<File> files = new ArrayList<File>();
1943 for (Container container : containers) {
1944 container.contributeFiles(files, this);
1945 }
1946 return files;
1947 }
1948
1949 public Collection<String> getRunVM() {
1950 Parameters hdr = getParameters(RUNVM);
1951 return hdr.keySet();
1952 }
1953
Stuart McCulloch4482c702012-06-15 13:27:53 +00001954 public Map<String,String> getRunProperties() {
Stuart McCullochf3173222012-06-07 21:57:32 +00001955 return OSGiHeader.parseProperties(getProperty(RUNPROPERTIES));
1956 }
1957
1958 /**
1959 * Get a launcher.
1960 *
1961 * @return
1962 * @throws Exception
1963 */
1964 public ProjectLauncher getProjectLauncher() throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001965 return getHandler(ProjectLauncher.class, getRunpath(), LAUNCHER_PLUGIN, "biz.aQute.launcher");
Stuart McCullochf3173222012-06-07 21:57:32 +00001966 }
1967
1968 public ProjectTester getProjectTester() throws Exception {
1969 return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN, "biz.aQute.junit");
1970 }
1971
Stuart McCulloch4482c702012-06-15 13:27:53 +00001972 private <T> T getHandler(Class<T> target, Collection<Container> containers, String header, String defaultHandler)
1973 throws Exception {
1974 Class< ? extends T> handlerClass = target;
Stuart McCullochf3173222012-06-07 21:57:32 +00001975
1976 // Make sure we find at least one handler, but hope to find an earlier
1977 // one
1978 List<Container> withDefault = Create.list();
1979 withDefault.addAll(containers);
1980 withDefault.addAll(getBundles(Strategy.HIGHEST, defaultHandler, null));
1981 trace("candidates for tester %s", withDefault);
1982
1983 for (Container c : withDefault) {
1984 Manifest manifest = c.getManifest();
1985
1986 if (manifest != null) {
1987 String launcher = manifest.getMainAttributes().getValue(header);
1988 if (launcher != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001989 Class< ? > clz = getClass(launcher, c.getFile());
Stuart McCullochf3173222012-06-07 21:57:32 +00001990 if (clz != null) {
1991 if (!target.isAssignableFrom(clz)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001992 msgs.IncompatibleHandler_For_(launcher, defaultHandler);
Stuart McCullochf3173222012-06-07 21:57:32 +00001993 } else {
1994 handlerClass = clz.asSubclass(target);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001995 Constructor< ? extends T> constructor = handlerClass.getConstructor(Project.class);
Stuart McCullochf3173222012-06-07 21:57:32 +00001996 return constructor.newInstance(this);
1997 }
1998 }
1999 }
2000 }
2001 }
2002
Stuart McCulloch4482c702012-06-15 13:27:53 +00002003 throw new IllegalArgumentException("Default handler for " + header + " not found in " + defaultHandler);
Stuart McCullochf3173222012-06-07 21:57:32 +00002004 }
2005
2006 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00002007 * Make this project delay the calculation of the run dependencies. The run
2008 * dependencies calculation can be done in prepare or until the dependencies
2009 * are actually needed.
Stuart McCullochf3173222012-06-07 21:57:32 +00002010 */
2011 public void setDelayRunDependencies(boolean x) {
2012 delayRunDependencies = x;
2013 }
2014
2015 /**
2016 * Sets the package version on an exported package
2017 *
2018 * @param packageName
2019 * The package name
2020 * @param version
2021 * The new package version
2022 */
2023 public void setPackageInfo(String packageName, Version version) {
2024 try {
2025 updatePackageInfoFile(packageName, version);
Stuart McCulloch4482c702012-06-15 13:27:53 +00002026 }
2027 catch (Exception e) {
2028 msgs.SettingPackageInfoException_(e);
Stuart McCullochf3173222012-06-07 21:57:32 +00002029 }
2030 }
2031
2032 void updatePackageInfoFile(String packageName, Version newVersion) throws Exception {
2033
2034 File file = getPackageInfoFile(packageName);
2035
2036 // If package/classes are copied into the bundle through Private-Package
2037 // etc, there will be no source
2038 if (!file.getParentFile().exists()) {
2039 return;
2040 }
2041
2042 Version oldVersion = getPackageInfo(packageName);
2043
2044 if (newVersion.compareTo(oldVersion) == 0) {
2045 return;
Stuart McCullochf3173222012-06-07 21:57:32 +00002046 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00002047 PrintWriter pw = IO.writer(file);
2048 pw.println("version " + newVersion);
2049 pw.flush();
2050 pw.close();
2051
2052 String path = packageName.replace('.', '/') + "/packageinfo";
2053 File binary = IO.getFile(getOutput(), path);
2054 binary.getParentFile().mkdirs();
2055 IO.copy(file, binary);
2056
2057 refresh();
Stuart McCullochf3173222012-06-07 21:57:32 +00002058 }
2059
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00002060 File getPackageInfoFile(String packageName) {
Stuart McCullochf3173222012-06-07 21:57:32 +00002061 String path = packageName.replace('.', '/') + "/packageinfo";
2062 return IO.getFile(getSrc(), path);
2063
2064 }
2065
2066 public Version getPackageInfo(String packageName) throws IOException {
2067 File packageInfoFile = getPackageInfoFile(packageName);
2068 if (!packageInfoFile.exists()) {
2069 return Version.emptyVersion;
2070 }
2071 BufferedReader reader = null;
2072 try {
2073 reader = IO.reader(packageInfoFile);
2074 String line;
2075 while ((line = reader.readLine()) != null) {
2076 line = line.trim();
2077 if (line.startsWith("version ")) {
2078 return Version.parseVersion(line.substring(8));
2079 }
2080 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002081 }
2082 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00002083 if (reader != null) {
2084 IO.close(reader);
2085 }
2086 }
2087 return Version.emptyVersion;
2088 }
2089
2090 /**
2091 * bnd maintains a class path that is set by the environment, i.e. bnd is
2092 * not in charge of it.
2093 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00002094
2095 public void addClasspath(File f) {
2096 if (!f.isFile() && !f.isDirectory()) {
2097 msgs.AddingNonExistentFileToClassPath_(f);
Stuart McCullochf3173222012-06-07 21:57:32 +00002098 }
2099 Container container = new Container(f);
2100 classpath.add(container);
2101 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002102
Stuart McCullochf3173222012-06-07 21:57:32 +00002103 public void clearClasspath() {
2104 classpath.clear();
2105 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002106
Stuart McCullochf3173222012-06-07 21:57:32 +00002107 public Collection<Container> getClasspath() {
2108 return classpath;
2109 }
2110}