blob: b6377c660fad71271b494e602f344c82a7fc104a [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.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000010import java.util.regex.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000011
Stuart McCulloch42151ee2012-07-16 13:43:38 +000012import aQute.bnd.header.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000013import aQute.bnd.help.*;
14import aQute.bnd.maven.support.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000015import aQute.bnd.osgi.*;
16import aQute.bnd.osgi.eclipse.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000017import aQute.bnd.service.*;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +000018import aQute.bnd.service.RepositoryPlugin.PutResult;
Stuart McCullochf3173222012-06-07 21:57:32 +000019import aQute.bnd.service.action.*;
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000020import aQute.bnd.version.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000021import aQute.lib.io.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000022import aQute.libg.generics.*;
Stuart McCulloch4482c702012-06-15 13:27:53 +000023import aQute.libg.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000024import aQute.libg.sed.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000025
26/**
27 * This class is NOT threadsafe
Stuart McCullochf3173222012-06-07 21:57:32 +000028 */
29
30public class Project extends Processor {
31
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000032 final static Pattern VERSION_ANNOTATION = Pattern.compile("@\\s*(:?aQute\\.bnd\\.annotation\\.)?Version\\s*\\(\\s*(:?value\\s*=\\s*)?\"(\\d+(:?\\.\\d+(:?\\.\\d+(:?\\.[\\d\\w-_]+)?)?)?)\"\\s*\\)");
Stuart McCullochf3173222012-06-07 21:57:32 +000033 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";
34 public final static String BNDFILE = "bnd.bnd";
35 public final static String BNDCNF = "cnf";
36 final Workspace workspace;
37 boolean preparedPaths;
38 final Collection<Project> dependson = new LinkedHashSet<Project>();
39 final Collection<Container> classpath = new LinkedHashSet<Container>();
40 final Collection<Container> buildpath = new LinkedHashSet<Container>();
41 final Collection<Container> testpath = new LinkedHashSet<Container>();
42 final Collection<Container> runpath = new LinkedHashSet<Container>();
43 final Collection<Container> runbundles = new LinkedHashSet<Container>();
44 File runstorage;
45 final Collection<File> sourcepath = new LinkedHashSet<File>();
46 final Collection<File> allsourcepath = new LinkedHashSet<File>();
47 final Collection<Container> bootclasspath = new LinkedHashSet<Container>();
48 final Lock lock = new ReentrantLock(true);
49 volatile String lockingReason;
50 volatile Thread lockingThread;
51 File output;
52 File target;
53 boolean inPrepare;
54 int revision;
55 File files[];
56 static List<Project> trail = new ArrayList<Project>();
57 boolean delayRunDependencies = false;
Stuart McCulloch4482c702012-06-15 13:27:53 +000058 final ProjectMessages msgs = ReporterMessages.base(this, ProjectMessages.class);
Stuart McCullochf3173222012-06-07 21:57:32 +000059
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000060 public Project(Workspace workspace, File projectDir, File buildFile) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +000061 super(workspace);
62 this.workspace = workspace;
63 setFileMustExist(false);
64 setProperties(buildFile);
65 assert workspace != null;
66 // For backward compatibility reasons, we also read
67 readBuildProperties();
68 }
69
70 public Project(Workspace workspace, File buildDir) throws Exception {
71 this(workspace, buildDir, new File(buildDir, BNDFILE));
72 }
73
74 private void readBuildProperties() throws Exception {
75 try {
76 File f = getFile("build.properties");
77 if (f.isFile()) {
78 Properties p = loadProperties(f);
Stuart McCulloch4482c702012-06-15 13:27:53 +000079 for (Enumeration< ? > e = p.propertyNames(); e.hasMoreElements();) {
Stuart McCullochf3173222012-06-07 21:57:32 +000080 String key = (String) e.nextElement();
81 String newkey = key;
82 if (key.indexOf('$') >= 0) {
83 newkey = getReplacer().process(key);
84 }
85 setProperty(newkey, p.getProperty(key));
86 }
87 }
Stuart McCulloch4482c702012-06-15 13:27:53 +000088 }
89 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +000090 e.printStackTrace();
91 }
92 }
93
94 public static Project getUnparented(File propertiesFile) throws Exception {
95 propertiesFile = propertiesFile.getAbsoluteFile();
96 Workspace workspace = new Workspace(propertiesFile.getParentFile());
97 Project project = new Project(workspace, propertiesFile.getParentFile());
98 project.setProperties(propertiesFile);
99 project.setFileMustExist(true);
100 return project;
101 }
102
103 public synchronized boolean isValid() {
104 return getBase().isDirectory() && getPropertiesFile().isFile();
105 }
106
107 /**
108 * Return a new builder that is nicely setup for this project. Please close
109 * this builder after use.
110 *
111 * @param parent
112 * The project builder to use as parent, use this project if null
113 * @return
114 * @throws Exception
115 */
116 public synchronized ProjectBuilder getBuilder(ProjectBuilder parent) throws Exception {
117
118 ProjectBuilder builder;
119
120 if (parent == null)
121 builder = new ProjectBuilder(this);
122 else
123 builder = new ProjectBuilder(parent);
124
125 builder.setBase(getBase());
Stuart McCulloch669423b2012-06-26 16:34:24 +0000126 builder.setPedantic(isPedantic());
127 builder.setTrace(isTrace());
Stuart McCullochf3173222012-06-07 21:57:32 +0000128 return builder;
129 }
130
131 public synchronized int getChanged() {
132 return revision;
133 }
134
135 /*
136 * Indicate a change in the external world that affects our build. This will
137 * clear any cached results.
138 */
139 public synchronized void setChanged() {
140 // if (refresh()) {
141 preparedPaths = false;
142 files = null;
143 revision++;
144 // }
145 }
146
147 public Workspace getWorkspace() {
148 return workspace;
149 }
150
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000151 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000152 public String toString() {
153 return getBase().getName();
154 }
155
156 /**
157 * Set up all the paths
158 */
159
160 public synchronized void prepare() throws Exception {
161 if (!isValid()) {
162 warning("Invalid project attempts to prepare: %s", this);
163 return;
164 }
165
166 if (inPrepare)
167 throw new CircularDependencyException(trail.toString() + "," + this);
168
169 trail.add(this);
170 try {
171 if (!preparedPaths) {
172 inPrepare = true;
173 try {
174 dependson.clear();
175 buildpath.clear();
176 sourcepath.clear();
177 allsourcepath.clear();
178 bootclasspath.clear();
179 testpath.clear();
180 runpath.clear();
181 runbundles.clear();
182
183 // We use a builder to construct all the properties for
184 // use.
185 setProperty("basedir", getBase().getAbsolutePath());
186
187 // If a bnd.bnd file exists, we read it.
188 // Otherwise, we just do the build properties.
189 if (!getPropertiesFile().isFile() && new File(getBase(), ".classpath").isFile()) {
190 // Get our Eclipse info, we might depend on other
191 // projects
192 // though ideally this should become empty and void
193 doEclipseClasspath();
194 }
195
196 // Calculate our source directory
197
198 File src = getSrc();
199 if (src.isDirectory()) {
200 sourcepath.add(src);
201 allsourcepath.add(src);
202 } else
203 sourcepath.add(getBase());
204
205 // Set default bin directory
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000206 output = getOutput0();
Stuart McCullochf3173222012-06-07 21:57:32 +0000207 if (!output.exists()) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000208 if (!output.mkdirs()) {
209 throw new IOException("Could not create directory " + output);
210 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000211 getWorkspace().changedFile(output);
212 }
213 if (!output.isDirectory())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000214 msgs.NoOutputDirectory_(output);
Stuart McCullochf3173222012-06-07 21:57:32 +0000215 else {
216 Container c = new Container(this, output);
217 if (!buildpath.contains(c))
218 buildpath.add(c);
219 }
220
221 // Where we store all our generated stuff.
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000222 target = getTarget0();
Stuart McCullochf3173222012-06-07 21:57:32 +0000223
224 // Where the launched OSGi framework stores stuff
225 String runStorageStr = getProperty(Constants.RUNSTORAGE);
226 runstorage = runStorageStr != null ? getFile(runStorageStr) : null;
227
228 // We might have some other projects we want build
229 // before we do anything, but these projects are not in
230 // our path. The -dependson allows you to build them before.
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000231 // The values are possibly negated globbing patterns.
Stuart McCullochf3173222012-06-07 21:57:32 +0000232
Stuart McCullochf3173222012-06-07 21:57:32 +0000233 // dependencies.add( getWorkspace().getProject("cnf"));
234
235 String dp = getProperty(Constants.DEPENDSON);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000236 Set<String> requiredProjectNames = new LinkedHashSet<String>(new Parameters(dp).keySet());
237
238 //Allow DependencyConstributors to modify requiredProjectNames
Stuart McCullochf3173222012-06-07 21:57:32 +0000239 List<DependencyContributor> dcs = getPlugins(DependencyContributor.class);
240 for (DependencyContributor dc : dcs)
241 dc.addDependencies(this, requiredProjectNames);
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000242
243 Instructions is = new Instructions(requiredProjectNames);
244
245 Set<Instruction> unused = new HashSet<Instruction>();
246 Collection<Project> projects = getWorkspace().getAllProjects();
247 Collection<Project> dependencies = is.select(projects, unused, false);
248
249 for (Instruction u: unused)
250 msgs.MissingDependson_(u.getInput());
251
Stuart McCullochf3173222012-06-07 21:57:32 +0000252 // We have two paths that consists of repo files, projects,
253 // or some other stuff. The doPath routine adds them to the
254 // path and extracts the projects so we can build them
255 // before.
256
257 doPath(buildpath, dependencies, parseBuildpath(), bootclasspath);
258 doPath(testpath, dependencies, parseTestpath(), bootclasspath);
259 if (!delayRunDependencies) {
260 doPath(runpath, dependencies, parseRunpath(), null);
261 doPath(runbundles, dependencies, parseRunbundles(), null);
262 }
263
264 // We now know all dependent projects. But we also depend
265 // on whatever those projects depend on. This creates an
266 // ordered list without any duplicates. This of course
267 // assumes
268 // that there is no circularity. However, this is checked
269 // by the inPrepare flag, will throw an exception if we
270 // are circular.
271
272 Set<Project> done = new HashSet<Project>();
273 done.add(this);
274 allsourcepath.addAll(sourcepath);
275
276 for (Project project : dependencies)
277 project.traverse(dependson, done);
278
279 for (Project project : dependson) {
280 allsourcepath.addAll(project.getSourcePath());
281 }
282 if (isOk())
283 preparedPaths = true;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000284 }
285 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000286 inPrepare = false;
287 }
288 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000289 }
290 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000291 trail.remove(this);
292 }
293 }
294
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000295 /**
296 * @return
297 */
298 private File getOutput0() {
299 return getFile(getProperty("bin", "bin")).getAbsoluteFile();
300 }
301
302 /**
303 *
304 */
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000305 private File getTarget0() throws IOException {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000306 File target = getFile(getProperty("target", "generated"));
307 if (!target.exists()) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000308 if (!target.mkdirs()) {
309 throw new IOException("Could not create directory " + target);
310 }
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000311 getWorkspace().changedFile(target);
312 }
313 return target;
314 }
315
Stuart McCullochf3173222012-06-07 21:57:32 +0000316 public File getSrc() {
317 return new File(getBase(), getProperty("src", "src"));
318 }
319
320 private void traverse(Collection<Project> dependencies, Set<Project> visited) throws Exception {
321 if (visited.contains(this))
322 return;
323
324 visited.add(this);
325
326 for (Project project : getDependson())
327 project.traverse(dependencies, visited);
328
329 dependencies.add(this);
330 }
331
332 /**
333 * Iterate over the entries and place the projects on the projects list and
334 * all the files of the entries on the resultpath.
335 *
336 * @param resultpath
337 * The list that gets all the files
338 * @param projects
339 * The list that gets any projects that are entries
340 * @param entries
341 * The input list of classpath entries
342 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000343 private void doPath(Collection<Container> resultpath, Collection<Project> projects, Collection<Container> entries,
344 Collection<Container> bootclasspath) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000345 for (Container cpe : entries) {
346 if (cpe.getError() != null)
347 error(cpe.getError());
348 else {
349 if (cpe.getType() == Container.TYPE.PROJECT) {
350 projects.add(cpe.getProject());
351 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000352 if (bootclasspath != null
353 && (cpe.getBundleSymbolicName().startsWith("ee.") || cpe.getAttributes().containsKey("boot")))
Stuart McCullochf3173222012-06-07 21:57:32 +0000354 bootclasspath.add(cpe);
355 else
356 resultpath.add(cpe);
357 }
358 }
359 }
360
361 /**
362 * Parse the list of bundles that are a prerequisite to this project.
Stuart McCullochf3173222012-06-07 21:57:32 +0000363 * Bundles are listed in repo specific names. So we just let our repo
364 * plugins iterate over the list of bundles and we get the highest version
365 * from them.
366 *
367 * @return
368 */
369
370 private List<Container> parseBuildpath() throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000371 List<Container> bundles = getBundles(Strategy.LOWEST, getProperty(Constants.BUILDPATH), Constants.BUILDPATH);
Stuart McCullochf3173222012-06-07 21:57:32 +0000372 return bundles;
373 }
374
375 private List<Container> parseRunpath() throws Exception {
376 return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNPATH), Constants.RUNPATH);
377 }
378
379 private List<Container> parseRunbundles() throws Exception {
380 return getBundles(Strategy.HIGHEST, getProperty(Constants.RUNBUNDLES), Constants.RUNBUNDLES);
381 }
382
383 private List<Container> parseTestpath() throws Exception {
384 return getBundles(Strategy.HIGHEST, getProperty(Constants.TESTPATH), Constants.TESTPATH);
385 }
386
387 /**
388 * Analyze the header and return a list of files that should be on the
389 * build, test or some other path. The list is assumed to be a list of bsns
390 * with a version specification. The special case of version=project
391 * indicates there is a project in the same workspace. The path to the
392 * output directory is calculated. The default directory ${bin} can be
393 * overridden with the output attribute.
394 *
395 * @param strategy
396 * STRATEGY_LOWEST or STRATEGY_HIGHEST
397 * @param spec
398 * The header
399 * @return
400 */
401
Stuart McCulloch4482c702012-06-15 13:27:53 +0000402 public List<Container> getBundles(Strategy strategyx, String spec, String source) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000403 List<Container> result = new ArrayList<Container>();
404 Parameters bundles = new Parameters(spec);
405
406 try {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000407 for (Iterator<Entry<String,Attrs>> i = bundles.entrySet().iterator(); i.hasNext();) {
408 Entry<String,Attrs> entry = i.next();
409 String bsn = removeDuplicateMarker(entry.getKey());
410 Map<String,String> attrs = entry.getValue();
Stuart McCullochf3173222012-06-07 21:57:32 +0000411
412 Container found = null;
413
414 String versionRange = attrs.get("version");
415
416 if (versionRange != null) {
417 if (versionRange.equals("latest") || versionRange.equals("snapshot")) {
418 found = getBundle(bsn, versionRange, strategyx, attrs);
419 }
420 }
421 if (found == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000422 if (versionRange != null && (versionRange.equals("project") || versionRange.equals("latest"))) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000423 Project project = getWorkspace().getProject(bsn);
424 if (project != null && project.exists()) {
425 File f = project.getOutput();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000426 found = new Container(project, bsn, versionRange, Container.TYPE.PROJECT, f, null, attrs, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000427 } else {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000428 msgs.NoSuchProject(bsn, spec);
Stuart McCullochf3173222012-06-07 21:57:32 +0000429 continue;
430 }
431 } else if (versionRange != null && versionRange.equals("file")) {
432 File f = getFile(bsn);
433 String error = null;
434 if (!f.exists())
435 error = "File does not exist: " + f.getAbsolutePath();
436 if (f.getName().endsWith(".lib")) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000437 found = new Container(this, bsn, "file", Container.TYPE.LIBRARY, f, error, attrs, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000438 } else {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000439 found = new Container(this, bsn, "file", Container.TYPE.EXTERNAL, f, error, attrs, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000440 }
441 } else {
442 found = getBundle(bsn, versionRange, strategyx, attrs);
443 }
444 }
445
446 if (found != null) {
447 List<Container> libs = found.getMembers();
448 for (Container cc : libs) {
449 if (result.contains(cc))
Stuart McCulloch4482c702012-06-15 13:27:53 +0000450 warning("Multiple bundles with the same final URL: %s, dropped duplicate", cc);
451 else
452 result.add(cc);
Stuart McCullochf3173222012-06-07 21:57:32 +0000453 }
454 } else {
455 // Oops, not a bundle in sight :-(
Stuart McCulloch4482c702012-06-15 13:27:53 +0000456 Container x = new Container(this, bsn, versionRange, Container.TYPE.ERROR, null, bsn + ";version="
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000457 + versionRange + " not found", attrs, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000458 result.add(x);
459 warning("Can not find URL for bsn " + bsn);
460 }
461 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000462 }
463 catch (CircularDependencyException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000464 String message = e.getMessage();
465 if (source != null)
466 message = String.format("%s (from property: %s)", message, source);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000467 msgs.CircularDependencyContext_Message_(getName(), message);
468 }
469 catch (Exception e) {
470 msgs.Unexpected_Error_(spec, e);
Stuart McCullochf3173222012-06-07 21:57:32 +0000471 }
472 return result;
473 }
474
475 /**
476 * Just calls a new method with a default parm.
477 *
478 * @throws Exception
Stuart McCullochf3173222012-06-07 21:57:32 +0000479 */
480 Collection<Container> getBundles(Strategy strategy, String spec) throws Exception {
481 return getBundles(strategy, spec, null);
482 }
483
Stuart McCullochf3173222012-06-07 21:57:32 +0000484 static void mergeNames(String names, Set<String> set) {
485 StringTokenizer tokenizer = new StringTokenizer(names, ",");
486 while (tokenizer.hasMoreTokens())
487 set.add(tokenizer.nextToken().trim());
488 }
489
490 static String flatten(Set<String> names) {
491 StringBuilder builder = new StringBuilder();
492 boolean first = true;
493 for (String name : names) {
494 if (!first)
495 builder.append(',');
496 builder.append(name);
497 first = false;
498 }
499 return builder.toString();
500 }
501
502 static void addToPackageList(Container container, String newPackageNames) {
503 Set<String> merged = new HashSet<String>();
504
505 String packageListStr = container.attributes.get("packages");
506 if (packageListStr != null)
507 mergeNames(packageListStr, merged);
508 if (newPackageNames != null)
509 mergeNames(newPackageNames, merged);
510
511 container.putAttribute("packages", flatten(merged));
512 }
513
514 /**
Stuart McCullochf3173222012-06-07 21:57:32 +0000515 * The user selected pom in a path. This will place the pom as well as its
516 * dependencies on the list
517 *
518 * @param strategyx
519 * the strategy to use.
520 * @param result
521 * The list of result containers
522 * @param attrs
523 * The attributes
524 * @throws Exception
525 * anything goes wrong
526 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000527 public void doMavenPom(Strategy strategyx, List<Container> result, String action) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000528 File pomFile = getFile("pom.xml");
529 if (!pomFile.isFile())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000530 msgs.MissingPom();
Stuart McCullochf3173222012-06-07 21:57:32 +0000531 else {
532 ProjectPom pom = getWorkspace().getMaven().createProjectModel(pomFile);
533 if (action == null)
534 action = "compile";
535 Pom.Scope act = Pom.Scope.valueOf(action);
536 Set<Pom> dependencies = pom.getDependencies(act);
537 for (Pom sub : dependencies) {
538 File artifact = sub.getArtifact();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000539 Container container = new Container(artifact, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000540 result.add(container);
541 }
542 }
543 }
544
545 public Collection<Project> getDependson() throws Exception {
546 prepare();
547 return dependson;
548 }
549
550 public Collection<Container> getBuildpath() throws Exception {
551 prepare();
552 return buildpath;
553 }
554
555 public Collection<Container> getTestpath() throws Exception {
556 prepare();
557 return testpath;
558 }
559
560 /**
561 * Handle dependencies for paths that are calculated on demand.
562 *
563 * @param testpath2
564 * @param parseTestpath
565 */
566 private void justInTime(Collection<Container> path, List<Container> entries) {
567 if (delayRunDependencies && path.isEmpty())
568 doPath(path, dependson, entries, null);
569 }
570
571 public Collection<Container> getRunpath() throws Exception {
572 prepare();
573 justInTime(runpath, parseRunpath());
574 return runpath;
575 }
576
577 public Collection<Container> getRunbundles() throws Exception {
578 prepare();
579 justInTime(runbundles, parseRunbundles());
580 return runbundles;
581 }
582
583 public File getRunStorage() throws Exception {
584 prepare();
585 return runstorage;
586 }
587
588 public boolean getRunBuilds() {
589 boolean result;
590 String runBuildsStr = getProperty(Constants.RUNBUILDS);
591 if (runBuildsStr == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000592 result = !getPropertiesFile().getName().toLowerCase().endsWith(Constants.DEFAULT_BNDRUN_EXTENSION);
Stuart McCullochf3173222012-06-07 21:57:32 +0000593 else
594 result = Boolean.parseBoolean(runBuildsStr);
595 return result;
596 }
597
598 public Collection<File> getSourcePath() throws Exception {
599 prepare();
600 return sourcepath;
601 }
602
603 public Collection<File> getAllsourcepath() throws Exception {
604 prepare();
605 return allsourcepath;
606 }
607
608 public Collection<Container> getBootclasspath() throws Exception {
609 prepare();
610 return bootclasspath;
611 }
612
613 public File getOutput() throws Exception {
614 prepare();
615 return output;
616 }
617
618 private void doEclipseClasspath() throws Exception {
619 EclipseClasspath eclipse = new EclipseClasspath(this, getWorkspace().getBase(), getBase());
620 eclipse.setRecurse(false);
621
622 // We get the file directories but in this case we need
623 // to tell ant that the project names
624 for (File dependent : eclipse.getDependents()) {
625 Project required = workspace.getProject(dependent.getName());
626 dependson.add(required);
627 }
628 for (File f : eclipse.getClasspath()) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000629 buildpath.add(new Container(f, null));
Stuart McCullochf3173222012-06-07 21:57:32 +0000630 }
631 for (File f : eclipse.getBootclasspath()) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000632 bootclasspath.add(new Container(f, null));
Stuart McCullochf3173222012-06-07 21:57:32 +0000633 }
634 sourcepath.addAll(eclipse.getSourcepath());
635 allsourcepath.addAll(eclipse.getAllSources());
636 output = eclipse.getOutput();
637 }
638
639 public String _p_dependson(String args[]) throws Exception {
640 return list(args, toFiles(getDependson()));
641 }
642
Stuart McCulloch4482c702012-06-15 13:27:53 +0000643 private Collection< ? > toFiles(Collection<Project> projects) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000644 List<File> files = new ArrayList<File>();
645 for (Project p : projects) {
646 files.add(p.getBase());
647 }
648 return files;
649 }
650
651 public String _p_buildpath(String args[]) throws Exception {
652 return list(args, getBuildpath());
653 }
654
655 public String _p_testpath(String args[]) throws Exception {
656 return list(args, getRunpath());
657 }
658
659 public String _p_sourcepath(String args[]) throws Exception {
660 return list(args, getSourcePath());
661 }
662
663 public String _p_allsourcepath(String args[]) throws Exception {
664 return list(args, getAllsourcepath());
665 }
666
667 public String _p_bootclasspath(String args[]) throws Exception {
668 return list(args, getBootclasspath());
669 }
670
671 public String _p_output(String args[]) throws Exception {
672 if (args.length != 1)
673 throw new IllegalArgumentException("${output} should not have arguments");
674 return getOutput().getAbsolutePath();
675 }
676
Stuart McCulloch4482c702012-06-15 13:27:53 +0000677 private String list(String[] args, Collection< ? > list) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000678 if (args.length > 3)
679 throw new IllegalArgumentException("${" + args[0]
Stuart McCulloch4482c702012-06-15 13:27:53 +0000680 + "[;<separator>]} can only take a separator as argument, has " + Arrays.toString(args));
Stuart McCullochf3173222012-06-07 21:57:32 +0000681
682 String separator = ",";
683
684 if (args.length == 2) {
685 separator = args[1];
686 }
687
688 return join(list, separator);
689 }
690
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000691 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000692 protected Object[] getMacroDomains() {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000693 return new Object[] {
694 workspace
695 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000696 }
697
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000698 public File release(String jarName, InputStream jarStream) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000699 String name = getProperty(Constants.RELEASEREPO);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000700 return release(name, jarName, jarStream);
Stuart McCullochf3173222012-06-07 21:57:32 +0000701 }
702
703 /**
704 * Release
705 *
706 * @param name
707 * The repository name
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000708 * @param jarName
709 * @param jarStream
Stuart McCullochf3173222012-06-07 21:57:32 +0000710 * @return
711 * @throws Exception
712 */
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000713 public File release(String name, String jarName, InputStream jarStream) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000714 trace("release %s", name);
715 List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
716 RepositoryPlugin rp = null;
717 for (RepositoryPlugin plugin : plugins) {
718 if (!plugin.canWrite()) {
719 continue;
720 }
721 if (name == null) {
722 rp = plugin;
723 break;
724 } else if (name.equals(plugin.getName())) {
725 rp = plugin;
726 break;
727 }
728 }
729
730 if (rp != null) {
731 try {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000732 PutResult r = rp.put(jarStream, new RepositoryPlugin.PutOptions());
733 trace("Released %s to %s in repository %s", jarName, r.artifact, rp);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000734 }
735 catch (Exception e) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000736 msgs.Release_Into_Exception_(jarName, rp, e);
Stuart McCullochf3173222012-06-07 21:57:32 +0000737 }
738 } else if (name == null)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000739 msgs.NoNameForReleaseRepository();
Stuart McCullochf3173222012-06-07 21:57:32 +0000740 else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000741 msgs.ReleaseRepository_NotFoundIn_(name, plugins);
Stuart McCullochf3173222012-06-07 21:57:32 +0000742
743 return null;
744
745 }
746
747 public void release(boolean test) throws Exception {
748 String name = getProperty(Constants.RELEASEREPO);
749 release(name, test);
750 }
751
752 /**
753 * Release
754 *
755 * @param name
756 * The respository name
757 * @param test
758 * Run testcases
759 * @throws Exception
760 */
761 public void release(String name, boolean test) throws Exception {
762 trace("release");
763 File[] jars = build(test);
764 // If build fails jars will be null
765 if (jars == null) {
766 trace("no jars being build");
767 return;
768 }
769 trace("build ", Arrays.toString(jars));
770 for (File jar : jars) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000771 release(name, jar.getName(), new BufferedInputStream(new FileInputStream(jar)));
Stuart McCullochf3173222012-06-07 21:57:32 +0000772 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000773 }
774
775 /**
776 * Get a bundle from one of the plugin repositories. If an exact version is
777 * required we just return the first repository found (in declaration order
778 * in the build.bnd file).
779 *
780 * @param bsn
781 * The bundle symbolic name
782 * @param range
783 * The version range
784 * @param lowest
785 * set to LOWEST or HIGHEST
786 * @return the file object that points to the bundle or null if not found
787 * @throws Exception
788 * when something goes wrong
789 */
790
Stuart McCulloch4482c702012-06-15 13:27:53 +0000791 public Container getBundle(String bsn, String range, Strategy strategy, Map<String,String> attrs) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000792
793 if (range == null)
794 range = "0";
795
796 if ("snapshot".equals(range)) {
797 return getBundleFromProject(bsn, attrs);
798 }
799
800 Strategy useStrategy = strategy;
801
802 if ("latest".equals(range)) {
803 Container c = getBundleFromProject(bsn, attrs);
804 if (c != null)
805 return c;
806
807 useStrategy = Strategy.HIGHEST;
808 }
809
810 useStrategy = overrideStrategy(attrs, useStrategy);
811
812 List<RepositoryPlugin> plugins = workspace.getRepositories();
813
814 if (useStrategy == Strategy.EXACT) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000815 if (!Verifier.isVersion(range))
816 return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version=" + range
817 + " Invalid version", null, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000818
819 // For an exact range we just iterate over the repos
820 // and return the first we find.
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000821 Version version = new Version(range);
Stuart McCullochf3173222012-06-07 21:57:32 +0000822 for (RepositoryPlugin plugin : plugins) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000823 DownloadBlocker blocker = new DownloadBlocker(this);
824 File result = plugin.get(bsn, version, attrs, blocker);
Stuart McCullochf3173222012-06-07 21:57:32 +0000825 if (result != null)
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000826 return toContainer(bsn, range, attrs, result, blocker);
Stuart McCullochf3173222012-06-07 21:57:32 +0000827 }
828 } else {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000829 VersionRange versionRange = "latest".equals(range) ? new VersionRange("0") : new VersionRange(range);
Stuart McCullochf3173222012-06-07 21:57:32 +0000830
831 // We have a range search. Gather all the versions in all the repos
832 // and make a decision on that choice. If the same version is found
833 // in
834 // multiple repos we take the first
835
Stuart McCulloch4482c702012-06-15 13:27:53 +0000836 SortedMap<Version,RepositoryPlugin> versions = new TreeMap<Version,RepositoryPlugin>();
Stuart McCullochf3173222012-06-07 21:57:32 +0000837 for (RepositoryPlugin plugin : plugins) {
838 try {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000839 SortedSet<Version> vs = plugin.versions(bsn);
Stuart McCullochf3173222012-06-07 21:57:32 +0000840 if (vs != null) {
841 for (Version v : vs) {
842 if (!versions.containsKey(v) && versionRange.includes(v))
843 versions.put(v, plugin);
844 }
845 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000846 }
847 catch (UnsupportedOperationException ose) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000848 // We have a plugin that cannot list versions, try
849 // if it has this specific version
850 // The main reaosn for this code was the Maven Remote
851 // Repository
852 // To query, we must have a real version
853 if (!versions.isEmpty() && Verifier.isVersion(range)) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000854 Version version = new Version(range);
855 DownloadBlocker blocker = new DownloadBlocker(this);
856 File file = plugin.get(bsn, version, attrs, blocker);
Stuart McCullochf3173222012-06-07 21:57:32 +0000857 // and the entry must exist
858 // if it does, return this as a result
859 if (file != null)
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000860 return toContainer(bsn, range, attrs, file, blocker);
Stuart McCullochf3173222012-06-07 21:57:32 +0000861 }
862 }
863 }
864
865 // Verify if we found any, if so, we use the strategy to pick
866 // the first or last
867
868 if (!versions.isEmpty()) {
869 Version provider = null;
870
871 switch (useStrategy) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000872 case HIGHEST :
873 provider = versions.lastKey();
874 break;
Stuart McCullochf3173222012-06-07 21:57:32 +0000875
Stuart McCulloch4482c702012-06-15 13:27:53 +0000876 case LOWEST :
877 provider = versions.firstKey();
878 break;
Stuart McCulloch1b98aa02012-06-18 11:15:15 +0000879 case EXACT :
880 // TODO need to handle exact better
881 break;
Stuart McCullochf3173222012-06-07 21:57:32 +0000882 }
883 if (provider != null) {
884 RepositoryPlugin repo = versions.get(provider);
885 String version = provider.toString();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000886 DownloadBlocker blocker = new DownloadBlocker(this);
887 File result = repo.get(bsn, provider, attrs, blocker);
Stuart McCullochf3173222012-06-07 21:57:32 +0000888 if (result != null)
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000889 return toContainer(bsn, version, attrs, result, blocker);
Stuart McCullochf3173222012-06-07 21:57:32 +0000890 } else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000891 msgs.FoundVersions_ForStrategy_ButNoProvider(versions, useStrategy);
Stuart McCullochf3173222012-06-07 21:57:32 +0000892 }
893 }
894
895 //
896 // If we get this far we ran into an error somewhere
897
Stuart McCulloch4482c702012-06-15 13:27:53 +0000898 return new Container(this, bsn, range, Container.TYPE.ERROR, null, bsn + ";version=" + range + " Not found in "
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000899 + plugins, null, null);
Stuart McCullochf3173222012-06-07 21:57:32 +0000900
901 }
902
903 /**
904 * @param attrs
905 * @param useStrategy
906 * @return
907 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000908 protected Strategy overrideStrategy(Map<String,String> attrs, Strategy useStrategy) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000909 if (attrs != null) {
910 String overrideStrategy = attrs.get("strategy");
911
912 if (overrideStrategy != null) {
913 if ("highest".equalsIgnoreCase(overrideStrategy))
914 useStrategy = Strategy.HIGHEST;
915 else if ("lowest".equalsIgnoreCase(overrideStrategy))
916 useStrategy = Strategy.LOWEST;
917 else if ("exact".equalsIgnoreCase(overrideStrategy))
918 useStrategy = Strategy.EXACT;
919 }
920 }
921 return useStrategy;
922 }
923
924 /**
925 * @param bsn
926 * @param range
927 * @param attrs
928 * @param result
929 * @return
930 */
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000931 protected Container toContainer(String bsn, String range, Map<String,String> attrs, File result, DownloadBlocker db) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000932 File f = result;
933 if (f == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000934 msgs.ConfusedNoContainerFile();
Stuart McCullochf3173222012-06-07 21:57:32 +0000935 f = new File("was null");
936 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000937 Container container;
Stuart McCullochf3173222012-06-07 21:57:32 +0000938 if (f.getName().endsWith("lib"))
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000939 container = new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs, db);
940 else
941 container = new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs, db);
942
943 return container;
Stuart McCullochf3173222012-06-07 21:57:32 +0000944 }
945
946 /**
947 * Look for the bundle in the workspace. The premise is that the bsn must
948 * start with the project name.
949 *
950 * @param bsn
951 * The bsn
952 * @param attrs
953 * Any attributes
954 * @return
955 * @throws Exception
956 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000957 private Container getBundleFromProject(String bsn, Map<String,String> attrs) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000958 String pname = bsn;
959 while (true) {
960 Project p = getWorkspace().getProject(pname);
961 if (p != null && p.isValid()) {
962 Container c = p.getDeliverable(bsn, attrs);
963 return c;
964 }
965
966 int n = pname.lastIndexOf('.');
967 if (n <= 0)
968 return null;
969 pname = pname.substring(0, n);
970 }
971 }
972
973 /**
974 * Deploy the file (which must be a bundle) into the repository.
975 *
976 * @param name
977 * The repository name
978 * @param file
979 * bundle
980 */
981 public void deploy(String name, File file) throws Exception {
982 List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
983
984 RepositoryPlugin rp = null;
985 for (RepositoryPlugin plugin : plugins) {
986 if (!plugin.canWrite()) {
987 continue;
988 }
989 if (name == null) {
990 rp = plugin;
991 break;
992 } else if (name.equals(plugin.getName())) {
993 rp = plugin;
994 break;
995 }
996 }
997
998 if (rp != null) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000999 try {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001000 rp.put(new BufferedInputStream(new FileInputStream(file)), new RepositoryPlugin.PutOptions());
Stuart McCullochf3173222012-06-07 21:57:32 +00001001 return;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001002 }
1003 catch (Exception e) {
1004 msgs.DeployingFile_On_Exception_(file, rp.getName(), e);
1005 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001006 return;
1007 }
1008 trace("No repo found " + file);
1009 throw new IllegalArgumentException("No repository found for " + file);
1010 }
1011
1012 /**
1013 * Deploy the file (which must be a bundle) into the repository.
1014 *
1015 * @param file
1016 * bundle
1017 */
1018 public void deploy(File file) throws Exception {
1019 String name = getProperty(Constants.DEPLOYREPO);
1020 deploy(name, file);
1021 }
1022
1023 /**
1024 * Deploy the current project to a repository
1025 *
1026 * @throws Exception
1027 */
1028 public void deploy() throws Exception {
1029 Parameters deploy = new Parameters(getProperty(DEPLOY));
1030 if (deploy.isEmpty()) {
1031 warning("Deploying but %s is not set to any repo", DEPLOY);
1032 return;
1033 }
1034 File[] outputs = getBuildFiles();
1035 for (File output : outputs) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001036 for (Deploy d : getPlugins(Deploy.class)) {
1037 trace("Deploying %s to: %s", output.getName(), d);
1038 try {
1039 if (d.deploy(this, output.getName(), new BufferedInputStream(new FileInputStream(output))))
1040 trace("deployed %s successfully to %s", output, d);
Stuart McCullochf3173222012-06-07 21:57:32 +00001041 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001042 catch (Exception e) {
1043 msgs.Deploying(e);
1044 }
1045 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001046 }
1047 }
1048
1049 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001050 * Macro access to the repository ${repo;<bsn>[;<version>[;<low|high>]]}
Stuart McCullochf3173222012-06-07 21:57:32 +00001051 */
1052
Stuart McCulloch4482c702012-06-15 13:27:53 +00001053 static String _repoHelp = "${repo ';'<bsn> [ ; <version> [; ('HIGHEST'|'LOWEST')]}";
1054
Stuart McCullochf3173222012-06-07 21:57:32 +00001055 public String _repo(String args[]) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001056 if (args.length < 2) {
1057 msgs.RepoTooFewArguments(_repoHelp, args);
1058 return null;
1059 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001060
1061 String bsns = args[1];
1062 String version = null;
1063 Strategy strategy = Strategy.HIGHEST;
1064
1065 if (args.length > 2) {
1066 version = args[2];
1067 if (args.length == 4) {
1068 if (args[3].equalsIgnoreCase("HIGHEST"))
1069 strategy = Strategy.HIGHEST;
1070 else if (args[3].equalsIgnoreCase("LOWEST"))
1071 strategy = Strategy.LOWEST;
1072 else if (args[3].equalsIgnoreCase("EXACT"))
1073 strategy = Strategy.EXACT;
1074 else
Stuart McCulloch4482c702012-06-15 13:27:53 +00001075 msgs.InvalidStrategy(_repoHelp, args);
Stuart McCullochf3173222012-06-07 21:57:32 +00001076 }
1077 }
1078
1079 Collection<String> parts = split(bsns);
1080 List<String> paths = new ArrayList<String>();
1081
1082 for (String bsn : parts) {
1083 Container container = getBundle(bsn, version, strategy, null);
1084 add(paths, container);
1085 }
1086 return join(paths);
1087 }
1088
1089 private void add(List<String> paths, Container container) throws Exception {
1090 if (container.getType() == Container.TYPE.LIBRARY) {
1091 List<Container> members = container.getMembers();
1092 for (Container sub : members) {
1093 add(paths, sub);
1094 }
1095 } else {
1096 if (container.getError() == null)
1097 paths.add(container.getFile().getAbsolutePath());
1098 else {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001099 paths.add("<<${repo} = " + container.getBundleSymbolicName() + "-" + container.getVersion() + " : "
1100 + container.getError() + ">>");
Stuart McCullochf3173222012-06-07 21:57:32 +00001101
1102 if (isPedantic()) {
1103 warning("Could not expand repo path request: %s ", container);
1104 }
1105 }
1106
1107 }
1108 }
1109
1110 public File getTarget() throws Exception {
1111 prepare();
1112 return target;
1113 }
1114
1115 /**
1116 * This is the external method that will pre-build any dependencies if it is
1117 * out of date.
1118 *
1119 * @param underTest
1120 * @return
1121 * @throws Exception
1122 */
1123 public File[] build(boolean underTest) throws Exception {
1124 if (isNoBundles())
1125 return null;
1126
1127 if (getProperty("-nope") != null) {
1128 warning("Please replace -nope with %s", NOBUNDLES);
1129 return null;
1130 }
1131
1132 if (isStale()) {
Stuart McCulloch669423b2012-06-26 16:34:24 +00001133 trace("building " + this);
Stuart McCullochf3173222012-06-07 21:57:32 +00001134 files = buildLocal(underTest);
1135 }
1136
1137 return files;
1138 }
1139
1140 /**
1141 * Return the files
1142 */
1143
1144 public File[] getFiles() {
1145 return files;
1146 }
1147
1148 /**
1149 * Check if this project needs building. This is defined as:
Stuart McCullochf3173222012-06-07 21:57:32 +00001150 */
1151 public boolean isStale() throws Exception {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001152 if (workspace.isOffline()) {
Stuart McCulloch1b98aa02012-06-18 11:15:15 +00001153 trace("working %s offline, so always stale", this);
1154 return true;
1155 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001156
Stuart McCulloch4482c702012-06-15 13:27:53 +00001157 Set<Project> visited = new HashSet<Project>();
1158 return isStale(visited);
1159 }
1160
1161 boolean isStale(Set<Project> visited) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001162 // When we do not generate anything ...
1163 if (isNoBundles())
1164 return false;
1165
Stuart McCulloch4482c702012-06-15 13:27:53 +00001166 if (visited.contains(this)) {
1167 msgs.CircularDependencyContext_Message_(this.getName(), visited.toString());
1168 return false;
1169 }
1170
1171 visited.add(this);
1172
Stuart McCullochf3173222012-06-07 21:57:32 +00001173 long buildTime = 0;
1174
1175 files = getBuildFiles(false);
1176 if (files == null)
1177 return true;
1178
1179 for (File f : files) {
1180 if (f.lastModified() < lastModified())
1181 return true;
1182
1183 if (buildTime < f.lastModified())
1184 buildTime = f.lastModified();
1185 }
1186
1187 for (Project dependency : getDependson()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001188 if (dependency == this)
1189 continue;
1190
Stuart McCullochf3173222012-06-07 21:57:32 +00001191 if (dependency.isStale())
1192 return true;
1193
1194 if (dependency.isNoBundles())
1195 continue;
1196
1197 File[] deps = dependency.getBuildFiles();
1198 for (File f : deps) {
1199 if (f.lastModified() >= buildTime)
1200 return true;
1201 }
1202 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001203
Stuart McCullochf3173222012-06-07 21:57:32 +00001204 return false;
1205 }
1206
1207 /**
1208 * This method must only be called when it is sure that the project has been
Stuart McCulloch4482c702012-06-15 13:27:53 +00001209 * build before in the same session. It is a bit yucky, but ant creates
1210 * different class spaces which makes it hard to detect we already build it.
Stuart McCullochf3173222012-06-07 21:57:32 +00001211 * This method remembers the files in the appropriate instance vars.
1212 *
1213 * @return
1214 */
1215
1216 public File[] getBuildFiles() throws Exception {
1217 return getBuildFiles(true);
1218 }
1219
1220 public File[] getBuildFiles(boolean buildIfAbsent) throws Exception {
1221 if (files != null)
1222 return files;
1223
1224 File f = new File(getTarget(), BUILDFILES);
1225 if (f.isFile()) {
1226 BufferedReader rdr = IO.reader(f);
1227 try {
1228 List<File> files = newList();
1229 for (String s = rdr.readLine(); s != null; s = rdr.readLine()) {
1230 s = s.trim();
1231 File ff = new File(s);
1232 if (!ff.isFile()) {
1233 // Originally we warned the user
1234 // but lets just rebuild. That way
Stuart McCulloch4482c702012-06-15 13:27:53 +00001235 // the error is not noticed but
Stuart McCullochf3173222012-06-07 21:57:32 +00001236 // it seems better to correct,
1237 // See #154
1238 rdr.close();
1239 f.delete();
1240 break;
Stuart McCulloch669423b2012-06-26 16:34:24 +00001241 }
1242 files.add(ff);
Stuart McCullochf3173222012-06-07 21:57:32 +00001243 }
1244 return this.files = files.toArray(new File[files.size()]);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001245 }
1246 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001247 rdr.close();
1248 }
1249 }
1250 if (buildIfAbsent)
1251 return files = buildLocal(false);
Stuart McCulloch669423b2012-06-26 16:34:24 +00001252 return files = null;
Stuart McCullochf3173222012-06-07 21:57:32 +00001253 }
1254
1255 /**
1256 * Build without doing any dependency checking. Make sure any dependent
1257 * projects are built first.
1258 *
1259 * @param underTest
1260 * @return
1261 * @throws Exception
1262 */
1263 public File[] buildLocal(boolean underTest) throws Exception {
1264 if (isNoBundles())
1265 return null;
1266
1267 File bfs = new File(getTarget(), BUILDFILES);
1268 bfs.delete();
1269
1270 files = null;
1271 ProjectBuilder builder = getBuilder(null);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001272 try {
1273 if (underTest)
1274 builder.setProperty(Constants.UNDERTEST, "true");
1275 Jar jars[] = builder.builds();
1276 File[] files = new File[jars.length];
Stuart McCullochf3173222012-06-07 21:57:32 +00001277
Stuart McCulloch4482c702012-06-15 13:27:53 +00001278 getInfo(builder);
Stuart McCullochf3173222012-06-07 21:57:32 +00001279
Stuart McCulloch4482c702012-06-15 13:27:53 +00001280 if (isOk()) {
1281 this.files = files;
1282
1283 for (int i = 0; i < jars.length; i++) {
1284 Jar jar = jars[i];
1285 files[i] = saveBuild(jar);
Stuart McCullochf3173222012-06-07 21:57:32 +00001286 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001287
1288 // Write out the filenames in the buildfiles file
1289 // so we can get them later evenin another process
1290 Writer fw = IO.writer(bfs);
1291 try {
1292 for (File f : files) {
1293 fw.append(f.getAbsolutePath());
1294 fw.append("\n");
1295 }
1296 }
1297 finally {
1298 fw.close();
1299 }
1300 getWorkspace().changedFile(bfs);
1301 return files;
Stuart McCulloch669423b2012-06-26 16:34:24 +00001302 }
1303 return null;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001304 }
1305 finally {
1306 builder.close();
1307 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001308 }
1309
1310 /**
1311 * Answer if this project does not have any output
1312 *
1313 * @return
1314 */
1315 public boolean isNoBundles() {
1316 return getProperty(NOBUNDLES) != null;
1317 }
1318
1319 public File saveBuild(Jar jar) throws Exception {
1320 try {
1321 String bsn = jar.getName();
1322 File f = getOutputFile(bsn);
1323 String msg = "";
1324 if (!f.exists() || f.lastModified() < jar.lastModified()) {
1325 reportNewer(f.lastModified(), jar);
1326 f.delete();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001327 File fp = f.getParentFile();
1328 if (!fp.isDirectory()) {
1329 if (!fp.exists() && !fp.mkdirs()) {
1330 throw new IOException("Could not create directory " + fp);
1331 }
1332 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001333 jar.write(f);
1334
1335 getWorkspace().changedFile(f);
1336 } else {
1337 msg = "(not modified since " + new Date(f.lastModified()) + ")";
1338 }
1339 trace(jar.getName() + " (" + f.getName() + ") " + jar.getResources().size() + " " + msg);
1340 return f;
Stuart McCulloch4482c702012-06-15 13:27:53 +00001341 }
1342 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001343 jar.close();
1344 }
1345 }
1346
1347 public File getOutputFile(String bsn) throws Exception {
1348 return new File(getTarget(), bsn + ".jar");
1349 }
1350
1351 private void reportNewer(long lastModified, Jar jar) {
1352 if (isTrue(getProperty(Constants.REPORTNEWER))) {
1353 StringBuilder sb = new StringBuilder();
1354 String del = "Newer than " + new Date(lastModified);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001355 for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001356 if (entry.getValue().lastModified() > lastModified) {
1357 sb.append(del);
1358 del = ", \n ";
1359 sb.append(entry.getKey());
1360 }
1361 }
1362 if (sb.length() > 0)
1363 warning(sb.toString());
1364 }
1365 }
1366
1367 /**
1368 * Refresh if we are based on stale data. This also implies our workspace.
1369 */
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001370 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001371 public boolean refresh() {
1372 boolean changed = false;
1373 if (isCnf()) {
1374 changed = workspace.refresh();
1375 }
1376 return super.refresh() || changed;
1377 }
1378
1379 public boolean isCnf() {
1380 return getBase().getName().equals(Workspace.CNFDIR);
1381 }
1382
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001383 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +00001384 public void propertiesChanged() {
1385 super.propertiesChanged();
1386 preparedPaths = false;
1387 files = null;
1388
1389 }
1390
1391 public String getName() {
1392 return getBase().getName();
1393 }
1394
Stuart McCulloch4482c702012-06-15 13:27:53 +00001395 public Map<String,Action> getActions() {
1396 Map<String,Action> all = newMap();
1397 Map<String,Action> actions = newMap();
Stuart McCullochf3173222012-06-07 21:57:32 +00001398 fillActions(all);
1399 getWorkspace().fillActions(all);
1400
Stuart McCulloch4482c702012-06-15 13:27:53 +00001401 for (Map.Entry<String,Action> action : all.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001402 String key = getReplacer().process(action.getKey());
1403 if (key != null && key.trim().length() != 0)
1404 actions.put(key, action.getValue());
1405 }
1406 return actions;
1407 }
1408
Stuart McCulloch4482c702012-06-15 13:27:53 +00001409 public void fillActions(Map<String,Action> all) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001410 List<NamedAction> plugins = getPlugins(NamedAction.class);
1411 for (NamedAction a : plugins)
1412 all.put(a.getName(), a);
1413
1414 Parameters actions = new Parameters(getProperty("-actions", DEFAULT_ACTIONS));
Stuart McCulloch4482c702012-06-15 13:27:53 +00001415 for (Entry<String,Attrs> entry : actions.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001416 String key = Processor.removeDuplicateMarker(entry.getKey());
1417 Action action;
1418
1419 if (entry.getValue().get("script") != null) {
1420 // TODO check for the type
Stuart McCulloch4482c702012-06-15 13:27:53 +00001421 action = new ScriptAction(entry.getValue().get("type"), entry.getValue().get("script"));
Stuart McCullochf3173222012-06-07 21:57:32 +00001422 } else {
1423 action = new ReflectAction(key);
1424 }
1425 String label = entry.getValue().get("label");
1426 all.put(label.toLowerCase(), action);
1427 }
1428 }
1429
1430 public void release() throws Exception {
1431 release(false);
1432 }
1433
1434 /**
1435 * Release.
1436 *
1437 * @param name
1438 * The repository name
1439 * @throws Exception
1440 */
1441 public void release(String name) throws Exception {
1442 release(name, false);
1443 }
1444
1445 public void clean() throws Exception {
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001446 File target = getTarget0();
Stuart McCullochf3173222012-06-07 21:57:32 +00001447 if (target.isDirectory() && target.getParentFile() != null) {
1448 IO.delete(target);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001449 if (!target.exists() && !target.mkdirs()) {
1450 throw new IOException("Could not create directory " + target);
1451 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001452 }
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001453 File output = getOutput0();
Stuart McCullochf3173222012-06-07 21:57:32 +00001454 if (getOutput().isDirectory())
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001455 IO.delete(output);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001456 if (!output.exists() && !output.mkdirs()) {
1457 throw new IOException("Could not create directory " + output);
1458 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001459 }
1460
1461 public File[] build() throws Exception {
1462 return build(false);
1463 }
1464
1465 public void run() throws Exception {
1466 ProjectLauncher pl = getProjectLauncher();
1467 pl.setTrace(isTrace());
1468 pl.launch();
1469 }
1470
1471 public void test() throws Exception {
1472 clear();
1473 ProjectTester tester = getProjectTester();
1474 tester.setContinuous(isTrue(getProperty(Constants.TESTCONTINUOUS)));
1475 tester.prepare();
1476
1477 if (!isOk()) {
1478 return;
1479 }
1480 int errors = tester.test();
1481 if (errors == 0) {
1482 System.err.println("No Errors");
1483 } else {
1484 if (errors > 0) {
1485 System.err.println(errors + " Error(s)");
1486
1487 } else
1488 System.err.println("Error " + errors);
1489 }
1490 }
1491
1492 /**
1493 * This methods attempts to turn any jar into a valid jar. If this is a
1494 * bundle with manifest, a manifest is added based on defaults. If it is a
1495 * bundle, but not r4, we try to add the r4 headers.
1496 *
1497 * @param descriptor
1498 * @param in
1499 * @return
1500 * @throws Exception
1501 */
1502 public Jar getValidJar(File f) throws Exception {
1503 Jar jar = new Jar(f);
1504 return getValidJar(jar, f.getAbsolutePath());
1505 }
1506
1507 public Jar getValidJar(URL url) throws Exception {
1508 InputStream in = url.openStream();
1509 try {
1510 Jar jar = new Jar(url.getFile().replace('/', '.'), in, System.currentTimeMillis());
1511 return getValidJar(jar, url.toString());
Stuart McCulloch4482c702012-06-15 13:27:53 +00001512 }
1513 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001514 in.close();
1515 }
1516 }
1517
1518 public Jar getValidJar(Jar jar, String id) throws Exception {
1519 Manifest manifest = jar.getManifest();
1520 if (manifest == null) {
1521 trace("Wrapping with all defaults");
1522 Builder b = new Builder(this);
1523 b.addClasspath(jar);
1524 b.setProperty("Bnd-Message", "Wrapped from " + id + "because lacked manifest");
1525 b.setProperty(Constants.EXPORT_PACKAGE, "*");
1526 b.setProperty(Constants.IMPORT_PACKAGE, "*;resolution:=optional");
1527 jar = b.build();
1528 } else if (manifest.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION) == null) {
1529 trace("Not a release 4 bundle, wrapping with manifest as source");
1530 Builder b = new Builder(this);
1531 b.addClasspath(jar);
1532 b.setProperty(Constants.PRIVATE_PACKAGE, "*");
1533 b.mergeManifest(manifest);
1534 String imprts = manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE);
1535 if (imprts == null)
1536 imprts = "";
1537 else
1538 imprts += ",";
1539 imprts += "*;resolution=optional";
1540
1541 b.setProperty(Constants.IMPORT_PACKAGE, imprts);
1542 b.setProperty("Bnd-Message", "Wrapped from " + id + "because had incomplete manifest");
1543 jar = b.build();
1544 }
1545 return jar;
1546 }
1547
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001548 public String _project(@SuppressWarnings("unused")
1549 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001550 return getBase().getAbsolutePath();
1551 }
1552
1553 /**
1554 * Bump the version of this project. First check the main bnd file. If this
1555 * does not contain a version, check the include files. If they still do not
1556 * contain a version, then check ALL the sub builders. If not, add a version
1557 * to the main bnd file.
1558 *
1559 * @param mask
1560 * the mask for bumping, see {@link Macro#_version(String[])}
1561 * @throws Exception
1562 */
1563 public void bump(String mask) throws Exception {
1564 String pattern = "(Bundle-Version\\s*(:|=)\\s*)(([0-9]+(\\.[0-9]+(\\.[0-9]+)?)?))";
1565 String replace = "$1${version;" + mask + ";$3}";
1566 try {
1567 // First try our main bnd file
1568 if (replace(getPropertiesFile(), pattern, replace))
1569 return;
1570
1571 trace("no version in bnd.bnd");
1572
1573 // Try the included filed in reverse order (last has highest
1574 // priority)
1575 List<File> included = getIncluded();
1576 if (included != null) {
1577 List<File> copy = new ArrayList<File>(included);
1578 Collections.reverse(copy);
1579
1580 for (File file : copy) {
1581 if (replace(file, pattern, replace)) {
1582 trace("replaced version in file %s", file);
1583 return;
1584 }
1585 }
1586 }
1587 trace("no version in included files");
1588
1589 boolean found = false;
1590
1591 // Replace in all sub builders.
1592 for (Builder sub : getSubBuilders()) {
1593 found |= replace(sub.getPropertiesFile(), pattern, replace);
1594 }
1595
1596 if (!found) {
1597 trace("no version in sub builders, add it to bnd.bnd");
1598 String bndfile = IO.collect(getPropertiesFile());
1599 bndfile += "\n# Added by by bump\nBundle-Version: 0.0.0\n";
1600 IO.store(bndfile, getPropertiesFile());
1601 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001602 }
1603 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00001604 forceRefresh();
1605 }
1606 }
1607
1608 boolean replace(File f, String pattern, String replacement) throws IOException {
Stuart McCullochb32291a2012-07-16 14:10:57 +00001609 final Macro macro = getReplacer();
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001610 Sed sed = new Sed(new Replacer() {
Stuart McCullochb32291a2012-07-16 14:10:57 +00001611 public String process(String line) {
1612 return macro.process(line);
1613 }
1614 }, f);
Stuart McCullochf3173222012-06-07 21:57:32 +00001615 sed.replace(pattern, replacement);
1616 return sed.doIt() > 0;
1617 }
1618
1619 public void bump() throws Exception {
1620 bump(getProperty(BUMPPOLICY, "=+0"));
1621 }
1622
1623 public void action(String command) throws Throwable {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001624 Map<String,Action> actions = getActions();
Stuart McCullochf3173222012-06-07 21:57:32 +00001625
1626 Action a = actions.get(command);
1627 if (a == null)
1628 a = new ReflectAction(command);
1629
1630 before(this, command);
1631 try {
1632 a.execute(this, command);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001633 }
1634 catch (Throwable t) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001635 after(this, command, t);
1636 throw t;
1637 }
1638 }
1639
1640 /**
1641 * Run all before command plugins
Stuart McCullochf3173222012-06-07 21:57:32 +00001642 */
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001643 void before(@SuppressWarnings("unused")
1644 Project p, String a) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001645 List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
1646 for (CommandPlugin testPlugin : testPlugins) {
1647 testPlugin.before(this, a);
1648 }
1649 }
1650
1651 /**
1652 * Run all after command plugins
1653 */
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001654 void after(@SuppressWarnings("unused")
1655 Project p, String a, Throwable t) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001656 List<CommandPlugin> testPlugins = getPlugins(CommandPlugin.class);
1657 for (int i = testPlugins.size() - 1; i >= 0; i--) {
1658 testPlugins.get(i).after(this, a, t);
1659 }
1660 }
1661
1662 public String _findfile(String args[]) {
1663 File f = getFile(args[1]);
1664 List<String> files = new ArrayList<String>();
1665 tree(files, f, "", new Instruction(args[2]));
1666 return join(files);
1667 }
1668
1669 void tree(List<String> list, File current, String path, Instruction instr) {
1670 if (path.length() > 0)
1671 path = path + "/";
1672
1673 String subs[] = current.list();
1674 if (subs != null) {
1675 for (String sub : subs) {
1676 File f = new File(current, sub);
1677 if (f.isFile()) {
1678 if (instr.matches(sub) && !instr.isNegated())
1679 list.add(path + sub);
1680 } else
1681 tree(list, f, path + sub, instr);
1682 }
1683 }
1684 }
1685
1686 public void refreshAll() {
1687 workspace.refresh();
1688 refresh();
1689 }
1690
Stuart McCulloch4482c702012-06-15 13:27:53 +00001691 @SuppressWarnings("unchecked")
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001692 public void script(@SuppressWarnings("unused")
1693 String type, String script) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001694 // TODO check tyiping
1695 List<Scripter> scripters = getPlugins(Scripter.class);
1696 if (scripters.isEmpty()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001697 msgs.NoScripters_(script);
Stuart McCullochf3173222012-06-07 21:57:32 +00001698 return;
1699 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001700 @SuppressWarnings("rawtypes")
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001701 Map x = getProperties();
1702 scripters.get(0).eval(x, new StringReader(script));
Stuart McCullochf3173222012-06-07 21:57:32 +00001703 }
1704
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001705 public String _repos(@SuppressWarnings("unused")
1706 String args[]) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001707 List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
1708 List<String> names = new ArrayList<String>();
1709 for (RepositoryPlugin rp : repos)
1710 names.add(rp.getName());
1711 return join(names, ", ");
1712 }
1713
1714 public String _help(String args[]) throws Exception {
1715 if (args.length == 1)
1716 return "Specify the option or header you want information for";
1717
1718 Syntax syntax = Syntax.HELP.get(args[1]);
1719 if (syntax == null)
1720 return "No help for " + args[1];
1721
1722 String what = null;
1723 if (args.length > 2)
1724 what = args[2];
1725
1726 if (what == null || what.equals("lead"))
1727 return syntax.getLead();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001728 if (what.equals("example"))
Stuart McCullochf3173222012-06-07 21:57:32 +00001729 return syntax.getExample();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001730 if (what.equals("pattern"))
Stuart McCullochf3173222012-06-07 21:57:32 +00001731 return syntax.getPattern();
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001732 if (what.equals("values"))
Stuart McCullochf3173222012-06-07 21:57:32 +00001733 return syntax.getValues();
1734
1735 return "Invalid type specified for help: lead, example, pattern, values";
1736 }
1737
1738 /**
1739 * Returns containers for the deliverables of this project. The deliverables
1740 * is the project builder for this project if no -sub is specified.
1741 * Otherwise it contains all the sub bnd files.
1742 *
1743 * @return A collection of containers
Stuart McCullochf3173222012-06-07 21:57:32 +00001744 * @throws Exception
1745 */
1746 public Collection<Container> getDeliverables() throws Exception {
1747 List<Container> result = new ArrayList<Container>();
Stuart McCulloch4482c702012-06-15 13:27:53 +00001748 Collection< ? extends Builder> builders = getSubBuilders();
Stuart McCullochf3173222012-06-07 21:57:32 +00001749
1750 for (Builder builder : builders) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001751 Container c = new Container(this, builder.getBsn(), builder.getVersion(), Container.TYPE.PROJECT,
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001752 getOutputFile(builder.getBsn()), null, null, null);
Stuart McCullochf3173222012-06-07 21:57:32 +00001753 result.add(c);
1754 }
1755 return result;
1756
1757 }
1758
1759 /**
1760 * Return the builder associated with the give bnd file or null. The bnd.bnd
1761 * file can contain -sub option. This option allows specifying files in the
1762 * same directory that should drive the generation of multiple deliverables.
1763 * This method figures out if the bndFile is actually one of the bnd files
1764 * of a deliverable.
1765 *
1766 * @param bndFile
1767 * A file pointing to a bnd file.
1768 * @return null or the builder for a sub file.
1769 * @throws Exception
1770 */
1771 public Builder getSubBuilder(File bndFile) throws Exception {
1772 bndFile = bndFile.getCanonicalFile();
1773
1774 // Verify that we are inside the project.
1775 File base = getBase().getCanonicalFile();
1776 if (!bndFile.getAbsolutePath().startsWith(base.getAbsolutePath()))
1777 return null;
1778
Stuart McCulloch4482c702012-06-15 13:27:53 +00001779 Collection< ? extends Builder> builders = getSubBuilders();
Stuart McCullochf3173222012-06-07 21:57:32 +00001780 for (Builder sub : builders) {
1781 File propertiesFile = sub.getPropertiesFile();
1782 if (propertiesFile != null) {
1783 if (propertiesFile.getCanonicalFile().equals(bndFile)) {
1784 // Found it!
1785 return sub;
1786 }
1787 }
1788 }
1789 return null;
1790 }
1791
1792 /**
1793 * Answer the container associated with a given bsn.
1794 *
1795 * @param bndFile
1796 * A file pointing to a bnd file.
1797 * @return null or the builder for a sub file.
1798 * @throws Exception
1799 */
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001800 public Container getDeliverable(String bsn, @SuppressWarnings("unused")
1801 Map<String,String> attrs) throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001802 Collection< ? extends Builder> builders = getSubBuilders();
Stuart McCullochf3173222012-06-07 21:57:32 +00001803 for (Builder sub : builders) {
1804 if (sub.getBsn().equals(bsn))
1805 return new Container(this, getOutputFile(bsn));
1806 }
1807 return null;
1808 }
1809
1810 /**
1811 * Get a list of the sub builders. A bnd.bnd file can contain the -sub
1812 * option. This will generate multiple deliverables. This method returns the
1813 * builders for each sub file. If no -sub option is present, the list will
1814 * contain a builder for the bnd.bnd file.
1815 *
1816 * @return A list of builders.
1817 * @throws Exception
1818 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00001819 public Collection< ? extends Builder> getSubBuilders() throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +00001820 return getBuilder(null).getSubBuilders();
1821 }
1822
1823 /**
1824 * Calculate the classpath. We include our own runtime.jar which includes
1825 * the test framework and we include the first of the test frameworks
1826 * specified.
1827 *
1828 * @throws Exception
1829 */
1830 Collection<File> toFile(Collection<Container> containers) throws Exception {
1831 ArrayList<File> files = new ArrayList<File>();
1832 for (Container container : containers) {
1833 container.contributeFiles(files, this);
1834 }
1835 return files;
1836 }
1837
1838 public Collection<String> getRunVM() {
1839 Parameters hdr = getParameters(RUNVM);
1840 return hdr.keySet();
1841 }
1842
Stuart McCulloch4482c702012-06-15 13:27:53 +00001843 public Map<String,String> getRunProperties() {
Stuart McCullochf3173222012-06-07 21:57:32 +00001844 return OSGiHeader.parseProperties(getProperty(RUNPROPERTIES));
1845 }
1846
1847 /**
1848 * Get a launcher.
1849 *
1850 * @return
1851 * @throws Exception
1852 */
1853 public ProjectLauncher getProjectLauncher() throws Exception {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001854 return getHandler(ProjectLauncher.class, getRunpath(), LAUNCHER_PLUGIN, "biz.aQute.launcher");
Stuart McCullochf3173222012-06-07 21:57:32 +00001855 }
1856
1857 public ProjectTester getProjectTester() throws Exception {
1858 return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN, "biz.aQute.junit");
1859 }
1860
Stuart McCulloch4482c702012-06-15 13:27:53 +00001861 private <T> T getHandler(Class<T> target, Collection<Container> containers, String header, String defaultHandler)
1862 throws Exception {
1863 Class< ? extends T> handlerClass = target;
Stuart McCullochf3173222012-06-07 21:57:32 +00001864
1865 // Make sure we find at least one handler, but hope to find an earlier
1866 // one
1867 List<Container> withDefault = Create.list();
1868 withDefault.addAll(containers);
1869 withDefault.addAll(getBundles(Strategy.HIGHEST, defaultHandler, null));
1870 trace("candidates for tester %s", withDefault);
1871
1872 for (Container c : withDefault) {
1873 Manifest manifest = c.getManifest();
1874
1875 if (manifest != null) {
1876 String launcher = manifest.getMainAttributes().getValue(header);
1877 if (launcher != null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001878 Class< ? > clz = getClass(launcher, c.getFile());
Stuart McCullochf3173222012-06-07 21:57:32 +00001879 if (clz != null) {
1880 if (!target.isAssignableFrom(clz)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +00001881 msgs.IncompatibleHandler_For_(launcher, defaultHandler);
Stuart McCullochf3173222012-06-07 21:57:32 +00001882 } else {
1883 handlerClass = clz.asSubclass(target);
Stuart McCulloch4482c702012-06-15 13:27:53 +00001884 Constructor< ? extends T> constructor = handlerClass.getConstructor(Project.class);
Stuart McCullochf3173222012-06-07 21:57:32 +00001885 return constructor.newInstance(this);
1886 }
1887 }
1888 }
1889 }
1890 }
1891
Stuart McCulloch4482c702012-06-15 13:27:53 +00001892 throw new IllegalArgumentException("Default handler for " + header + " not found in " + defaultHandler);
Stuart McCullochf3173222012-06-07 21:57:32 +00001893 }
1894
1895 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +00001896 * Make this project delay the calculation of the run dependencies. The run
1897 * dependencies calculation can be done in prepare or until the dependencies
1898 * are actually needed.
Stuart McCullochf3173222012-06-07 21:57:32 +00001899 */
1900 public void setDelayRunDependencies(boolean x) {
1901 delayRunDependencies = x;
1902 }
1903
1904 /**
1905 * Sets the package version on an exported package
1906 *
1907 * @param packageName
1908 * The package name
1909 * @param version
1910 * The new package version
1911 */
1912 public void setPackageInfo(String packageName, Version version) {
1913 try {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001914 Version current = getPackageInfoJavaVersion(packageName);
1915 boolean packageInfoJava = false;
1916 if (current != null) {
1917 updatePackageInfoJavaFile(packageName, version);
1918 packageInfoJava = true;
1919 }
1920 if (!packageInfoJava || getPackageInfoFile(packageName).exists()) {
1921 updatePackageInfoFile(packageName, version);
1922 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00001923 }
1924 catch (Exception e) {
1925 msgs.SettingPackageInfoException_(e);
Stuart McCullochf3173222012-06-07 21:57:32 +00001926 }
1927 }
1928
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001929 void updatePackageInfoJavaFile(String packageName, final Version newVersion) throws Exception {
1930 File file = getPackageInfoJavaFile(packageName);
1931
1932 if (!file.exists()) {
1933 return;
1934 }
1935
1936 // If package/classes are copied into the bundle through Private-Package
1937 // etc, there will be no source
1938 if (!file.getParentFile().exists()) {
1939 return;
1940 }
1941
1942 Version oldVersion = getPackageInfo(packageName);
1943
1944 if (newVersion.compareTo(oldVersion) == 0) {
1945 return;
1946 }
1947
1948 Sed sed = new Sed(new Replacer() {
1949 public String process(String line) {
1950 Matcher m = VERSION_ANNOTATION.matcher(line);
1951 if (m.find()) {
1952 return line.substring(0, m.start(3)) + newVersion.toString() + line.substring(m.end(3));
1953 }
1954 return line;
1955 }
1956 }, file);
1957
1958 sed.replace(VERSION_ANNOTATION.pattern(), "$0");
1959 sed.setBackup(false);
1960 sed.doIt();
1961 }
1962
Stuart McCullochf3173222012-06-07 21:57:32 +00001963 void updatePackageInfoFile(String packageName, Version newVersion) throws Exception {
1964
1965 File file = getPackageInfoFile(packageName);
1966
1967 // If package/classes are copied into the bundle through Private-Package
1968 // etc, there will be no source
1969 if (!file.getParentFile().exists()) {
1970 return;
1971 }
1972
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001973 Version oldVersion = getPackageInfoVersion(packageName);
1974 if (oldVersion == null) {
1975 oldVersion = Version.emptyVersion;
1976 }
Stuart McCullochf3173222012-06-07 21:57:32 +00001977
1978 if (newVersion.compareTo(oldVersion) == 0) {
1979 return;
Stuart McCullochf3173222012-06-07 21:57:32 +00001980 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00001981 PrintWriter pw = IO.writer(file);
1982 pw.println("version " + newVersion);
1983 pw.flush();
1984 pw.close();
1985
1986 String path = packageName.replace('.', '/') + "/packageinfo";
1987 File binary = IO.getFile(getOutput(), path);
Stuart McCulloch2929e2d2012-08-07 10:57:21 +00001988 File bp = binary.getParentFile();
1989 if (!bp.exists() && !bp.mkdirs()) {
1990 throw new IOException("Could not create directory " + bp);
1991 }
Stuart McCulloch669423b2012-06-26 16:34:24 +00001992 IO.copy(file, binary);
Stuart McCullochf3173222012-06-07 21:57:32 +00001993 }
1994
Stuart McCulloch2b3253e2012-06-17 20:38:35 +00001995 File getPackageInfoFile(String packageName) {
Stuart McCullochf3173222012-06-07 21:57:32 +00001996 String path = packageName.replace('.', '/') + "/packageinfo";
1997 return IO.getFile(getSrc(), path);
1998
1999 }
2000
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002001 File getPackageInfoJavaFile(String packageName) {
2002 String path = packageName.replace('.', '/') + "/package-info.java";
2003 return IO.getFile(getSrc(), path);
2004
2005 }
2006
Stuart McCullochf3173222012-06-07 21:57:32 +00002007 public Version getPackageInfo(String packageName) throws IOException {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002008
2009 Version version = getPackageInfoJavaVersion(packageName);
2010 if (version != null) {
2011 return version;
2012 }
2013
2014 version = getPackageInfoVersion(packageName);
2015 if (version != null) {
2016 return version;
2017 }
2018
2019 return Version.emptyVersion;
2020 }
2021
2022 Version getPackageInfoVersion(String packageName) throws IOException {
Stuart McCullochf3173222012-06-07 21:57:32 +00002023 File packageInfoFile = getPackageInfoFile(packageName);
2024 if (!packageInfoFile.exists()) {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002025 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +00002026 }
2027 BufferedReader reader = null;
2028 try {
2029 reader = IO.reader(packageInfoFile);
2030 String line;
2031 while ((line = reader.readLine()) != null) {
2032 line = line.trim();
2033 if (line.startsWith("version ")) {
2034 return Version.parseVersion(line.substring(8));
2035 }
2036 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002037 }
2038 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +00002039 if (reader != null) {
2040 IO.close(reader);
2041 }
2042 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002043 return null;
2044 }
2045
2046 Version getPackageInfoJavaVersion(String packageName) throws IOException {
2047 File packageInfoJavaFile = getPackageInfoJavaFile(packageName);
2048 if (!packageInfoJavaFile.exists()) {
2049 return null;
2050 }
2051 BufferedReader reader = null;
2052 try {
2053 reader = IO.reader(packageInfoJavaFile);
2054 String line;
2055 while ((line = reader.readLine()) != null) {
2056 Matcher matcher = VERSION_ANNOTATION.matcher(line);
2057 if (matcher.find()) {
2058 return Version.parseVersion(matcher.group(3));
2059 }
2060 }
2061 }
2062 finally {
2063 if (reader != null) {
2064 IO.close(reader);
2065 }
2066 }
2067 return null;
Stuart McCullochf3173222012-06-07 21:57:32 +00002068 }
2069
2070 /**
2071 * bnd maintains a class path that is set by the environment, i.e. bnd is
2072 * not in charge of it.
2073 */
Stuart McCulloch4482c702012-06-15 13:27:53 +00002074
2075 public void addClasspath(File f) {
2076 if (!f.isFile() && !f.isDirectory()) {
2077 msgs.AddingNonExistentFileToClassPath_(f);
Stuart McCullochf3173222012-06-07 21:57:32 +00002078 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00002079 Container container = new Container(f, null);
Stuart McCullochf3173222012-06-07 21:57:32 +00002080 classpath.add(container);
2081 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002082
Stuart McCullochf3173222012-06-07 21:57:32 +00002083 public void clearClasspath() {
2084 classpath.clear();
2085 }
Stuart McCulloch4482c702012-06-15 13:27:53 +00002086
Stuart McCullochf3173222012-06-07 21:57:32 +00002087 public Collection<Container> getClasspath() {
2088 return classpath;
2089 }
2090}