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