blob: 912bad3ed959f4d49cf569dc143cd8e9620904cc [file] [log] [blame]
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochbb014372012-06-07 21:57:32 +00002
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.jar.*;
7import java.util.regex.*;
8import java.util.zip.*;
9
10import aQute.bnd.component.*;
11import aQute.bnd.differ.*;
12import aQute.bnd.differ.Baseline.Info;
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000013import aQute.bnd.header.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000014import aQute.bnd.make.*;
15import aQute.bnd.make.component.*;
16import aQute.bnd.make.metatype.*;
17import aQute.bnd.maven.*;
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000018import aQute.bnd.osgi.Descriptors.PackageRef;
19import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochbb014372012-06-07 21:57:32 +000020import aQute.bnd.service.*;
Stuart McCulloch7adbc952012-07-12 22:12:58 +000021import aQute.bnd.service.RepositoryPlugin.Strategy;
Stuart McCullochbb014372012-06-07 21:57:32 +000022import aQute.bnd.service.diff.*;
23import aQute.lib.collections.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000024import aQute.libg.generics.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000025
26/**
Stuart McCulloch2286f232012-06-15 13:27:53 +000027 * Include-Resource: ( [name '=' ] file )+ Private-Package: package-decl ( ','
28 * package-decl )* Export-Package: package-decl ( ',' package-decl )*
Stuart McCullochbb014372012-06-07 21:57:32 +000029 * Import-Package: package-decl ( ',' package-decl )*
30 *
31 * @version $Revision$
32 */
33public class Builder extends Analyzer {
Stuart McCulloch2286f232012-06-15 13:27:53 +000034 static Pattern IR_PATTERN = Pattern.compile("[{]?-?@?(?:[^=]+=)?\\s*([^}!]+).*");
Stuart McCullochbb014372012-06-07 21:57:32 +000035 private final DiffPluginImpl differ = new DiffPluginImpl();
36 private Pattern xdoNotCopy = null;
37 private static final int SPLIT_MERGE_LAST = 1;
38 private static final int SPLIT_MERGE_FIRST = 2;
39 private static final int SPLIT_ERROR = 3;
40 private static final int SPLIT_FIRST = 4;
41 private static final int SPLIT_DEFAULT = 0;
42 private final List<File> sourcePath = new ArrayList<File>();
43 private final Make make = new Make(this);
44
45 public Builder(Processor parent) {
46 super(parent);
47 }
48
Stuart McCulloch2286f232012-06-15 13:27:53 +000049 public Builder() {}
Stuart McCullochbb014372012-06-07 21:57:32 +000050
51 public Jar build() throws Exception {
52 trace("build");
53 init();
54 if (isTrue(getProperty(NOBUNDLES)))
55 return null;
56
57 if (getProperty(CONDUIT) != null)
Stuart McCulloch2286f232012-06-15 13:27:53 +000058 error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");
Stuart McCullochbb014372012-06-07 21:57:32 +000059
60 Jar dot = new Jar("dot");
61 try {
62 long modified = Long.parseLong(getProperty("base.modified"));
63 dot.updateModified(modified, "Base modified");
64 }
65 catch (Exception e) {
66 // Ignore
67 }
68 setJar(dot);
69
70 doExpand(dot);
71 doIncludeResources(dot);
72 doWab(dot);
73
Stuart McCulloch2286f232012-06-15 13:27:53 +000074
Stuart McCullochbb014372012-06-07 21:57:32 +000075 // Check if we override the calculation of the
76 // manifest. We still need to calculated it because
77 // we need to have analyzed the classpath.
78
79 Manifest manifest = calcManifest();
80
81 String mf = getProperty(MANIFEST);
82 if (mf != null) {
83 File mff = getFile(mf);
84 if (mff.isFile()) {
85 try {
86 InputStream in = new FileInputStream(mff);
87 manifest = new Manifest(in);
88 in.close();
89 }
90 catch (Exception e) {
91 error(MANIFEST + " while reading manifest file", e);
92 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000093 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +000094 error(MANIFEST + ", no such file " + mf);
95 }
96 }
97
98 if (getProperty(NOMANIFEST) == null)
99 dot.setManifest(manifest);
100 else
101 dot.setDoNotTouchManifest();
102
103 // This must happen after we analyzed so
104 // we know what it is on the classpath
105 addSources(dot);
106
107 if (getProperty(POM) != null)
108 dot.putResource("pom.xml", new PomResource(dot.getManifest()));
Stuart McCullochffa8aaf2012-06-17 20:38:35 +0000109
Stuart McCullochbb014372012-06-07 21:57:32 +0000110 if (!isNoBundle())
111 doVerify(dot);
112
113 if (dot.getResources().isEmpty())
114 warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
115 getBsn());
116
117 dot.updateModified(lastModified(), "Last Modified Processor");
118 dot.setName(getBsn());
119
Stuart McCulloch6a046662012-07-19 13:11:20 +0000120 doDigests(dot);
121
Stuart McCullochbb014372012-06-07 21:57:32 +0000122 sign(dot);
Stuart McCullochbb014372012-06-07 21:57:32 +0000123 doSaveManifest(dot);
124
125 doDiff(dot); // check if need to diff this bundle
126 doBaseline(dot); // check for a baseline
127 return dot;
128 }
129
Stuart McCulloch2286f232012-06-15 13:27:53 +0000130
131 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000132 * Check if we need to calculate any checksums.
133 *
134 * @param dot
135 * @throws Exception
136 */
137 private void doDigests(Jar dot) throws Exception {
138 Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
139 if (ps.isEmpty())
140 return;
141 trace("digests %s", ps);
142 String[] digests = ps.keySet().toArray(new String[ps.size()]);
Stuart McCulloch6a046662012-07-19 13:11:20 +0000143 dot.setDigestAlgorithms(digests);
Stuart McCullochbb014372012-06-07 21:57:32 +0000144 }
145
146 /**
147 * Allow any local initialization by subclasses before we build.
148 */
149 public void init() throws Exception {
150 begin();
151 doRequireBnd();
152
153 // Check if we have sensible setup
154
155 if (getClasspath().size() == 0
156 && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
157 warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
158
159 }
160
161 /**
162 * Turn this normal bundle in a web and add any resources.
163 *
164 * @throws Exception
165 */
166 private Jar doWab(Jar dot) throws Exception {
167 String wab = getProperty(WAB);
168 String wablib = getProperty(WABLIB);
169 if (wab == null && wablib == null)
170 return dot;
171
172 trace("wab %s %s", wab, wablib);
173 setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
174
175 Set<String> paths = new HashSet<String>(dot.getResources().keySet());
176
177 for (String path : paths) {
178 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
179 trace("wab: moving: %s", path);
180 dot.rename(path, "WEB-INF/classes/" + path);
181 }
182 }
183
184 Parameters clauses = parseHeader(getProperty(WABLIB));
185 for (String key : clauses.keySet()) {
186 File f = getFile(key);
187 addWabLib(dot, f);
188 }
189 doIncludeResource(dot, wab);
190 return dot;
191 }
192
193 /**
194 * Add a wab lib to the jar.
195 *
196 * @param f
197 */
198 private void addWabLib(Jar dot, File f) throws Exception {
199 if (f.exists()) {
200 Jar jar = new Jar(f);
201 jar.setDoNotTouchManifest();
202 addClose(jar);
203 String path = "WEB-INF/lib/" + f.getName();
204 dot.putResource(path, new JarResource(jar));
205 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
206
207 Manifest m = jar.getManifest();
208 String cp = m.getMainAttributes().getValue("Class-Path");
209 if (cp != null) {
210 Collection<String> parts = split(cp, ",");
211 for (String part : parts) {
212 File sub = getFile(f.getParentFile(), part);
213 if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000214 warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory", sub,
215 f);
216 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000217 addWabLib(dot, sub);
218 }
219 }
220 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000221 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000222 error("WAB lib does not exist %s", f);
223 }
224 }
225
226 /**
227 * Get the manifest and write it out separately if -savemanifest is set
228 *
229 * @param dot
230 */
231 private void doSaveManifest(Jar dot) throws Exception {
232 String output = getProperty(SAVEMANIFEST);
233 if (output == null)
234 return;
235
236 File f = getFile(output);
237 if (f.isDirectory()) {
238 f = new File(f, "MANIFEST.MF");
239 }
240 f.delete();
241 f.getParentFile().mkdirs();
242 OutputStream out = new FileOutputStream(f);
243 try {
244 Jar.writeManifest(dot.getManifest(), out);
245 }
246 finally {
247 out.close();
248 }
249 changedFile(f);
250 }
251
Stuart McCullochd4826102012-06-26 16:34:24 +0000252 protected void changedFile(@SuppressWarnings("unused") File f) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000253
254 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +0000255 * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
256 * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
Stuart McCullochbb014372012-06-07 21:57:32 +0000257 *
258 * @return
259 */
260
Stuart McCullochd4826102012-06-26 16:34:24 +0000261 void sign(@SuppressWarnings("unused") Jar jar) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000262 String signing = getProperty("-sign");
263 if (signing == null)
264 return;
265
266 trace("Signing %s, with %s", getBsn(), signing);
267 List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
268
269 Parameters infos = parseHeader(signing);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000270 for (Entry<String,Attrs> entry : infos.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000271 for (SignerPlugin signer : signers) {
272 signer.sign(this, entry.getKey());
273 }
274 }
275 }
276
277 public boolean hasSources() {
278 return isTrue(getProperty(SOURCES));
279 }
280
281 /**
282 * Answer extra packages. In this case we implement conditional package. Any
283 */
284 protected Jar getExtra() throws Exception {
285 Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
286 if (conditionals.isEmpty())
287 return null;
Stuart McCullochd4826102012-06-26 16:34:24 +0000288 trace("do Conditional Package %s", conditionals);
Stuart McCullochbb014372012-06-07 21:57:32 +0000289 Instructions instructions = new Instructions(conditionals);
290
291 Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
292 referred.removeAll(getContained().keySet());
293
294 Jar jar = new Jar("conditional-import");
295 addClose(jar);
296 for (PackageRef pref : referred) {
297 for (Jar cpe : getClasspath()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000298 Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
Stuart McCullochbb014372012-06-07 21:57:32 +0000299 if (map != null) {
Stuart McCullochd4826102012-06-26 16:34:24 +0000300 copy(jar, cpe, pref.getPath(), false);
301// Now use copy so that bnd.info is processed, next line should be
302// removed in the future TODO
303// jar.addDirectory(map, false);
Stuart McCullochbb014372012-06-07 21:57:32 +0000304 break;
305 }
306 }
307 }
308 if (jar.getDirectories().size() == 0)
309 return null;
310 return jar;
311 }
312
313 /**
314 * Intercept the call to analyze and cleanup versions after we have analyzed
315 * the setup. We do not want to cleanup if we are going to verify.
316 */
317
318 public void analyze() throws Exception {
319 super.analyze();
320 cleanupVersion(getImports(), null);
321 cleanupVersion(getExports(), getVersion());
322 String version = getProperty(BUNDLE_VERSION);
323 if (version != null) {
324 version = cleanupVersion(version);
325 if (version.endsWith(".SNAPSHOT")) {
326 version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
327 }
328 setProperty(BUNDLE_VERSION, version);
329 }
330 }
331
332 public void cleanupVersion(Packages packages, String defaultVersion) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000333 for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000334 Attrs attributes = entry.getValue();
335 String v = attributes.get(Constants.VERSION_ATTRIBUTE);
336 if (v == null && defaultVersion != null) {
337 if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
338 v = defaultVersion;
339 if (isPedantic())
340 warning("Used bundle version %s for exported package %s", v, entry.getKey());
Stuart McCulloch2286f232012-06-15 13:27:53 +0000341 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000342 if (isPedantic())
343 warning("No export version for exported package %s", entry.getKey());
344 }
345 }
346 if (v != null)
347 attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
348 }
349 }
350
351 /**
352 *
353 */
354 private void addSources(Jar dot) {
355 if (!hasSources())
356 return;
357
358 Set<PackageRef> packages = Create.set();
359
360 for (TypeRef typeRef : getClassspace().keySet()) {
361 PackageRef packageRef = typeRef.getPackageRef();
362 String sourcePath = typeRef.getSourcePath();
363 String packagePath = packageRef.getPath();
364
365 boolean found = false;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000366 String[] fixed = {
367 "packageinfo", "package.html", "module-info.java", "package-info.java"
368 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000369
370 for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
371 File root = i.next();
372
373 // TODO should use bcp?
374
375 File f = getFile(root, sourcePath);
376 if (f.exists()) {
377 found = true;
378 if (!packages.contains(packageRef)) {
379 packages.add(packageRef);
380 File bdir = getFile(root, packagePath);
381 for (int j = 0; j < fixed.length; j++) {
382 File ff = getFile(bdir, fixed[j]);
383 if (ff.isFile()) {
384 String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
385 dot.putResource(name, new FileResource(ff));
386 }
387 }
388 }
389 if (packageRef.isDefaultPackage())
390 System.err.println("Duh?");
391 dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
392 }
393 }
394 if (!found) {
395 for (Jar jar : getClasspath()) {
396 Resource resource = jar.getResource(sourcePath);
397 if (resource != null) {
398 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000399 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000400 resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
401 if (resource != null) {
402 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
403 }
404 }
405 }
406 }
407 if (getSourcePath().isEmpty())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000408 warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
Stuart McCullochbb014372012-06-07 21:57:32 +0000409 // TODO copy from the jars where they came from
410 }
411 }
412
413 boolean firstUse = true;
414 private Tree tree;
415
416 public Collection<File> getSourcePath() {
417 if (firstUse) {
418 firstUse = false;
419 String sp = getProperty(SOURCEPATH);
420 if (sp != null) {
421 Parameters map = parseHeader(sp);
422 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
423 String file = i.next();
424 if (!isDuplicate(file)) {
425 File f = getFile(file);
426 if (!f.isDirectory()) {
427 error("Adding a sourcepath that is not a directory: " + f);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000428 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000429 sourcePath.add(f);
430 }
431 }
432 }
433 }
434 }
435 return sourcePath;
436 }
437
Stuart McCullochd4826102012-06-26 16:34:24 +0000438 private void doVerify(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000439 Verifier verifier = new Verifier(this);
440 // Give the verifier the benefit of our analysis
441 // prevents parsing the files twice
442 verifier.verify();
443 getInfo(verifier);
444 }
445
Stuart McCulloch2286f232012-06-15 13:27:53 +0000446 private void doExpand(Jar dot) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000447
448 // Build an index of the class path that we can then
449 // use destructively
Stuart McCulloch2286f232012-06-15 13:27:53 +0000450 MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000451 for (Jar srce : getClasspath()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000452 for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000453 if (e.getValue() != null)
454 packages.add(e.getKey(), srce);
455 }
456 }
457
458 Parameters privatePackages = getPrivatePackage();
459 if (isTrue(getProperty(Constants.UNDERTEST))) {
460 String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
461 privatePackages.putAll(parseHeader(h));
462 }
463
464 if (!privatePackages.isEmpty()) {
465 Instructions privateFilter = new Instructions(privatePackages);
466 Set<Instruction> unused = doExpand(dot, packages, privateFilter);
467
468 if (!unused.isEmpty()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000469 warning("Unused Private-Package instructions, no such package(s) on the class path: %s", unused);
Stuart McCullochbb014372012-06-07 21:57:32 +0000470 }
471 }
472
473 Parameters exportedPackage = getExportPackage();
474 if (!exportedPackage.isEmpty()) {
475 Instructions exportedFilter = new Instructions(exportedPackage);
476
477 // We ignore unused instructions for exports, they should show
478 // up as errors during analysis. Otherwise any overlapping
479 // packages with the private packages should show up as
480 // unused
481
482 doExpand(dot, packages, exportedFilter);
483 }
484 }
485
486 /**
487 * Destructively filter the packages from the build up index. This index is
488 * used by the Export Package as well as the Private Package
489 *
490 * @param jar
491 * @param name
492 * @param instructions
493 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000494 private Set<Instruction> doExpand(Jar jar, MultiMap<String,Jar> index, Instructions filter) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000495 Set<Instruction> unused = Create.set();
496
Stuart McCulloch2286f232012-06-15 13:27:53 +0000497 for (Entry<Instruction,Attrs> e : filter.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000498 Instruction instruction = e.getKey();
499 if (instruction.isDuplicate())
500 continue;
501
502 Attrs directives = e.getValue();
503
504 // We can optionally filter on the
505 // source of the package. We assume
506 // they all match but this can be overridden
507 // on the instruction
508 Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));
509
510 boolean used = false;
511
Stuart McCulloch2286f232012-06-15 13:27:53 +0000512 for (Iterator<Entry<String,List<Jar>>> entry = index.entrySet().iterator(); entry.hasNext();) {
513 Entry<String,List<Jar>> p = entry.next();
Stuart McCullochbb014372012-06-07 21:57:32 +0000514
515 String directory = p.getKey();
516 PackageRef packageRef = getPackageRef(directory);
517
518 // Skip * and meta data, we're talking packages!
519 if (packageRef.isMetaData() && instruction.isAny())
520 continue;
521
522 if (!instruction.matches(packageRef.getFQN()))
523 continue;
524
525 // Ensure it is never matched again
526 entry.remove();
527
528 // ! effectively removes it from consideration by others (this
529 // includes exports)
530 if (instruction.isNegated())
531 continue;
532
533 // Do the from: directive, filters on the JAR type
534 List<Jar> providers = filterFrom(from, p.getValue());
535 if (providers.isEmpty())
536 continue;
537
538 int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
539 copyPackage(jar, providers, directory, splitStrategy);
540
541 used = true;
542 }
543
544 if (!used && !isTrue(directives.get("optional:")))
545 unused.add(instruction);
546 }
547 return unused;
548 }
549
550 /**
551 * @param from
552 * @return
553 */
554 private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
555 if (from.isAny())
556 return providers;
557
558 List<Jar> np = new ArrayList<Jar>();
559 for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
560 Jar j = i.next();
561 if (from.matches(j.getName())) {
562 np.add(j);
563 }
564 }
565 return np;
566 }
567
568 /**
569 * Copy the package from the providers based on the split package strategy.
570 *
571 * @param dest
572 * @param providers
573 * @param directory
574 * @param splitStrategy
575 */
576 private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
577 switch (splitStrategy) {
578 case SPLIT_MERGE_LAST :
579 for (Jar srce : providers) {
580 copy(dest, srce, path, true);
581 }
582 break;
583
584 case SPLIT_MERGE_FIRST :
585 for (Jar srce : providers) {
586 copy(dest, srce, path, false);
587 }
588 break;
589
590 case SPLIT_ERROR :
591 error(diagnostic(path, providers));
592 break;
593
594 case SPLIT_FIRST :
595 copy(dest, providers.get(0), path, false);
596 break;
597
598 default :
599 if (providers.size() > 1)
600 warning("%s", diagnostic(path, providers));
601 for (Jar srce : providers) {
602 copy(dest, srce, path, false);
603 }
604 break;
605 }
606 }
607
608 /**
609 * Cop
610 *
611 * @param dest
612 * @param srce
613 * @param path
614 * @param overwriteResource
615 */
616 private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
Stuart McCullochd4826102012-06-26 16:34:24 +0000617 trace("copy d=" + dest + " s=" + srce +" p="+ path);
Stuart McCullochbb014372012-06-07 21:57:32 +0000618 dest.copy(srce, path, overwrite);
Stuart McCullochd4826102012-06-26 16:34:24 +0000619
620 // bnd.info sources must be preprocessed
621 String bndInfoPath = path + "/bnd.info";
622 Resource r = dest.getResource(bndInfoPath);
623 if ( r != null && !(r instanceof PreprocessResource)) {
624 trace("preprocessing bnd.info");
625 PreprocessResource pp = new PreprocessResource(this, r);
626 dest.putResource(bndInfoPath, pp);
627 }
628
Stuart McCullochbb014372012-06-07 21:57:32 +0000629 if (hasSources()) {
630 String srcPath = "OSGI-OPT/src/" + path;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000631 Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
Stuart McCullochbb014372012-06-07 21:57:32 +0000632 if (srcContents != null) {
633 dest.addDirectory(srcContents, overwrite);
634 }
635 }
636 }
637
638 /**
639 * Analyze the classpath for a split package
640 *
641 * @param pack
642 * @param classpath
643 * @param source
644 * @return
645 */
646 private String diagnostic(String pack, List<Jar> culprits) {
647 // Default is like merge-first, but with a warning
648 return "Split package, multiple jars provide the same package:"
649 + pack
650 + "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
651 + "Package found in " + culprits + "\n" //
652 + "Class path " + getClasspath();
653 }
654
655 private int getSplitStrategy(String type) {
656 if (type == null)
657 return SPLIT_DEFAULT;
658
659 if (type.equals("merge-last"))
660 return SPLIT_MERGE_LAST;
661
662 if (type.equals("merge-first"))
663 return SPLIT_MERGE_FIRST;
664
665 if (type.equals("error"))
666 return SPLIT_ERROR;
667
668 if (type.equals("first"))
669 return SPLIT_FIRST;
670
671 error("Invalid strategy for split-package: " + type);
672 return SPLIT_DEFAULT;
673 }
674
675 /**
676 * Matches the instructions against a package.
677 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000678 * @param instructions
679 * The list of instructions
680 * @param pack
681 * The name of the package
682 * @param unused
683 * The total list of patterns, matched patterns are removed
684 * @param source
685 * The name of the source container, can be filtered upon with
686 * the from: directive.
Stuart McCullochbb014372012-06-07 21:57:32 +0000687 * @return
688 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000689 private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused, String source) {
690 for (Entry<Instruction,Attrs> entry : instructions.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000691 Instruction pattern = entry.getKey();
692
693 // It is possible to filter on the source of the
694 // package with the from: directive. This is an
695 // instruction that must match the name of the
696 // source class path entry.
697
698 String from = entry.getValue().get(FROM_DIRECTIVE);
699 if (from != null) {
700 Instruction f = new Instruction(from);
701 if (!f.matches(source) || f.isNegated())
702 continue;
703 }
704
705 // Now do the normal
706 // matching
707 if (pattern.matches(pack)) {
708 if (unused != null)
709 unused.remove(pattern);
710 return pattern;
711 }
712 }
713 return null;
714 }
715
716 /**
717 * Parse the Bundle-Includes header. Files in the bundles Include header are
718 * included in the jar. The source can be a directory or a file.
719 *
720 * @throws IOException
721 * @throws FileNotFoundException
722 */
723 private void doIncludeResources(Jar jar) throws Exception {
724 String includes = getProperty("Bundle-Includes");
725 if (includes == null) {
726 includes = getProperty(INCLUDERESOURCE);
727 if (includes == null || includes.length() == 0)
728 includes = getProperty("Include-Resource");
Stuart McCulloch2286f232012-06-15 13:27:53 +0000729 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000730 warning("Please use -includeresource instead of Bundle-Includes");
731
732 doIncludeResource(jar, includes);
733
734 }
735
736 private void doIncludeResource(Jar jar, String includes) throws Exception {
737 Parameters clauses = parseHeader(includes);
738 doIncludeResource(jar, clauses);
739 }
740
Stuart McCulloch2286f232012-06-15 13:27:53 +0000741 private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException, Exception {
742 for (Entry<String,Attrs> entry : clauses.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000743 doIncludeResource(jar, entry.getKey(), entry.getValue());
744 }
745 }
746
Stuart McCulloch2286f232012-06-15 13:27:53 +0000747 private void doIncludeResource(Jar jar, String name, Map<String,String> extra) throws ZipException, IOException,
748 Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000749
750 boolean preprocess = false;
751 boolean absentIsOk = false;
752
753 if (name.startsWith("{") && name.endsWith("}")) {
754 preprocess = true;
755 name = name.substring(1, name.length() - 1).trim();
756 }
757
758 String parts[] = name.split("\\s*=\\s*");
759 String source = parts[0];
760 String destination = parts[0];
761 if (parts.length == 2)
762 source = parts[1];
763
764 if (source.startsWith("-")) {
765 source = source.substring(1);
766 absentIsOk = true;
767 }
768
769 if (source.startsWith("@")) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000770 extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination, absentIsOk);
771 } else if (extra.containsKey("cmd")) {
772 doCommand(jar, source, destination, extra, preprocess, absentIsOk);
Stuart McCulloch61c61eb2012-07-24 21:37:47 +0000773 } else if (extra.containsKey(LITERAL_ATTRIBUTE)) {
774 String literal = extra.get(LITERAL_ATTRIBUTE);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000775 Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
776 String x = extra.get("extra");
777 if (x != null)
778 r.setExtra(x);
779 jar.putResource(name, r);
780 } else {
781 File sourceFile;
782 String destinationPath;
783
784 sourceFile = getFile(source);
785 if (parts.length == 1) {
786 // Directories should be copied to the root
787 // but files to their file name ...
788 if (sourceFile.isDirectory())
789 destinationPath = "";
790 else
791 destinationPath = sourceFile.getName();
792 } else {
793 destinationPath = parts[0];
Stuart McCullochbb014372012-06-07 21:57:32 +0000794 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000795 // Handle directories
796 if (sourceFile.isDirectory()) {
797 destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile, destinationPath);
798 return;
799 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000800
Stuart McCulloch2286f232012-06-15 13:27:53 +0000801 // destinationPath = checkDestinationPath(destinationPath);
Stuart McCullochbb014372012-06-07 21:57:32 +0000802
Stuart McCulloch2286f232012-06-15 13:27:53 +0000803 if (!sourceFile.exists()) {
804 if (absentIsOk)
805 return;
Stuart McCullochbb014372012-06-07 21:57:32 +0000806
Stuart McCulloch2286f232012-06-15 13:27:53 +0000807 noSuchFile(jar, name, extra, source, destinationPath);
808 } else
809 copy(jar, destinationPath, sourceFile, preprocess, extra);
810 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000811 }
812
813 /**
814 * It is possible in Include-Resource to use a system command that generates
815 * the contents, this is indicated with {@code cmd} attribute. The command
816 * can be repeated for a number of source files with the {@code for}
817 * attribute which indicates a list of repetitions, often down with the
818 * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
819 * repetition will repeat the given command for each item. The @} macro can
820 * be used to replace the current item. If no {@code for} is given, the
Stuart McCulloch2286f232012-06-15 13:27:53 +0000821 * source is used as the only item. If the destination contains a macro,
822 * each iteration will create a new file, otherwise the destination name is
823 * used. The execution of the command is delayed until the JAR is actually
824 * written to the file system for performance reasons.
Stuart McCullochbb014372012-06-07 21:57:32 +0000825 *
826 * @param jar
827 * @param source
828 * @param destination
829 * @param extra
830 * @param preprocess
831 * @param absentIsOk
832 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000833 private void doCommand(Jar jar, String source, String destination, Map<String,String> extra, boolean preprocess,
834 boolean absentIsOk) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000835 String repeat = extra.get("for"); // TODO constant
836 if (repeat == null)
837 repeat = source;
838
839 Collection<String> requires = split(extra.get("requires"));
840 long lastModified = 0;
841 for (String required : requires) {
842 File file = getFile(required);
843 if (!file.isFile()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000844 error("Include-Resource.cmd for %s, requires %s, but no such file %s", source, required,
845 file.getAbsoluteFile());
846 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000847 lastModified = Math.max(lastModified, file.lastModified());
848 }
849
850 String cmd = extra.get("cmd");
851
852 Collection<String> items = Processor.split(repeat);
853
854 CombinedResource cr = null;
855
856 if (!destination.contains("${@}")) {
857 cr = new CombinedResource();
858 }
859 trace("last modified requires %s", lastModified);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000860
Stuart McCullochbb014372012-06-07 21:57:32 +0000861 for (String item : items) {
862 setProperty("@", item);
863 try {
864 String path = getReplacer().process(destination);
865 String command = getReplacer().process(cmd);
866 File file = getFile(item);
867
868 Resource r = new CommandResource(command, this, Math.max(lastModified,
Stuart McCulloch2286f232012-06-15 13:27:53 +0000869 file.exists() ? file.lastModified() : 0L));
Stuart McCullochbb014372012-06-07 21:57:32 +0000870
871 if (preprocess)
872 r = new PreprocessResource(this, r);
873
874 if (cr == null)
875 jar.putResource(path, r);
876 else
877 cr.addResource(r);
878 }
879 finally {
880 unsetProperty("@");
881 }
882 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000883
Stuart McCullochbb014372012-06-07 21:57:32 +0000884 // Add last so the correct modification date is used
885 // to update the modified time.
Stuart McCulloch2286f232012-06-15 13:27:53 +0000886 if (cr != null)
Stuart McCullochbb014372012-06-07 21:57:32 +0000887 jar.putResource(destination, cr);
888 }
889
Stuart McCulloch2286f232012-06-15 13:27:53 +0000890 private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
891 String destinationPath) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000892 String filter = extra.get("filter:");
893 boolean flatten = isTrue(extra.get("flatten:"));
894 boolean recursive = true;
895 String directive = extra.get("recursive:");
896 if (directive != null) {
897 recursive = isTrue(directive);
898 }
899
900 Instruction.Filter iFilter = null;
901 if (filter != null) {
902 iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
Stuart McCulloch2286f232012-06-15 13:27:53 +0000903 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000904 iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
905 }
906
Stuart McCulloch2286f232012-06-15 13:27:53 +0000907 Map<String,File> files = newMap();
Stuart McCullochbb014372012-06-07 21:57:32 +0000908 resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
909
Stuart McCulloch2286f232012-06-15 13:27:53 +0000910 for (Map.Entry<String,File> entry : files.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000911 copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
912 }
913 return destinationPath;
914 }
915
Stuart McCulloch2286f232012-06-15 13:27:53 +0000916 private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path, Map<String,File> files,
917 boolean flatten) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000918
919 if (doNotCopy(dir.getName())) {
920 return;
921 }
922
923 File[] fs = dir.listFiles(filter);
924 for (File file : fs) {
925 if (file.isDirectory()) {
926 if (recursive) {
927 String nextPath;
928 if (flatten)
929 nextPath = path;
930 else
931 nextPath = appendPath(path, file.getName());
932
933 resolveFiles(file, filter, recursive, nextPath, files, flatten);
934 }
935 // Directories are ignored otherwise
Stuart McCulloch2286f232012-06-15 13:27:53 +0000936 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000937 String p = appendPath(path, file.getName());
938 if (files.containsKey(p))
939 warning("Include-Resource overwrites entry %s from file %s", p, file);
940 files.put(p, file);
941 }
942 }
943 }
944
Stuart McCullochd4826102012-06-26 16:34:24 +0000945 private void noSuchFile(Jar jar, @SuppressWarnings("unused") String clause, Map<String,String> extra, String source, String destinationPath)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000946 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000947 Jar src = getJarFromName(source, "Include-Resource " + source);
948 if (src != null) {
949 // Do not touch the manifest so this also
950 // works for signed files.
951 src.setDoNotTouchManifest();
952 JarResource jarResource = new JarResource(src);
953 jar.putResource(destinationPath, jarResource);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000954 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000955 Resource lastChance = make.process(source);
956 if (lastChance != null) {
957 String x = extra.get("extra");
958 if (x != null)
959 lastChance.setExtra(x);
960 jar.putResource(destinationPath, lastChance);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000961 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000962 error("Input file does not exist: " + source);
963 }
964 }
965
966 /**
967 * Extra resources from a Jar and add them to the given jar. The clause is
968 * the
969 *
970 * @param jar
971 * @param clauses
972 * @param i
973 * @throws ZipException
974 * @throws IOException
975 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000976 private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk) throws ZipException,
977 IOException {
Stuart McCullochbb014372012-06-07 21:57:32 +0000978 // Inline all resources and classes from another jar
979 // optionally appended with a modified regular expression
980 // like @zip.jar!/META-INF/MANIFEST.MF
981 int n = source.lastIndexOf("!/");
982 Instruction instr = null;
983 if (n > 0) {
984 instr = new Instruction(source.substring(n + 2));
985 source = source.substring(0, n);
986 }
987
988 // Pattern filter = null;
989 // if (n > 0) {
990 // String fstring = source.substring(n + 2);
991 // source = source.substring(0, n);
992 // filter = wildcard(fstring);
993 // }
994 Jar sub = getJarFromName(source, "extract from jar");
995 if (sub == null) {
996 if (absentIsOk)
997 return;
998
999 error("Can not find JAR file " + source);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001000 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001001 addAll(jar, sub, instr, destination);
1002 }
1003 }
1004
1005 /**
1006 * Add all the resources in the given jar that match the given filter.
1007 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001008 * @param sub
1009 * the jar
1010 * @param filter
1011 * a pattern that should match the resoures in sub to be added
Stuart McCullochbb014372012-06-07 21:57:32 +00001012 */
1013 public boolean addAll(Jar to, Jar sub, Instruction filter) {
1014 return addAll(to, sub, filter, "");
1015 }
1016
1017 /**
1018 * Add all the resources in the given jar that match the given filter.
1019 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001020 * @param sub
1021 * the jar
1022 * @param filter
1023 * a pattern that should match the resoures in sub to be added
Stuart McCullochbb014372012-06-07 21:57:32 +00001024 */
1025 public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
1026 boolean dupl = false;
1027 for (String name : sub.getResources().keySet()) {
1028 if ("META-INF/MANIFEST.MF".equals(name))
1029 continue;
1030
1031 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch2286f232012-06-15 13:27:53 +00001032 dupl |= to.putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochbb014372012-06-07 21:57:32 +00001033 }
1034 return dupl;
1035 }
1036
Stuart McCulloch2286f232012-06-15 13:27:53 +00001037 private void copy(Jar jar, String path, File from, boolean preprocess, Map<String,String> extra) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +00001038 if (doNotCopy(from.getName()))
1039 return;
1040
1041 if (from.isDirectory()) {
1042
1043 File files[] = from.listFiles();
1044 for (int i = 0; i < files.length; i++) {
1045 copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
1046 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001047 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001048 if (from.exists()) {
1049 Resource resource = new FileResource(from);
1050 if (preprocess) {
1051 resource = new PreprocessResource(this, resource);
1052 }
1053 String x = extra.get("extra");
1054 if (x != null)
1055 resource.setExtra(x);
1056 if (path.endsWith("/"))
1057 path = path + from.getName();
1058 jar.putResource(path, resource);
1059
1060 if (isTrue(extra.get(LIB_DIRECTIVE))) {
1061 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
1062 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001063 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001064 error("Input file does not exist: " + from);
1065 }
1066 }
1067 }
1068
1069 public void setSourcepath(File[] files) {
1070 for (int i = 0; i < files.length; i++)
1071 addSourcepath(files[i]);
1072 }
1073
1074 public void addSourcepath(File cp) {
1075 if (!cp.exists())
1076 warning("File on sourcepath that does not exist: " + cp);
1077
1078 sourcePath.add(cp);
1079 }
1080
1081 public void close() {
1082 super.close();
1083 }
1084
1085 /**
1086 * Build Multiple jars. If the -sub command is set, we filter the file with
1087 * the given patterns.
1088 *
1089 * @return
1090 * @throws Exception
1091 */
1092 public Jar[] builds() throws Exception {
1093 begin();
1094
1095 // Are we acting as a conduit for another JAR?
1096 String conduit = getProperty(CONDUIT);
1097 if (conduit != null) {
1098 Parameters map = parseHeader(conduit);
1099 Jar[] result = new Jar[map.size()];
1100 int n = 0;
1101 for (String file : map.keySet()) {
1102 Jar c = new Jar(getFile(file));
1103 addClose(c);
1104 String name = map.get(file).get("name");
1105 if (name != null)
1106 c.setName(name);
1107
1108 result[n++] = c;
1109 }
1110 return result;
1111 }
1112
1113 List<Jar> result = new ArrayList<Jar>();
1114 List<Builder> builders;
1115
1116 builders = getSubBuilders();
1117
1118 for (Builder builder : builders) {
1119 try {
1120 Jar jar = builder.build();
1121 jar.setName(builder.getBsn());
1122 result.add(jar);
1123 }
1124 catch (Exception e) {
1125 e.printStackTrace();
1126 error("Sub Building " + builder.getBsn(), e);
1127 }
1128 if (builder != this)
1129 getInfo(builder, builder.getBsn() + ": ");
1130 }
1131 return result.toArray(new Jar[result.size()]);
1132 }
1133
1134 /**
1135 * Answer a list of builders that represent this file or a list of files
1136 * specified in -sub. This list can be empty. These builders represents to
1137 * be created artifacts and are each scoped to such an artifacts. The
1138 * builders can be used to build the bundles or they can be used to find out
1139 * information about the to be generated bundles.
1140 *
1141 * @return List of 0..n builders representing artifacts.
1142 * @throws Exception
1143 */
1144 public List<Builder> getSubBuilders() throws Exception {
1145 String sub = getProperty(SUB);
1146 if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
1147 return Arrays.asList(this);
1148
1149 List<Builder> builders = new ArrayList<Builder>();
1150 if (isTrue(getProperty(NOBUNDLES)))
1151 return builders;
1152
1153 Parameters subsMap = parseHeader(sub);
1154 for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
1155 File file = getFile(i.next());
1156 if (file.isFile()) {
1157 builders.add(getSubBuilder(file));
1158 i.remove();
1159 }
1160 }
1161
1162 Instructions instructions = new Instructions(subsMap);
1163
1164 List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
1165
1166 nextFile: while (members.size() > 0) {
1167
1168 File file = members.remove(0);
1169
1170 // Check if the file is one of our parents
1171 Processor p = this;
1172 while (p != null) {
1173 if (file.equals(p.getPropertiesFile()))
1174 continue nextFile;
1175 p = p.getParent();
1176 }
1177
1178 for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
1179
1180 Instruction instruction = i.next();
1181 if (instruction.matches(file.getName())) {
1182
1183 if (!instruction.isNegated()) {
1184 builders.add(getSubBuilder(file));
1185 }
1186
1187 // Because we matched (even though we could be negated)
1188 // we skip any remaining searches
1189 continue nextFile;
1190 }
1191 }
1192 }
1193 return builders;
1194 }
1195
1196 public Builder getSubBuilder(File file) throws Exception {
1197 Builder builder = getSubBuilder();
1198 if (builder != null) {
1199 builder.setProperties(file);
1200 addClose(builder);
1201 }
1202 return builder;
1203 }
1204
1205 public Builder getSubBuilder() throws Exception {
1206 Builder builder = new Builder(this);
1207 builder.setBase(getBase());
1208
1209 for (Jar file : getClasspath()) {
1210 builder.addClasspath(file);
1211 }
1212
1213 return builder;
1214 }
1215
1216 /**
1217 * A macro to convert a maven version to an OSGi version
1218 */
1219
1220 public String _maven_version(String args[]) {
1221 if (args.length > 2)
1222 error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
Stuart McCulloch2286f232012-06-15 13:27:53 +00001223 else if (args.length < 2)
1224 error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
1225 else {
1226 return cleanupVersion(args[1]);
1227 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001228 return null;
1229 }
1230
Stuart McCulloch2286f232012-06-15 13:27:53 +00001231 public String _permissions(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001232 StringBuilder sb = new StringBuilder();
1233
1234 for (String arg : args) {
1235 if ("packages".equals(arg) || "all".equals(arg)) {
1236 for (PackageRef imp : getImports().keySet()) {
1237 if (!imp.isJava()) {
1238 sb.append("(org.osgi.framework.PackagePermission \"");
1239 sb.append(imp);
1240 sb.append("\" \"import\")\r\n");
1241 }
1242 }
1243 for (PackageRef exp : getExports().keySet()) {
1244 sb.append("(org.osgi.framework.PackagePermission \"");
1245 sb.append(exp);
1246 sb.append("\" \"export\")\r\n");
1247 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001248 } else if ("admin".equals(arg) || "all".equals(arg)) {
1249 sb.append("(org.osgi.framework.AdminPermission)");
1250 } else if ("permissions".equals(arg))
1251 ;
Stuart McCullochbb014372012-06-07 21:57:32 +00001252 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001253 error("Invalid option in ${permissions}: %s", arg);
Stuart McCullochbb014372012-06-07 21:57:32 +00001254 }
1255 return sb.toString();
1256 }
1257
1258 /**
1259 *
1260 */
1261 public void removeBundleSpecificHeaders() {
1262 Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
1263 setForceLocal(set);
1264 }
1265
1266 /**
1267 * Check if the given resource is in scope of this bundle. That is, it
1268 * checks if the Include-Resource includes this resource or if it is a class
Stuart McCulloch2286f232012-06-15 13:27:53 +00001269 * file it is on the class path and the Export-Package or Private-Package
Stuart McCullochbb014372012-06-07 21:57:32 +00001270 * include this resource.
1271 *
1272 * @param f
1273 * @return
1274 */
1275 public boolean isInScope(Collection<File> resources) throws Exception {
1276 Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
1277 clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
1278 if (isTrue(getProperty(Constants.UNDERTEST))) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001279 clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
Stuart McCullochbb014372012-06-07 21:57:32 +00001280 }
1281
1282 Collection<String> ir = getIncludedResourcePrefixes();
1283
1284 Instructions instructions = new Instructions(clauses);
1285
1286 for (File r : resources) {
1287 String cpEntry = getClasspathEntrySuffix(r);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001288
Stuart McCullochbb014372012-06-07 21:57:32 +00001289 if (cpEntry != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001290
1291 if (cpEntry.equals("")) // Meaning we actually have a CPE
1292 return true;
1293
Stuart McCullochbb014372012-06-07 21:57:32 +00001294 String pack = Descriptors.getPackage(cpEntry);
1295 Instruction i = matches(instructions, pack, null, r.getName());
1296 if (i != null)
1297 return !i.isNegated();
1298 }
1299
1300 // Check if this resource starts with one of the I-C header
1301 // paths.
1302 String path = r.getAbsolutePath();
1303 for (String p : ir) {
1304 if (path.startsWith(p))
1305 return true;
1306 }
1307 }
1308 return false;
1309 }
1310
1311 /**
1312 * Extra the paths for the directories and files that are used in the
1313 * Include-Resource header.
1314 *
1315 * @return
1316 */
1317 private Collection<String> getIncludedResourcePrefixes() {
1318 List<String> prefixes = new ArrayList<String>();
1319 Parameters includeResource = getIncludeResource();
Stuart McCulloch2286f232012-06-15 13:27:53 +00001320 for (Entry<String,Attrs> p : includeResource.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001321 if (p.getValue().containsKey("literal"))
1322 continue;
1323
1324 Matcher m = IR_PATTERN.matcher(p.getKey());
1325 if (m.matches()) {
1326 File f = getFile(m.group(1));
1327 prefixes.add(f.getAbsolutePath());
1328 }
1329 }
1330 return prefixes;
1331 }
1332
1333 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +00001334 * Answer the string of the resource that it has in the container. It is
1335 * possible that the resource is a classpath entry. In that case an empty
1336 * string is returned.
Stuart McCullochbb014372012-06-07 21:57:32 +00001337 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001338 * @param resource
1339 * The resource to look for
1340 * @return A suffix on the classpath or "" if the resource is a class path
1341 * entry
Stuart McCullochbb014372012-06-07 21:57:32 +00001342 * @throws Exception
1343 */
1344 public String getClasspathEntrySuffix(File resource) throws Exception {
1345 for (Jar jar : getClasspath()) {
1346 File source = jar.getSource();
1347 if (source != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001348
Stuart McCullochbb014372012-06-07 21:57:32 +00001349 source = source.getCanonicalFile();
1350 String sourcePath = source.getAbsolutePath();
1351 String resourcePath = resource.getAbsolutePath();
Stuart McCulloch2286f232012-06-15 13:27:53 +00001352 if (sourcePath.equals(resourcePath))
1353 return ""; // Matches a classpath entry
Stuart McCullochbb014372012-06-07 21:57:32 +00001354
1355 if (resourcePath.startsWith(sourcePath)) {
1356 // Make sure that the path name is translated correctly
1357 // i.e. on Windows the \ must be translated to /
1358 String filePath = resourcePath.substring(sourcePath.length() + 1);
1359
1360 return filePath.replace(File.separatorChar, '/');
1361 }
1362 }
1363 }
1364 return null;
1365 }
1366
1367 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +00001368 * doNotCopy The doNotCopy variable maintains a patter for files that should
1369 * not be copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this
1370 * ca be overridden with the {@link Constants#DONOTCOPY} property.
Stuart McCullochbb014372012-06-07 21:57:32 +00001371 */
1372
1373 public boolean doNotCopy(String v) {
1374 return getDoNotCopy().matcher(v).matches();
1375 }
1376
1377 public Pattern getDoNotCopy() {
1378 if (xdoNotCopy == null) {
1379 String string = null;
1380 try {
1381 string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
1382 xdoNotCopy = Pattern.compile(string);
1383 }
1384 catch (Exception e) {
1385 error("Invalid value for %s, value is %s", DONOTCOPY, string);
1386 xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
1387 }
1388 }
1389 return xdoNotCopy;
1390 }
1391
1392 /**
1393 */
1394
1395 static MakeBnd makeBnd = new MakeBnd();
1396 static MakeCopy makeCopy = new MakeCopy();
1397 static ServiceComponent serviceComponent = new ServiceComponent();
1398 static DSAnnotations dsAnnotations = new DSAnnotations();
1399 static MetatypePlugin metatypePlugin = new MetatypePlugin();
1400
1401 @Override
1402 protected void setTypeSpecificPlugins(Set<Object> list) {
1403 list.add(makeBnd);
1404 list.add(makeCopy);
1405 list.add(serviceComponent);
1406 list.add(dsAnnotations);
1407 list.add(metatypePlugin);
1408 super.setTypeSpecificPlugins(list);
1409 }
1410
1411 /**
1412 * Diff this bundle to another bundle for the given packages.
1413 *
1414 * @throws Exception
1415 */
1416
Stuart McCullochd4826102012-06-26 16:34:24 +00001417 public void doDiff(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +00001418 Parameters diffs = parseHeader(getProperty("-diff"));
1419 if (diffs.isEmpty())
1420 return;
1421
1422 trace("diff %s", diffs);
1423
1424 if (tree == null)
1425 tree = differ.tree(this);
1426
Stuart McCulloch2286f232012-06-15 13:27:53 +00001427 for (Entry<String,Attrs> entry : diffs.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001428 String path = entry.getKey();
1429 File file = getFile(path);
1430 if (!file.isFile()) {
1431 error("Diffing against %s that is not a file", file);
1432 continue;
1433 }
1434
1435 boolean full = entry.getValue().get("--full") != null;
1436 boolean warning = entry.getValue().get("--warning") != null;
1437
1438 Tree other = differ.tree(file);
1439 Diff api = tree.diff(other).get("<api>");
1440 Instructions instructions = new Instructions(entry.getValue().get("--pack"));
1441
1442 trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
1443 for (Diff p : api.getChildren()) {
1444 String pname = p.getName();
1445 if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
1446 if (p.getDelta() != Delta.UNCHANGED) {
1447
1448 if (!full)
1449 if (warning)
1450 warning("Differ %s", p);
1451 else
1452 error("Differ %s", p);
1453 else {
1454 if (warning)
Stuart McCulloch2286f232012-06-15 13:27:53 +00001455 warning("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochbb014372012-06-07 21:57:32 +00001456 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001457 error("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochbb014372012-06-07 21:57:32 +00001458 show(p, "", warning);
1459 }
1460 }
1461 }
1462 }
1463 }
1464 }
1465
1466 /**
1467 * Show the diff recursively
1468 *
1469 * @param p
1470 * @param i
1471 */
1472 private void show(Diff p, String indent, boolean warning) {
1473 Delta d = p.getDelta();
1474 if (d == Delta.UNCHANGED)
1475 return;
1476
1477 if (warning)
1478 warning("%s%s", indent, p);
1479 else
1480 error("%s%s", indent, p);
1481
1482 indent = indent + " ";
1483 switch (d) {
1484 case CHANGED :
1485 case MAJOR :
1486 case MINOR :
1487 case MICRO :
1488 break;
1489
1490 default :
1491 return;
1492 }
1493 for (Diff c : p.getChildren())
1494 show(c, indent, warning);
1495 }
1496
1497 /**
1498 * Base line against a previous version
1499 *
1500 * @throws Exception
1501 */
1502
1503 private void doBaseline(Jar dot) throws Exception {
1504 Parameters diffs = parseHeader(getProperty("-baseline"));
1505 if (diffs.isEmpty())
1506 return;
1507
1508 System.err.printf("baseline %s%n", diffs);
1509
Stuart McCulloch7adbc952012-07-12 22:12:58 +00001510 Jar other = getBaselineJar();
1511 if (other == null) {
1512 return;
1513 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001514 Baseline baseline = new Baseline(this, differ);
Stuart McCulloch7adbc952012-07-12 22:12:58 +00001515 Set<Info> infos = baseline.baseline(dot, other, null);
1516 for (Info info : infos) {
1517 if (info.mismatch) {
1518 error("%s %-50s %-10s %-10s %-10s %-10s %-10s\n", info.mismatch ? '*' : ' ', info.packageName,
1519 info.packageDiff.getDelta(), info.newerVersion, info.olderVersion, info.suggestedVersion,
1520 info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders);
Stuart McCullochbb014372012-06-07 21:57:32 +00001521 }
1522 }
1523 }
1524
1525 public void addSourcepath(Collection<File> sourcepath) {
1526 for (File f : sourcepath) {
1527 addSourcepath(f);
1528 }
1529 }
1530
Stuart McCulloch7adbc952012-07-12 22:12:58 +00001531 public Jar getBaselineJar() throws Exception {
1532
1533 List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
1534
1535 Parameters diffs = parseHeader(getProperty("-baseline"));
1536 File baselineFile = null;
1537 if (diffs.isEmpty()) {
1538 String repoName = getProperty("-baseline-repo");
1539 if (repoName == null) {
1540 return null;
1541 }
1542 for (RepositoryPlugin repo : repos) {
1543 if (repoName.equals(repo.getName())) {
1544 baselineFile = repo.get(getBsn(), null, Strategy.HIGHEST, null);
1545 break;
1546 }
1547 }
1548 } else {
1549
1550 String bsn = null;
1551 String version = null;
1552 for (Entry<String,Attrs> entry : diffs.entrySet()) {
1553 bsn = entry.getKey();
1554 if ("@".equals(bsn)) {
1555 bsn = getBsn();
1556 }
1557 version = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
1558 break;
1559 }
1560
1561 for (RepositoryPlugin repo : repos) {
1562 if (version == null) {
1563 baselineFile = repo.get(bsn, null, Strategy.HIGHEST, null);
1564 } else {
1565 baselineFile = repo.get(bsn, version, Strategy.EXACT, null);
1566 }
1567 if (baselineFile != null) {
1568 break;
1569 }
1570 }
1571 }
1572 if (baselineFile == null) {
1573 return new Jar(".");
1574 }
1575 return new Jar(baselineFile);
1576 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001577}