blob: a5bb7f6e4ad0e9968105a25afa063bec68534dfd [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
120 sign(dot);
Stuart McCullochbb014372012-06-07 21:57:32 +0000121 doSaveManifest(dot);
122
123 doDiff(dot); // check if need to diff this bundle
124 doBaseline(dot); // check for a baseline
125 return dot;
126 }
127
Stuart McCulloch2286f232012-06-15 13:27:53 +0000128
129 /**
Stuart McCullochbb014372012-06-07 21:57:32 +0000130 * Check if we need to calculate any checksums.
131 *
132 * @param dot
133 * @throws Exception
134 */
135 private void doDigests(Jar dot) throws Exception {
136 Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
137 if (ps.isEmpty())
138 return;
139 trace("digests %s", ps);
140 String[] digests = ps.keySet().toArray(new String[ps.size()]);
141 dot.calcChecksums(digests);
142 }
143
144 /**
145 * Allow any local initialization by subclasses before we build.
146 */
147 public void init() throws Exception {
148 begin();
149 doRequireBnd();
150
151 // Check if we have sensible setup
152
153 if (getClasspath().size() == 0
154 && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null))
155 warning("Classpath is empty. Private-Package and Export-Package can only expand from the classpath when there is one");
156
157 }
158
159 /**
160 * Turn this normal bundle in a web and add any resources.
161 *
162 * @throws Exception
163 */
164 private Jar doWab(Jar dot) throws Exception {
165 String wab = getProperty(WAB);
166 String wablib = getProperty(WABLIB);
167 if (wab == null && wablib == null)
168 return dot;
169
170 trace("wab %s %s", wab, wablib);
171 setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
172
173 Set<String> paths = new HashSet<String>(dot.getResources().keySet());
174
175 for (String path : paths) {
176 if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
177 trace("wab: moving: %s", path);
178 dot.rename(path, "WEB-INF/classes/" + path);
179 }
180 }
181
182 Parameters clauses = parseHeader(getProperty(WABLIB));
183 for (String key : clauses.keySet()) {
184 File f = getFile(key);
185 addWabLib(dot, f);
186 }
187 doIncludeResource(dot, wab);
188 return dot;
189 }
190
191 /**
192 * Add a wab lib to the jar.
193 *
194 * @param f
195 */
196 private void addWabLib(Jar dot, File f) throws Exception {
197 if (f.exists()) {
198 Jar jar = new Jar(f);
199 jar.setDoNotTouchManifest();
200 addClose(jar);
201 String path = "WEB-INF/lib/" + f.getName();
202 dot.putResource(path, new JarResource(jar));
203 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
204
205 Manifest m = jar.getManifest();
206 String cp = m.getMainAttributes().getValue("Class-Path");
207 if (cp != null) {
208 Collection<String> parts = split(cp, ",");
209 for (String part : parts) {
210 File sub = getFile(f.getParentFile(), part);
211 if (!sub.exists() || !sub.getParentFile().equals(f.getParentFile())) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000212 warning("Invalid Class-Path entry %s in %s, must exist and must reside in same directory", sub,
213 f);
214 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000215 addWabLib(dot, sub);
216 }
217 }
218 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000219 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000220 error("WAB lib does not exist %s", f);
221 }
222 }
223
224 /**
225 * Get the manifest and write it out separately if -savemanifest is set
226 *
227 * @param dot
228 */
229 private void doSaveManifest(Jar dot) throws Exception {
230 String output = getProperty(SAVEMANIFEST);
231 if (output == null)
232 return;
233
234 File f = getFile(output);
235 if (f.isDirectory()) {
236 f = new File(f, "MANIFEST.MF");
237 }
238 f.delete();
239 f.getParentFile().mkdirs();
240 OutputStream out = new FileOutputStream(f);
241 try {
242 Jar.writeManifest(dot.getManifest(), out);
243 }
244 finally {
245 out.close();
246 }
247 changedFile(f);
248 }
249
Stuart McCullochd4826102012-06-26 16:34:24 +0000250 protected void changedFile(@SuppressWarnings("unused") File f) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000251
252 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +0000253 * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
254 * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
Stuart McCullochbb014372012-06-07 21:57:32 +0000255 *
256 * @return
257 */
258
Stuart McCullochd4826102012-06-26 16:34:24 +0000259 void sign(@SuppressWarnings("unused") Jar jar) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000260 String signing = getProperty("-sign");
261 if (signing == null)
262 return;
263
264 trace("Signing %s, with %s", getBsn(), signing);
265 List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
266
267 Parameters infos = parseHeader(signing);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000268 for (Entry<String,Attrs> entry : infos.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000269 for (SignerPlugin signer : signers) {
270 signer.sign(this, entry.getKey());
271 }
272 }
273 }
274
275 public boolean hasSources() {
276 return isTrue(getProperty(SOURCES));
277 }
278
279 /**
280 * Answer extra packages. In this case we implement conditional package. Any
281 */
282 protected Jar getExtra() throws Exception {
283 Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
284 if (conditionals.isEmpty())
285 return null;
Stuart McCullochd4826102012-06-26 16:34:24 +0000286 trace("do Conditional Package %s", conditionals);
Stuart McCullochbb014372012-06-07 21:57:32 +0000287 Instructions instructions = new Instructions(conditionals);
288
289 Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
290 referred.removeAll(getContained().keySet());
291
292 Jar jar = new Jar("conditional-import");
293 addClose(jar);
294 for (PackageRef pref : referred) {
295 for (Jar cpe : getClasspath()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000296 Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
Stuart McCullochbb014372012-06-07 21:57:32 +0000297 if (map != null) {
Stuart McCullochd4826102012-06-26 16:34:24 +0000298 copy(jar, cpe, pref.getPath(), false);
299// Now use copy so that bnd.info is processed, next line should be
300// removed in the future TODO
301// jar.addDirectory(map, false);
Stuart McCullochbb014372012-06-07 21:57:32 +0000302 break;
303 }
304 }
305 }
306 if (jar.getDirectories().size() == 0)
307 return null;
308 return jar;
309 }
310
311 /**
312 * Intercept the call to analyze and cleanup versions after we have analyzed
313 * the setup. We do not want to cleanup if we are going to verify.
314 */
315
316 public void analyze() throws Exception {
317 super.analyze();
318 cleanupVersion(getImports(), null);
319 cleanupVersion(getExports(), getVersion());
320 String version = getProperty(BUNDLE_VERSION);
321 if (version != null) {
322 version = cleanupVersion(version);
323 if (version.endsWith(".SNAPSHOT")) {
324 version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
325 }
326 setProperty(BUNDLE_VERSION, version);
327 }
328 }
329
330 public void cleanupVersion(Packages packages, String defaultVersion) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000331 for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000332 Attrs attributes = entry.getValue();
333 String v = attributes.get(Constants.VERSION_ATTRIBUTE);
334 if (v == null && defaultVersion != null) {
335 if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
336 v = defaultVersion;
337 if (isPedantic())
338 warning("Used bundle version %s for exported package %s", v, entry.getKey());
Stuart McCulloch2286f232012-06-15 13:27:53 +0000339 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000340 if (isPedantic())
341 warning("No export version for exported package %s", entry.getKey());
342 }
343 }
344 if (v != null)
345 attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
346 }
347 }
348
349 /**
350 *
351 */
352 private void addSources(Jar dot) {
353 if (!hasSources())
354 return;
355
356 Set<PackageRef> packages = Create.set();
357
358 for (TypeRef typeRef : getClassspace().keySet()) {
359 PackageRef packageRef = typeRef.getPackageRef();
360 String sourcePath = typeRef.getSourcePath();
361 String packagePath = packageRef.getPath();
362
363 boolean found = false;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000364 String[] fixed = {
365 "packageinfo", "package.html", "module-info.java", "package-info.java"
366 };
Stuart McCullochbb014372012-06-07 21:57:32 +0000367
368 for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
369 File root = i.next();
370
371 // TODO should use bcp?
372
373 File f = getFile(root, sourcePath);
374 if (f.exists()) {
375 found = true;
376 if (!packages.contains(packageRef)) {
377 packages.add(packageRef);
378 File bdir = getFile(root, packagePath);
379 for (int j = 0; j < fixed.length; j++) {
380 File ff = getFile(bdir, fixed[j]);
381 if (ff.isFile()) {
382 String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
383 dot.putResource(name, new FileResource(ff));
384 }
385 }
386 }
387 if (packageRef.isDefaultPackage())
388 System.err.println("Duh?");
389 dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
390 }
391 }
392 if (!found) {
393 for (Jar jar : getClasspath()) {
394 Resource resource = jar.getResource(sourcePath);
395 if (resource != null) {
396 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000397 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000398 resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
399 if (resource != null) {
400 dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
401 }
402 }
403 }
404 }
405 if (getSourcePath().isEmpty())
Stuart McCulloch2286f232012-06-15 13:27:53 +0000406 warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
Stuart McCullochbb014372012-06-07 21:57:32 +0000407 // TODO copy from the jars where they came from
408 }
409 }
410
411 boolean firstUse = true;
412 private Tree tree;
413
414 public Collection<File> getSourcePath() {
415 if (firstUse) {
416 firstUse = false;
417 String sp = getProperty(SOURCEPATH);
418 if (sp != null) {
419 Parameters map = parseHeader(sp);
420 for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
421 String file = i.next();
422 if (!isDuplicate(file)) {
423 File f = getFile(file);
424 if (!f.isDirectory()) {
425 error("Adding a sourcepath that is not a directory: " + f);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000426 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000427 sourcePath.add(f);
428 }
429 }
430 }
431 }
432 }
433 return sourcePath;
434 }
435
Stuart McCullochd4826102012-06-26 16:34:24 +0000436 private void doVerify(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000437 Verifier verifier = new Verifier(this);
438 // Give the verifier the benefit of our analysis
439 // prevents parsing the files twice
440 verifier.verify();
441 getInfo(verifier);
442 }
443
Stuart McCulloch2286f232012-06-15 13:27:53 +0000444 private void doExpand(Jar dot) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000445
446 // Build an index of the class path that we can then
447 // use destructively
Stuart McCulloch2286f232012-06-15 13:27:53 +0000448 MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
Stuart McCullochbb014372012-06-07 21:57:32 +0000449 for (Jar srce : getClasspath()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000450 for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000451 if (e.getValue() != null)
452 packages.add(e.getKey(), srce);
453 }
454 }
455
456 Parameters privatePackages = getPrivatePackage();
457 if (isTrue(getProperty(Constants.UNDERTEST))) {
458 String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
459 privatePackages.putAll(parseHeader(h));
460 }
461
462 if (!privatePackages.isEmpty()) {
463 Instructions privateFilter = new Instructions(privatePackages);
464 Set<Instruction> unused = doExpand(dot, packages, privateFilter);
465
466 if (!unused.isEmpty()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000467 warning("Unused Private-Package instructions, no such package(s) on the class path: %s", unused);
Stuart McCullochbb014372012-06-07 21:57:32 +0000468 }
469 }
470
471 Parameters exportedPackage = getExportPackage();
472 if (!exportedPackage.isEmpty()) {
473 Instructions exportedFilter = new Instructions(exportedPackage);
474
475 // We ignore unused instructions for exports, they should show
476 // up as errors during analysis. Otherwise any overlapping
477 // packages with the private packages should show up as
478 // unused
479
480 doExpand(dot, packages, exportedFilter);
481 }
482 }
483
484 /**
485 * Destructively filter the packages from the build up index. This index is
486 * used by the Export Package as well as the Private Package
487 *
488 * @param jar
489 * @param name
490 * @param instructions
491 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000492 private Set<Instruction> doExpand(Jar jar, MultiMap<String,Jar> index, Instructions filter) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000493 Set<Instruction> unused = Create.set();
494
Stuart McCulloch2286f232012-06-15 13:27:53 +0000495 for (Entry<Instruction,Attrs> e : filter.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000496 Instruction instruction = e.getKey();
497 if (instruction.isDuplicate())
498 continue;
499
500 Attrs directives = e.getValue();
501
502 // We can optionally filter on the
503 // source of the package. We assume
504 // they all match but this can be overridden
505 // on the instruction
506 Instruction from = new Instruction(directives.get(FROM_DIRECTIVE, "*"));
507
508 boolean used = false;
509
Stuart McCulloch2286f232012-06-15 13:27:53 +0000510 for (Iterator<Entry<String,List<Jar>>> entry = index.entrySet().iterator(); entry.hasNext();) {
511 Entry<String,List<Jar>> p = entry.next();
Stuart McCullochbb014372012-06-07 21:57:32 +0000512
513 String directory = p.getKey();
514 PackageRef packageRef = getPackageRef(directory);
515
516 // Skip * and meta data, we're talking packages!
517 if (packageRef.isMetaData() && instruction.isAny())
518 continue;
519
520 if (!instruction.matches(packageRef.getFQN()))
521 continue;
522
523 // Ensure it is never matched again
524 entry.remove();
525
526 // ! effectively removes it from consideration by others (this
527 // includes exports)
528 if (instruction.isNegated())
529 continue;
530
531 // Do the from: directive, filters on the JAR type
532 List<Jar> providers = filterFrom(from, p.getValue());
533 if (providers.isEmpty())
534 continue;
535
536 int splitStrategy = getSplitStrategy(directives.get(SPLIT_PACKAGE_DIRECTIVE));
537 copyPackage(jar, providers, directory, splitStrategy);
538
539 used = true;
540 }
541
542 if (!used && !isTrue(directives.get("optional:")))
543 unused.add(instruction);
544 }
545 return unused;
546 }
547
548 /**
549 * @param from
550 * @return
551 */
552 private List<Jar> filterFrom(Instruction from, List<Jar> providers) {
553 if (from.isAny())
554 return providers;
555
556 List<Jar> np = new ArrayList<Jar>();
557 for (Iterator<Jar> i = providers.iterator(); i.hasNext();) {
558 Jar j = i.next();
559 if (from.matches(j.getName())) {
560 np.add(j);
561 }
562 }
563 return np;
564 }
565
566 /**
567 * Copy the package from the providers based on the split package strategy.
568 *
569 * @param dest
570 * @param providers
571 * @param directory
572 * @param splitStrategy
573 */
574 private void copyPackage(Jar dest, List<Jar> providers, String path, int splitStrategy) {
575 switch (splitStrategy) {
576 case SPLIT_MERGE_LAST :
577 for (Jar srce : providers) {
578 copy(dest, srce, path, true);
579 }
580 break;
581
582 case SPLIT_MERGE_FIRST :
583 for (Jar srce : providers) {
584 copy(dest, srce, path, false);
585 }
586 break;
587
588 case SPLIT_ERROR :
589 error(diagnostic(path, providers));
590 break;
591
592 case SPLIT_FIRST :
593 copy(dest, providers.get(0), path, false);
594 break;
595
596 default :
597 if (providers.size() > 1)
598 warning("%s", diagnostic(path, providers));
599 for (Jar srce : providers) {
600 copy(dest, srce, path, false);
601 }
602 break;
603 }
604 }
605
606 /**
607 * Cop
608 *
609 * @param dest
610 * @param srce
611 * @param path
612 * @param overwriteResource
613 */
614 private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
Stuart McCullochd4826102012-06-26 16:34:24 +0000615 trace("copy d=" + dest + " s=" + srce +" p="+ path);
Stuart McCullochbb014372012-06-07 21:57:32 +0000616 dest.copy(srce, path, overwrite);
Stuart McCullochd4826102012-06-26 16:34:24 +0000617
618 // bnd.info sources must be preprocessed
619 String bndInfoPath = path + "/bnd.info";
620 Resource r = dest.getResource(bndInfoPath);
621 if ( r != null && !(r instanceof PreprocessResource)) {
622 trace("preprocessing bnd.info");
623 PreprocessResource pp = new PreprocessResource(this, r);
624 dest.putResource(bndInfoPath, pp);
625 }
626
Stuart McCullochbb014372012-06-07 21:57:32 +0000627 if (hasSources()) {
628 String srcPath = "OSGI-OPT/src/" + path;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000629 Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
Stuart McCullochbb014372012-06-07 21:57:32 +0000630 if (srcContents != null) {
631 dest.addDirectory(srcContents, overwrite);
632 }
633 }
634 }
635
636 /**
637 * Analyze the classpath for a split package
638 *
639 * @param pack
640 * @param classpath
641 * @param source
642 * @return
643 */
644 private String diagnostic(String pack, List<Jar> culprits) {
645 // Default is like merge-first, but with a warning
646 return "Split package, multiple jars provide the same package:"
647 + pack
648 + "\nUse Import/Export Package directive -split-package:=(merge-first|merge-last|error|first) to get rid of this warning\n"
649 + "Package found in " + culprits + "\n" //
650 + "Class path " + getClasspath();
651 }
652
653 private int getSplitStrategy(String type) {
654 if (type == null)
655 return SPLIT_DEFAULT;
656
657 if (type.equals("merge-last"))
658 return SPLIT_MERGE_LAST;
659
660 if (type.equals("merge-first"))
661 return SPLIT_MERGE_FIRST;
662
663 if (type.equals("error"))
664 return SPLIT_ERROR;
665
666 if (type.equals("first"))
667 return SPLIT_FIRST;
668
669 error("Invalid strategy for split-package: " + type);
670 return SPLIT_DEFAULT;
671 }
672
673 /**
674 * Matches the instructions against a package.
675 *
Stuart McCulloch2286f232012-06-15 13:27:53 +0000676 * @param instructions
677 * The list of instructions
678 * @param pack
679 * The name of the package
680 * @param unused
681 * The total list of patterns, matched patterns are removed
682 * @param source
683 * The name of the source container, can be filtered upon with
684 * the from: directive.
Stuart McCullochbb014372012-06-07 21:57:32 +0000685 * @return
686 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000687 private Instruction matches(Instructions instructions, String pack, Set<Instruction> unused, String source) {
688 for (Entry<Instruction,Attrs> entry : instructions.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000689 Instruction pattern = entry.getKey();
690
691 // It is possible to filter on the source of the
692 // package with the from: directive. This is an
693 // instruction that must match the name of the
694 // source class path entry.
695
696 String from = entry.getValue().get(FROM_DIRECTIVE);
697 if (from != null) {
698 Instruction f = new Instruction(from);
699 if (!f.matches(source) || f.isNegated())
700 continue;
701 }
702
703 // Now do the normal
704 // matching
705 if (pattern.matches(pack)) {
706 if (unused != null)
707 unused.remove(pattern);
708 return pattern;
709 }
710 }
711 return null;
712 }
713
714 /**
715 * Parse the Bundle-Includes header. Files in the bundles Include header are
716 * included in the jar. The source can be a directory or a file.
717 *
718 * @throws IOException
719 * @throws FileNotFoundException
720 */
721 private void doIncludeResources(Jar jar) throws Exception {
722 String includes = getProperty("Bundle-Includes");
723 if (includes == null) {
724 includes = getProperty(INCLUDERESOURCE);
725 if (includes == null || includes.length() == 0)
726 includes = getProperty("Include-Resource");
Stuart McCulloch2286f232012-06-15 13:27:53 +0000727 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000728 warning("Please use -includeresource instead of Bundle-Includes");
729
730 doIncludeResource(jar, includes);
731
732 }
733
734 private void doIncludeResource(Jar jar, String includes) throws Exception {
735 Parameters clauses = parseHeader(includes);
736 doIncludeResource(jar, clauses);
737 }
738
Stuart McCulloch2286f232012-06-15 13:27:53 +0000739 private void doIncludeResource(Jar jar, Parameters clauses) throws ZipException, IOException, Exception {
740 for (Entry<String,Attrs> entry : clauses.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000741 doIncludeResource(jar, entry.getKey(), entry.getValue());
742 }
743 }
744
Stuart McCulloch2286f232012-06-15 13:27:53 +0000745 private void doIncludeResource(Jar jar, String name, Map<String,String> extra) throws ZipException, IOException,
746 Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000747
748 boolean preprocess = false;
749 boolean absentIsOk = false;
750
751 if (name.startsWith("{") && name.endsWith("}")) {
752 preprocess = true;
753 name = name.substring(1, name.length() - 1).trim();
754 }
755
756 String parts[] = name.split("\\s*=\\s*");
757 String source = parts[0];
758 String destination = parts[0];
759 if (parts.length == 2)
760 source = parts[1];
761
762 if (source.startsWith("-")) {
763 source = source.substring(1);
764 absentIsOk = true;
765 }
766
767 if (source.startsWith("@")) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000768 extractFromJar(jar, source.substring(1), parts.length == 1 ? "" : destination, absentIsOk);
769 } else if (extra.containsKey("cmd")) {
770 doCommand(jar, source, destination, extra, preprocess, absentIsOk);
771 } else if (extra.containsKey("literal")) {
772 String literal = extra.get("literal");
773 Resource r = new EmbeddedResource(literal.getBytes("UTF-8"), 0);
774 String x = extra.get("extra");
775 if (x != null)
776 r.setExtra(x);
777 jar.putResource(name, r);
778 } else {
779 File sourceFile;
780 String destinationPath;
781
782 sourceFile = getFile(source);
783 if (parts.length == 1) {
784 // Directories should be copied to the root
785 // but files to their file name ...
786 if (sourceFile.isDirectory())
787 destinationPath = "";
788 else
789 destinationPath = sourceFile.getName();
790 } else {
791 destinationPath = parts[0];
Stuart McCullochbb014372012-06-07 21:57:32 +0000792 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000793 // Handle directories
794 if (sourceFile.isDirectory()) {
795 destinationPath = doResourceDirectory(jar, extra, preprocess, sourceFile, destinationPath);
796 return;
797 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000798
Stuart McCulloch2286f232012-06-15 13:27:53 +0000799 // destinationPath = checkDestinationPath(destinationPath);
Stuart McCullochbb014372012-06-07 21:57:32 +0000800
Stuart McCulloch2286f232012-06-15 13:27:53 +0000801 if (!sourceFile.exists()) {
802 if (absentIsOk)
803 return;
Stuart McCullochbb014372012-06-07 21:57:32 +0000804
Stuart McCulloch2286f232012-06-15 13:27:53 +0000805 noSuchFile(jar, name, extra, source, destinationPath);
806 } else
807 copy(jar, destinationPath, sourceFile, preprocess, extra);
808 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000809 }
810
811 /**
812 * It is possible in Include-Resource to use a system command that generates
813 * the contents, this is indicated with {@code cmd} attribute. The command
814 * can be repeated for a number of source files with the {@code for}
815 * attribute which indicates a list of repetitions, often down with the
816 * {@link Macro#_lsa(String[])} or {@link Macro#_lsb(String[])} macro. The
817 * repetition will repeat the given command for each item. The @} macro can
818 * be used to replace the current item. If no {@code for} is given, the
Stuart McCulloch2286f232012-06-15 13:27:53 +0000819 * source is used as the only item. If the destination contains a macro,
820 * each iteration will create a new file, otherwise the destination name is
821 * used. The execution of the command is delayed until the JAR is actually
822 * written to the file system for performance reasons.
Stuart McCullochbb014372012-06-07 21:57:32 +0000823 *
824 * @param jar
825 * @param source
826 * @param destination
827 * @param extra
828 * @param preprocess
829 * @param absentIsOk
830 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000831 private void doCommand(Jar jar, String source, String destination, Map<String,String> extra, boolean preprocess,
832 boolean absentIsOk) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000833 String repeat = extra.get("for"); // TODO constant
834 if (repeat == null)
835 repeat = source;
836
837 Collection<String> requires = split(extra.get("requires"));
838 long lastModified = 0;
839 for (String required : requires) {
840 File file = getFile(required);
841 if (!file.isFile()) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000842 error("Include-Resource.cmd for %s, requires %s, but no such file %s", source, required,
843 file.getAbsoluteFile());
844 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000845 lastModified = Math.max(lastModified, file.lastModified());
846 }
847
848 String cmd = extra.get("cmd");
849
850 Collection<String> items = Processor.split(repeat);
851
852 CombinedResource cr = null;
853
854 if (!destination.contains("${@}")) {
855 cr = new CombinedResource();
856 }
857 trace("last modified requires %s", lastModified);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000858
Stuart McCullochbb014372012-06-07 21:57:32 +0000859 for (String item : items) {
860 setProperty("@", item);
861 try {
862 String path = getReplacer().process(destination);
863 String command = getReplacer().process(cmd);
864 File file = getFile(item);
865
866 Resource r = new CommandResource(command, this, Math.max(lastModified,
Stuart McCulloch2286f232012-06-15 13:27:53 +0000867 file.exists() ? file.lastModified() : 0L));
Stuart McCullochbb014372012-06-07 21:57:32 +0000868
869 if (preprocess)
870 r = new PreprocessResource(this, r);
871
872 if (cr == null)
873 jar.putResource(path, r);
874 else
875 cr.addResource(r);
876 }
877 finally {
878 unsetProperty("@");
879 }
880 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000881
Stuart McCullochbb014372012-06-07 21:57:32 +0000882 // Add last so the correct modification date is used
883 // to update the modified time.
Stuart McCulloch2286f232012-06-15 13:27:53 +0000884 if (cr != null)
Stuart McCullochbb014372012-06-07 21:57:32 +0000885 jar.putResource(destination, cr);
886 }
887
Stuart McCulloch2286f232012-06-15 13:27:53 +0000888 private String doResourceDirectory(Jar jar, Map<String,String> extra, boolean preprocess, File sourceFile,
889 String destinationPath) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000890 String filter = extra.get("filter:");
891 boolean flatten = isTrue(extra.get("flatten:"));
892 boolean recursive = true;
893 String directive = extra.get("recursive:");
894 if (directive != null) {
895 recursive = isTrue(directive);
896 }
897
898 Instruction.Filter iFilter = null;
899 if (filter != null) {
900 iFilter = new Instruction.Filter(new Instruction(filter), recursive, getDoNotCopy());
Stuart McCulloch2286f232012-06-15 13:27:53 +0000901 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000902 iFilter = new Instruction.Filter(null, recursive, getDoNotCopy());
903 }
904
Stuart McCulloch2286f232012-06-15 13:27:53 +0000905 Map<String,File> files = newMap();
Stuart McCullochbb014372012-06-07 21:57:32 +0000906 resolveFiles(sourceFile, iFilter, recursive, destinationPath, files, flatten);
907
Stuart McCulloch2286f232012-06-15 13:27:53 +0000908 for (Map.Entry<String,File> entry : files.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000909 copy(jar, entry.getKey(), entry.getValue(), preprocess, extra);
910 }
911 return destinationPath;
912 }
913
Stuart McCulloch2286f232012-06-15 13:27:53 +0000914 private void resolveFiles(File dir, FileFilter filter, boolean recursive, String path, Map<String,File> files,
915 boolean flatten) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000916
917 if (doNotCopy(dir.getName())) {
918 return;
919 }
920
921 File[] fs = dir.listFiles(filter);
922 for (File file : fs) {
923 if (file.isDirectory()) {
924 if (recursive) {
925 String nextPath;
926 if (flatten)
927 nextPath = path;
928 else
929 nextPath = appendPath(path, file.getName());
930
931 resolveFiles(file, filter, recursive, nextPath, files, flatten);
932 }
933 // Directories are ignored otherwise
Stuart McCulloch2286f232012-06-15 13:27:53 +0000934 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000935 String p = appendPath(path, file.getName());
936 if (files.containsKey(p))
937 warning("Include-Resource overwrites entry %s from file %s", p, file);
938 files.put(p, file);
939 }
940 }
941 }
942
Stuart McCullochd4826102012-06-26 16:34:24 +0000943 private void noSuchFile(Jar jar, @SuppressWarnings("unused") String clause, Map<String,String> extra, String source, String destinationPath)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000944 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000945 Jar src = getJarFromName(source, "Include-Resource " + source);
946 if (src != null) {
947 // Do not touch the manifest so this also
948 // works for signed files.
949 src.setDoNotTouchManifest();
950 JarResource jarResource = new JarResource(src);
951 jar.putResource(destinationPath, jarResource);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000952 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000953 Resource lastChance = make.process(source);
954 if (lastChance != null) {
955 String x = extra.get("extra");
956 if (x != null)
957 lastChance.setExtra(x);
958 jar.putResource(destinationPath, lastChance);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000959 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000960 error("Input file does not exist: " + source);
961 }
962 }
963
964 /**
965 * Extra resources from a Jar and add them to the given jar. The clause is
966 * the
967 *
968 * @param jar
969 * @param clauses
970 * @param i
971 * @throws ZipException
972 * @throws IOException
973 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000974 private void extractFromJar(Jar jar, String source, String destination, boolean absentIsOk) throws ZipException,
975 IOException {
Stuart McCullochbb014372012-06-07 21:57:32 +0000976 // Inline all resources and classes from another jar
977 // optionally appended with a modified regular expression
978 // like @zip.jar!/META-INF/MANIFEST.MF
979 int n = source.lastIndexOf("!/");
980 Instruction instr = null;
981 if (n > 0) {
982 instr = new Instruction(source.substring(n + 2));
983 source = source.substring(0, n);
984 }
985
986 // Pattern filter = null;
987 // if (n > 0) {
988 // String fstring = source.substring(n + 2);
989 // source = source.substring(0, n);
990 // filter = wildcard(fstring);
991 // }
992 Jar sub = getJarFromName(source, "extract from jar");
993 if (sub == null) {
994 if (absentIsOk)
995 return;
996
997 error("Can not find JAR file " + source);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000998 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +0000999 addAll(jar, sub, instr, destination);
1000 }
1001 }
1002
1003 /**
1004 * Add all the resources in the given jar that match the given filter.
1005 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001006 * @param sub
1007 * the jar
1008 * @param filter
1009 * a pattern that should match the resoures in sub to be added
Stuart McCullochbb014372012-06-07 21:57:32 +00001010 */
1011 public boolean addAll(Jar to, Jar sub, Instruction filter) {
1012 return addAll(to, sub, filter, "");
1013 }
1014
1015 /**
1016 * Add all the resources in the given jar that match the given filter.
1017 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001018 * @param sub
1019 * the jar
1020 * @param filter
1021 * a pattern that should match the resoures in sub to be added
Stuart McCullochbb014372012-06-07 21:57:32 +00001022 */
1023 public boolean addAll(Jar to, Jar sub, Instruction filter, String destination) {
1024 boolean dupl = false;
1025 for (String name : sub.getResources().keySet()) {
1026 if ("META-INF/MANIFEST.MF".equals(name))
1027 continue;
1028
1029 if (filter == null || filter.matches(name) != filter.isNegated())
Stuart McCulloch2286f232012-06-15 13:27:53 +00001030 dupl |= to.putResource(Processor.appendPath(destination, name), sub.getResource(name), true);
Stuart McCullochbb014372012-06-07 21:57:32 +00001031 }
1032 return dupl;
1033 }
1034
Stuart McCulloch2286f232012-06-15 13:27:53 +00001035 private void copy(Jar jar, String path, File from, boolean preprocess, Map<String,String> extra) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +00001036 if (doNotCopy(from.getName()))
1037 return;
1038
1039 if (from.isDirectory()) {
1040
1041 File files[] = from.listFiles();
1042 for (int i = 0; i < files.length; i++) {
1043 copy(jar, appendPath(path, files[i].getName()), files[i], preprocess, extra);
1044 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001045 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001046 if (from.exists()) {
1047 Resource resource = new FileResource(from);
1048 if (preprocess) {
1049 resource = new PreprocessResource(this, resource);
1050 }
1051 String x = extra.get("extra");
1052 if (x != null)
1053 resource.setExtra(x);
1054 if (path.endsWith("/"))
1055 path = path + from.getName();
1056 jar.putResource(path, resource);
1057
1058 if (isTrue(extra.get(LIB_DIRECTIVE))) {
1059 setProperty(BUNDLE_CLASSPATH, append(getProperty(BUNDLE_CLASSPATH), path));
1060 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001061 } else {
Stuart McCullochbb014372012-06-07 21:57:32 +00001062 error("Input file does not exist: " + from);
1063 }
1064 }
1065 }
1066
1067 public void setSourcepath(File[] files) {
1068 for (int i = 0; i < files.length; i++)
1069 addSourcepath(files[i]);
1070 }
1071
1072 public void addSourcepath(File cp) {
1073 if (!cp.exists())
1074 warning("File on sourcepath that does not exist: " + cp);
1075
1076 sourcePath.add(cp);
1077 }
1078
1079 public void close() {
1080 super.close();
1081 }
1082
1083 /**
1084 * Build Multiple jars. If the -sub command is set, we filter the file with
1085 * the given patterns.
1086 *
1087 * @return
1088 * @throws Exception
1089 */
1090 public Jar[] builds() throws Exception {
1091 begin();
1092
1093 // Are we acting as a conduit for another JAR?
1094 String conduit = getProperty(CONDUIT);
1095 if (conduit != null) {
1096 Parameters map = parseHeader(conduit);
1097 Jar[] result = new Jar[map.size()];
1098 int n = 0;
1099 for (String file : map.keySet()) {
1100 Jar c = new Jar(getFile(file));
1101 addClose(c);
1102 String name = map.get(file).get("name");
1103 if (name != null)
1104 c.setName(name);
1105
1106 result[n++] = c;
1107 }
1108 return result;
1109 }
1110
1111 List<Jar> result = new ArrayList<Jar>();
1112 List<Builder> builders;
1113
1114 builders = getSubBuilders();
1115
1116 for (Builder builder : builders) {
1117 try {
1118 Jar jar = builder.build();
1119 jar.setName(builder.getBsn());
1120 result.add(jar);
1121 }
1122 catch (Exception e) {
1123 e.printStackTrace();
1124 error("Sub Building " + builder.getBsn(), e);
1125 }
1126 if (builder != this)
1127 getInfo(builder, builder.getBsn() + ": ");
1128 }
1129 return result.toArray(new Jar[result.size()]);
1130 }
1131
1132 /**
1133 * Answer a list of builders that represent this file or a list of files
1134 * specified in -sub. This list can be empty. These builders represents to
1135 * be created artifacts and are each scoped to such an artifacts. The
1136 * builders can be used to build the bundles or they can be used to find out
1137 * information about the to be generated bundles.
1138 *
1139 * @return List of 0..n builders representing artifacts.
1140 * @throws Exception
1141 */
1142 public List<Builder> getSubBuilders() throws Exception {
1143 String sub = getProperty(SUB);
1144 if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
1145 return Arrays.asList(this);
1146
1147 List<Builder> builders = new ArrayList<Builder>();
1148 if (isTrue(getProperty(NOBUNDLES)))
1149 return builders;
1150
1151 Parameters subsMap = parseHeader(sub);
1152 for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
1153 File file = getFile(i.next());
1154 if (file.isFile()) {
1155 builders.add(getSubBuilder(file));
1156 i.remove();
1157 }
1158 }
1159
1160 Instructions instructions = new Instructions(subsMap);
1161
1162 List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
1163
1164 nextFile: while (members.size() > 0) {
1165
1166 File file = members.remove(0);
1167
1168 // Check if the file is one of our parents
1169 Processor p = this;
1170 while (p != null) {
1171 if (file.equals(p.getPropertiesFile()))
1172 continue nextFile;
1173 p = p.getParent();
1174 }
1175
1176 for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
1177
1178 Instruction instruction = i.next();
1179 if (instruction.matches(file.getName())) {
1180
1181 if (!instruction.isNegated()) {
1182 builders.add(getSubBuilder(file));
1183 }
1184
1185 // Because we matched (even though we could be negated)
1186 // we skip any remaining searches
1187 continue nextFile;
1188 }
1189 }
1190 }
1191 return builders;
1192 }
1193
1194 public Builder getSubBuilder(File file) throws Exception {
1195 Builder builder = getSubBuilder();
1196 if (builder != null) {
1197 builder.setProperties(file);
1198 addClose(builder);
1199 }
1200 return builder;
1201 }
1202
1203 public Builder getSubBuilder() throws Exception {
1204 Builder builder = new Builder(this);
1205 builder.setBase(getBase());
1206
1207 for (Jar file : getClasspath()) {
1208 builder.addClasspath(file);
1209 }
1210
1211 return builder;
1212 }
1213
1214 /**
1215 * A macro to convert a maven version to an OSGi version
1216 */
1217
1218 public String _maven_version(String args[]) {
1219 if (args.length > 2)
1220 error("${maven_version} macro receives too many arguments " + Arrays.toString(args));
Stuart McCulloch2286f232012-06-15 13:27:53 +00001221 else if (args.length < 2)
1222 error("${maven_version} macro has no arguments, use ${maven_version;1.2.3-SNAPSHOT}");
1223 else {
1224 return cleanupVersion(args[1]);
1225 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001226 return null;
1227 }
1228
Stuart McCulloch2286f232012-06-15 13:27:53 +00001229 public String _permissions(String args[]) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001230 StringBuilder sb = new StringBuilder();
1231
1232 for (String arg : args) {
1233 if ("packages".equals(arg) || "all".equals(arg)) {
1234 for (PackageRef imp : getImports().keySet()) {
1235 if (!imp.isJava()) {
1236 sb.append("(org.osgi.framework.PackagePermission \"");
1237 sb.append(imp);
1238 sb.append("\" \"import\")\r\n");
1239 }
1240 }
1241 for (PackageRef exp : getExports().keySet()) {
1242 sb.append("(org.osgi.framework.PackagePermission \"");
1243 sb.append(exp);
1244 sb.append("\" \"export\")\r\n");
1245 }
Stuart McCulloch2286f232012-06-15 13:27:53 +00001246 } else if ("admin".equals(arg) || "all".equals(arg)) {
1247 sb.append("(org.osgi.framework.AdminPermission)");
1248 } else if ("permissions".equals(arg))
1249 ;
Stuart McCullochbb014372012-06-07 21:57:32 +00001250 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001251 error("Invalid option in ${permissions}: %s", arg);
Stuart McCullochbb014372012-06-07 21:57:32 +00001252 }
1253 return sb.toString();
1254 }
1255
1256 /**
1257 *
1258 */
1259 public void removeBundleSpecificHeaders() {
1260 Set<String> set = new HashSet<String>(Arrays.asList(BUNDLE_SPECIFIC_HEADERS));
1261 setForceLocal(set);
1262 }
1263
1264 /**
1265 * Check if the given resource is in scope of this bundle. That is, it
1266 * checks if the Include-Resource includes this resource or if it is a class
Stuart McCulloch2286f232012-06-15 13:27:53 +00001267 * file it is on the class path and the Export-Package or Private-Package
Stuart McCullochbb014372012-06-07 21:57:32 +00001268 * include this resource.
1269 *
1270 * @param f
1271 * @return
1272 */
1273 public boolean isInScope(Collection<File> resources) throws Exception {
1274 Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
1275 clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
1276 if (isTrue(getProperty(Constants.UNDERTEST))) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001277 clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
Stuart McCullochbb014372012-06-07 21:57:32 +00001278 }
1279
1280 Collection<String> ir = getIncludedResourcePrefixes();
1281
1282 Instructions instructions = new Instructions(clauses);
1283
1284 for (File r : resources) {
1285 String cpEntry = getClasspathEntrySuffix(r);
Stuart McCulloch2286f232012-06-15 13:27:53 +00001286
Stuart McCullochbb014372012-06-07 21:57:32 +00001287 if (cpEntry != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001288
1289 if (cpEntry.equals("")) // Meaning we actually have a CPE
1290 return true;
1291
Stuart McCullochbb014372012-06-07 21:57:32 +00001292 String pack = Descriptors.getPackage(cpEntry);
1293 Instruction i = matches(instructions, pack, null, r.getName());
1294 if (i != null)
1295 return !i.isNegated();
1296 }
1297
1298 // Check if this resource starts with one of the I-C header
1299 // paths.
1300 String path = r.getAbsolutePath();
1301 for (String p : ir) {
1302 if (path.startsWith(p))
1303 return true;
1304 }
1305 }
1306 return false;
1307 }
1308
1309 /**
1310 * Extra the paths for the directories and files that are used in the
1311 * Include-Resource header.
1312 *
1313 * @return
1314 */
1315 private Collection<String> getIncludedResourcePrefixes() {
1316 List<String> prefixes = new ArrayList<String>();
1317 Parameters includeResource = getIncludeResource();
Stuart McCulloch2286f232012-06-15 13:27:53 +00001318 for (Entry<String,Attrs> p : includeResource.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001319 if (p.getValue().containsKey("literal"))
1320 continue;
1321
1322 Matcher m = IR_PATTERN.matcher(p.getKey());
1323 if (m.matches()) {
1324 File f = getFile(m.group(1));
1325 prefixes.add(f.getAbsolutePath());
1326 }
1327 }
1328 return prefixes;
1329 }
1330
1331 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +00001332 * Answer the string of the resource that it has in the container. It is
1333 * possible that the resource is a classpath entry. In that case an empty
1334 * string is returned.
Stuart McCullochbb014372012-06-07 21:57:32 +00001335 *
Stuart McCulloch2286f232012-06-15 13:27:53 +00001336 * @param resource
1337 * The resource to look for
1338 * @return A suffix on the classpath or "" if the resource is a class path
1339 * entry
Stuart McCullochbb014372012-06-07 21:57:32 +00001340 * @throws Exception
1341 */
1342 public String getClasspathEntrySuffix(File resource) throws Exception {
1343 for (Jar jar : getClasspath()) {
1344 File source = jar.getSource();
1345 if (source != null) {
Stuart McCulloch2286f232012-06-15 13:27:53 +00001346
Stuart McCullochbb014372012-06-07 21:57:32 +00001347 source = source.getCanonicalFile();
1348 String sourcePath = source.getAbsolutePath();
1349 String resourcePath = resource.getAbsolutePath();
Stuart McCulloch2286f232012-06-15 13:27:53 +00001350 if (sourcePath.equals(resourcePath))
1351 return ""; // Matches a classpath entry
Stuart McCullochbb014372012-06-07 21:57:32 +00001352
1353 if (resourcePath.startsWith(sourcePath)) {
1354 // Make sure that the path name is translated correctly
1355 // i.e. on Windows the \ must be translated to /
1356 String filePath = resourcePath.substring(sourcePath.length() + 1);
1357
1358 return filePath.replace(File.separatorChar, '/');
1359 }
1360 }
1361 }
1362 return null;
1363 }
1364
1365 /**
Stuart McCulloch2286f232012-06-15 13:27:53 +00001366 * doNotCopy The doNotCopy variable maintains a patter for files that should
1367 * not be copied. There is a default {@link #DEFAULT_DO_NOT_COPY} but this
1368 * ca be overridden with the {@link Constants#DONOTCOPY} property.
Stuart McCullochbb014372012-06-07 21:57:32 +00001369 */
1370
1371 public boolean doNotCopy(String v) {
1372 return getDoNotCopy().matcher(v).matches();
1373 }
1374
1375 public Pattern getDoNotCopy() {
1376 if (xdoNotCopy == null) {
1377 String string = null;
1378 try {
1379 string = getProperty(DONOTCOPY, DEFAULT_DO_NOT_COPY);
1380 xdoNotCopy = Pattern.compile(string);
1381 }
1382 catch (Exception e) {
1383 error("Invalid value for %s, value is %s", DONOTCOPY, string);
1384 xdoNotCopy = Pattern.compile(DEFAULT_DO_NOT_COPY);
1385 }
1386 }
1387 return xdoNotCopy;
1388 }
1389
1390 /**
1391 */
1392
1393 static MakeBnd makeBnd = new MakeBnd();
1394 static MakeCopy makeCopy = new MakeCopy();
1395 static ServiceComponent serviceComponent = new ServiceComponent();
1396 static DSAnnotations dsAnnotations = new DSAnnotations();
1397 static MetatypePlugin metatypePlugin = new MetatypePlugin();
1398
1399 @Override
1400 protected void setTypeSpecificPlugins(Set<Object> list) {
1401 list.add(makeBnd);
1402 list.add(makeCopy);
1403 list.add(serviceComponent);
1404 list.add(dsAnnotations);
1405 list.add(metatypePlugin);
1406 super.setTypeSpecificPlugins(list);
1407 }
1408
1409 /**
1410 * Diff this bundle to another bundle for the given packages.
1411 *
1412 * @throws Exception
1413 */
1414
Stuart McCullochd4826102012-06-26 16:34:24 +00001415 public void doDiff(@SuppressWarnings("unused") Jar dot) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +00001416 Parameters diffs = parseHeader(getProperty("-diff"));
1417 if (diffs.isEmpty())
1418 return;
1419
1420 trace("diff %s", diffs);
1421
1422 if (tree == null)
1423 tree = differ.tree(this);
1424
Stuart McCulloch2286f232012-06-15 13:27:53 +00001425 for (Entry<String,Attrs> entry : diffs.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +00001426 String path = entry.getKey();
1427 File file = getFile(path);
1428 if (!file.isFile()) {
1429 error("Diffing against %s that is not a file", file);
1430 continue;
1431 }
1432
1433 boolean full = entry.getValue().get("--full") != null;
1434 boolean warning = entry.getValue().get("--warning") != null;
1435
1436 Tree other = differ.tree(file);
1437 Diff api = tree.diff(other).get("<api>");
1438 Instructions instructions = new Instructions(entry.getValue().get("--pack"));
1439
1440 trace("diff against %s --full=%s --pack=%s --warning=%s", file, full, instructions);
1441 for (Diff p : api.getChildren()) {
1442 String pname = p.getName();
1443 if (p.getType() == Type.PACKAGE && instructions.matches(pname)) {
1444 if (p.getDelta() != Delta.UNCHANGED) {
1445
1446 if (!full)
1447 if (warning)
1448 warning("Differ %s", p);
1449 else
1450 error("Differ %s", p);
1451 else {
1452 if (warning)
Stuart McCulloch2286f232012-06-15 13:27:53 +00001453 warning("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochbb014372012-06-07 21:57:32 +00001454 else
Stuart McCulloch2286f232012-06-15 13:27:53 +00001455 error("Diff found a difference in %s for packages %s", file, instructions);
Stuart McCullochbb014372012-06-07 21:57:32 +00001456 show(p, "", warning);
1457 }
1458 }
1459 }
1460 }
1461 }
1462 }
1463
1464 /**
1465 * Show the diff recursively
1466 *
1467 * @param p
1468 * @param i
1469 */
1470 private void show(Diff p, String indent, boolean warning) {
1471 Delta d = p.getDelta();
1472 if (d == Delta.UNCHANGED)
1473 return;
1474
1475 if (warning)
1476 warning("%s%s", indent, p);
1477 else
1478 error("%s%s", indent, p);
1479
1480 indent = indent + " ";
1481 switch (d) {
1482 case CHANGED :
1483 case MAJOR :
1484 case MINOR :
1485 case MICRO :
1486 break;
1487
1488 default :
1489 return;
1490 }
1491 for (Diff c : p.getChildren())
1492 show(c, indent, warning);
1493 }
1494
1495 /**
1496 * Base line against a previous version
1497 *
1498 * @throws Exception
1499 */
1500
1501 private void doBaseline(Jar dot) throws Exception {
1502 Parameters diffs = parseHeader(getProperty("-baseline"));
1503 if (diffs.isEmpty())
1504 return;
1505
1506 System.err.printf("baseline %s%n", diffs);
1507
Stuart McCulloch7adbc952012-07-12 22:12:58 +00001508 Jar other = getBaselineJar();
1509 if (other == null) {
1510 return;
1511 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001512 Baseline baseline = new Baseline(this, differ);
Stuart McCulloch7adbc952012-07-12 22:12:58 +00001513 Set<Info> infos = baseline.baseline(dot, other, null);
1514 for (Info info : infos) {
1515 if (info.mismatch) {
1516 error("%s %-50s %-10s %-10s %-10s %-10s %-10s\n", info.mismatch ? '*' : ' ', info.packageName,
1517 info.packageDiff.getDelta(), info.newerVersion, info.olderVersion, info.suggestedVersion,
1518 info.suggestedIfProviders == null ? "-" : info.suggestedIfProviders);
Stuart McCullochbb014372012-06-07 21:57:32 +00001519 }
1520 }
1521 }
1522
1523 public void addSourcepath(Collection<File> sourcepath) {
1524 for (File f : sourcepath) {
1525 addSourcepath(f);
1526 }
1527 }
1528
Stuart McCulloch7adbc952012-07-12 22:12:58 +00001529 public Jar getBaselineJar() throws Exception {
1530
1531 List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
1532
1533 Parameters diffs = parseHeader(getProperty("-baseline"));
1534 File baselineFile = null;
1535 if (diffs.isEmpty()) {
1536 String repoName = getProperty("-baseline-repo");
1537 if (repoName == null) {
1538 return null;
1539 }
1540 for (RepositoryPlugin repo : repos) {
1541 if (repoName.equals(repo.getName())) {
1542 baselineFile = repo.get(getBsn(), null, Strategy.HIGHEST, null);
1543 break;
1544 }
1545 }
1546 } else {
1547
1548 String bsn = null;
1549 String version = null;
1550 for (Entry<String,Attrs> entry : diffs.entrySet()) {
1551 bsn = entry.getKey();
1552 if ("@".equals(bsn)) {
1553 bsn = getBsn();
1554 }
1555 version = entry.getValue().get(Constants.VERSION_ATTRIBUTE);
1556 break;
1557 }
1558
1559 for (RepositoryPlugin repo : repos) {
1560 if (version == null) {
1561 baselineFile = repo.get(bsn, null, Strategy.HIGHEST, null);
1562 } else {
1563 baselineFile = repo.get(bsn, version, Strategy.EXACT, null);
1564 }
1565 if (baselineFile != null) {
1566 break;
1567 }
1568 }
1569 }
1570 if (baselineFile == null) {
1571 return new Jar(".");
1572 }
1573 return new Jar(baselineFile);
1574 }
Stuart McCullochbb014372012-06-07 21:57:32 +00001575}