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