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