blob: 6f6a33e15a64e8dfc366baa0d829bcdef01b7400 [file] [log] [blame]
Richard S. Hall85bafab2009-07-13 13:25:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20package org.cauldron.bld.bnd;
21
22import static java.lang.String.format;
23
24import java.io.File;
25import java.io.FileOutputStream;
26import java.io.FileWriter;
27import java.io.IOException;
28import java.io.OutputStream;
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.HashSet;
33import java.util.List;
34import java.util.Map;
35import java.util.Properties;
36import java.util.Set;
37import java.util.jar.Attributes;
38
39import org.cauldron.bld.config.BldAttr;
40import org.cauldron.bld.config.IBldProject;
41import org.cauldron.bld.config.IBldProject.IBldBundle;
42import org.cauldron.bld.core.internal.model.osgi.PackageImport;
43import org.cauldron.bld.core.repository.SystemRepositoryProvider;
44import org.cauldron.sigil.model.common.VersionRange;
45import org.cauldron.sigil.model.osgi.IPackageExport;
46import org.cauldron.sigil.model.osgi.IPackageImport;
47import org.cauldron.sigil.model.osgi.IRequiredBundle;
48import org.osgi.framework.Version;
49
50import aQute.lib.osgi.Builder;
51import aQute.lib.osgi.Constants;
52import aQute.lib.osgi.Jar;
53import aQute.lib.osgi.Processor;
54
55public class BundleBuilder {
56 public static final String COMPONENT_ACTIVATOR_PKG = "XXX-FIXME-XXX";
57 public static final String COMPONENT_ACTIVATOR = COMPONENT_ACTIVATOR_PKG + ".Activator";
58 public static final String[] COMPONENT_ACTIVATOR_DEPS = { "XXX-FIXME-XXX",
59 "org.osgi.framework", "org.osgi.util.tracker" };
60 public static final String COMPONENT_DIR = "META-INF/XXX-FIXME-XXX";
61 public static final String COMPONENT_FLAG = "Installable-Component";
62 public static final String COMPONENT_LIST = "Installable-Component-Templates";
63
64 private IBldProject project;
65 private File[] classpath;
66 private String destPattern;
67 private Properties env;
68 private List<String> errors = new ArrayList<String>();
69 private List<String> warnings = new ArrayList<String>();
70
71 private Set<String> unused = new HashSet<String>();
72 private String lastBundle = null;
73
74 private boolean addMissingImports;
75 private boolean omitUnusedImports;
76 private String defaultPubtype;
77 private String codebaseFormat;
78 private Set<String> systemPkgs;
79
80 public interface Log {
81 void warn(String msg);
82
83 void verbose(String msg);
84 }
85
86 /**
87 * creates a BundleBuilder.
88 *
89 * @param classpath
90 * @param destPattern
91 * ivy-like pattern: PATH/[id].[ext] [id] is replaced with the
92 * bundle id. [name] is replaced with the Bundle-SymbolicName
93 * [ext] is replaced with "jar".
94 * @param hashtable
95 */
96 public BundleBuilder(IBldProject project, File[] classpath, String destPattern, Properties env) {
97 this.project = project;
98 this.classpath = classpath;
99 this.destPattern = destPattern;
100 this.env = env;
101
102 Properties options = project.getOptions();
103
104 addMissingImports = options.containsKey(BldAttr.OPTION_ADD_IMPORTS)
105 && Boolean.parseBoolean(options.getProperty(BldAttr.OPTION_ADD_IMPORTS));
106 omitUnusedImports = options.containsKey(BldAttr.OPTION_OMIT_IMPORTS)
107 && Boolean.parseBoolean(options.getProperty(BldAttr.OPTION_OMIT_IMPORTS));
108
109 defaultPubtype = options.getProperty(BldAttr.PUBTYPE_ATTRIBUTE, "rmi.codebase");
110
111 codebaseFormat = options.getProperty("codebaseFormat",
112 "cds://%1$s?bundle.symbolic.name=%2$s&type=%3$s");
113
114 for (IBldBundle b : project.getBundles()) {
115 lastBundle = b.getId();
116 for (IPackageImport import1 : b.getImports()) {
117 if (import1.getOSGiImport().equals(IPackageImport.OSGiImport.AUTO)) {
118 unused.add(import1.getPackageName());
119 }
120 }
121 }
122
123 try {
124 systemPkgs = new HashSet<String>();
125 Properties profile = SystemRepositoryProvider.readProfile(null);
126 String pkgs = profile.getProperty("org.osgi.framework.system.packages");
127 for (String pkg : pkgs.split(",\\s*")) {
128 systemPkgs.add(pkg);
129 }
130 } catch (IOException e) {
131 // TODO Auto-generated catch block
132 e.printStackTrace();
133 }
134 }
135
136 public List<String> errors() {
137 return errors;
138 }
139
140 public List<String> warnings() {
141 return warnings;
142 }
143
144 @SuppressWarnings("unchecked")
145 private void convertErrors(String prefix, List messages) {
146 // TODO: make error mapping more generic
147 final String jarEmpty = "The JAR is empty";
148
149 for (Object omsg : messages) {
150 if (jarEmpty.equals(omsg))
151 warnings.add(prefix + omsg);
152 else
153 errors.add(prefix + omsg);
154 }
155 }
156
157 @SuppressWarnings("unchecked")
158 private void convertWarnings(String prefix, List messages) {
159 for (Object omsg : messages) {
160 warnings.add(prefix + omsg);
161 }
162 }
163
164 public boolean createBundle(IBldBundle bundle, boolean force, Log log) throws Exception {
165 int bracket = destPattern.indexOf('[');
166 if (bracket < 0) {
167 throw new Exception("destPattern MUST contain [id] or [name].");
168 }
169
170 String dest = destPattern.replaceFirst("\\[id\\]", bundle.getId());
171 dest = dest.replaceFirst("\\[name\\]", bundle.getSymbolicName());
172 dest = dest.replaceFirst("\\[ext\\]", "jar");
173
174 bracket = dest.indexOf('[');
175 if (bracket >= 0) {
176 String token = dest.substring(bracket);
177 throw new Exception("destPattern: expected [id] or [name]: " + token);
178 }
179
180 errors.clear();
181 warnings.clear();
182
183 if (!bundle.getDownloadContents().isEmpty()) {
184 // create dljar
185 Properties dlspec = new Properties();
186 StringBuilder sb = new StringBuilder();
187
188 for (String pkg : bundle.getDownloadContents()) {
189 if (sb.length() > 0)
190 sb.append(",");
191 sb.append(pkg);
192 }
193
194 dlspec.setProperty(Constants.PRIVATE_PACKAGE, sb.toString());
195 dlspec.setProperty(Constants.BUNDLE_NAME, "Newton download jar");
196 dlspec.setProperty(Constants.NOEXTRAHEADERS, "true");
197 // stop it being a bundle, so cds doesn't scan it
198 dlspec.setProperty(Constants.REMOVE_HEADERS, Constants.BUNDLE_SYMBOLICNAME);
199
200 Builder builder = new Builder();
201 builder.setProperties(dlspec);
202 builder.setClasspath(classpath);
203
204 Jar dljar = builder.build();
205 convertErrors("BND (dljar): ", builder.getErrors());
206 convertWarnings("BND (dljar): ", builder.getWarnings());
207
208 String dldest = dest.replaceFirst("\\.jar$", "-dl.jar");
209 File dloutput = new File(dldest);
210 if (!dloutput.exists() || dloutput.lastModified() <= dljar.lastModified() || force) {
211 // jar.write(dldest) catches and ignores IOException
212 OutputStream out = new FileOutputStream(dldest);
213 dljar.write(out);
214 out.close();
215 dljar.close();
216 // XXX deleting dljar causes it to be rebuilt each time
217 // XXX but leaving it mean it may be installed where it's not
218 // wanted/needed.
219 dloutput.deleteOnExit();
220 }
221 builder.close();
222 }
223
224 Properties spec = getBndSpec(bundle, dest);
225
226 if (log != null) {
227 log.verbose("BND instructions: " + spec.toString());
228 }
229
230 Builder builder = new Builder();
231 builder.setPedantic(true);
232 builder.setProperties(spec);
233 builder.mergeProperties(env, false);
234
235 builder.setClasspath(classpath);
236 // builder.setSourcepath(sourcepath);
237
238 Jar jar = builder.build();
239
240 convertErrors("BND: ", builder.getErrors());
241 convertWarnings("BND: ", builder.getWarnings());
242
243 augmentImports(builder, jar, bundle);
244
245 if (log != null) {
246 for (String warn : warnings) {
247 log.warn(warn);
248 }
249 }
250
251 if (!errors.isEmpty()) {
252 throw new Exception(errors.toString());
253 }
254
255 boolean modified = false;
256 File output = new File(dest);
257
258 if (!output.exists() || force || (output.lastModified() <= jar.lastModified())
259 || (output.lastModified() <= project.getLastModified())) {
260 modified = true;
261 // jar.write(dest) catches and ignores IOException
262 OutputStream out = new FileOutputStream(dest);
263 jar.write(out);
264 out.close();
265 jar.close();
266 }
267
268 builder.close();
269
270 return modified;
271 }
272
273 private void augmentImports(Builder builder, Jar jar, IBldBundle bundle) throws IOException {
274 Attributes main = jar.getManifest().getMainAttributes();
275 String impHeader = main.getValue(Constants.IMPORT_PACKAGE);
276 Map<String, Map<String, String>> bndImports = Processor.parseHeader(impHeader, builder);
277
278 if (bndImports.isEmpty())
279 return;
280
281 ArrayList<String> self = new ArrayList<String>();
282 ArrayList<String> missing = new ArrayList<String>();
283 ArrayList<String> modified = new ArrayList<String>();
284 ArrayList<String> unversioned = new ArrayList<String>();
285
286 String expHeader = main.getValue(Constants.EXPORT_PACKAGE);
287 Set<String> bndExports = Processor.parseHeader(expHeader, builder).keySet();
288
289 HashMap<String, IPackageImport> imports = new HashMap<String, IPackageImport>();
290 for (IPackageImport pi : getImports(bundle)) {
291 switch (pi.getOSGiImport()) {
292 case NEVER:
293 break;
294 case ALWAYS:
295 String pkg = pi.getPackageName();
296 if (!bndImports.containsKey(pkg)) {
297 // Bnd doesn't think this import is needed - but we know
298 // better
299 HashMap<String, String> attrs = new HashMap<String, String>();
300 attrs.put(BldAttr.VERSION_ATTRIBUTE, pi.getVersions().toString());
301 bndImports.put(pkg, attrs);
302 modified.add(pkg + ";resolve=runtime");
303 }
304 // fall thru */
305 case AUTO:
306 imports.put(pi.getPackageName(), pi);
307 break;
308 }
309 }
310
311 boolean importDot = false;
312
313 for (String pkg : bndImports.keySet()) {
314 unused.remove(pkg);
315 Map<String, String> attrs = bndImports.get(pkg);
316 String currentVersion = (String) attrs.get(BldAttr.VERSION_ATTRIBUTE);
317 IPackageImport pi = imports.get(pkg);
318
319 if (pi != null) {
320 VersionRange range = pi.getVersions();
321 String version = range.toString();
322
323 if (!version.equals(currentVersion) && !range.equals(VersionRange.ANY_VERSION)) {
324 attrs.put(BldAttr.VERSION_ATTRIBUTE, version);
325 if (pi.isOptional())
326 attrs.put(BldAttr.RESOLUTION_ATTRIBUTE, BldAttr.RESOLUTION_OPTIONAL);
327 modified
328 .add(pkg + ";version=" + version + (pi.isOptional() ? ";optional" : ""));
329 } else if ((currentVersion == null) && !systemPkgs.contains(pkg)) {
330 unversioned.add(pkg);
331 }
332 } else {
333 // bnd added the import ...
334 if (currentVersion == null) {
335 String defaultVersion = project.getDefaultPackageVersion(pkg);
336 if (defaultVersion != null) {
337 attrs.put(BldAttr.VERSION_ATTRIBUTE, defaultVersion);
338 currentVersion = defaultVersion;
339 }
340 }
341
342 String imp = pkg + (currentVersion == null ? "" : ";version=" + currentVersion);
343 if (bndExports.contains(pkg)) {
344 self.add(imp);
345 } else {
346 if (pkg.equals(".")) {
347 warnings.add("Bnd wants to import '.' (ignored)");
348 importDot = true;
349 } else {
350 missing.add(imp);
351 }
352 }
353 }
354 }
355
356 if (!modified.isEmpty() || importDot) {
357 if (importDot)
358 bndImports.remove(".");
359 // warnings.add("INFO: sigil modified imports: " + modified);
360 main.putValue(Constants.IMPORT_PACKAGE, Processor.printClauses(bndImports,
361 "resolution:"));
362 }
363
364 if (!self.isEmpty()) {
365 // warnings.add("INFO: added self imports: " + self);
366 }
367
368 if (!missing.isEmpty()) {
369 warnings.add("missing imports (added): " + missing);
370 }
371
372 if (!unversioned.isEmpty()) {
373 warnings.add("unversioned imports: " + unversioned);
374 }
375
376 if (bundle.getId().equals(lastBundle)) {
377 if (!unused.isEmpty()) {
378 warnings.add("unused imports (omitted): " + unused);
379 }
380 }
381 }
382
383 public Properties getBndSpec(IBldBundle bundle, String dest) throws IOException {
384 Properties spec = new Properties();
385
386 String junkHeaders = Constants.INCLUDE_RESOURCE; // shows local build
387 // paths; can be
388 // verbose
389 junkHeaders += "," + Constants.PRIVATE_PACKAGE; // less useful, as we
390 // use it for exported
391 // content too.
392
393 spec.setProperty(Constants.REMOVE_HEADERS, junkHeaders);
394 spec.setProperty(Constants.NOEXTRAHEADERS, "true"); // Created-By,
395 // Bnd-LastModified
396 // and Tool
397 spec.setProperty(Constants.CREATED_BY, "sigil.codecauldron.org");
398
399 Properties headers = bundle.getHeaders();
400 // XXX: catch attempts to set headers that conflict with Bnd
401 // instructions we generate?
402 spec.putAll(headers);
403
404 spec.setProperty(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
405 spec.setProperty(Constants.BUNDLE_VERSION, bundle.getVersion());
406
407 String activator = bundle.getActivator();
408 if (activator != null)
409 spec.setProperty(Constants.BUNDLE_ACTIVATOR, activator);
410
411 addRequirements(bundle, spec);
412
413 List<String> exports = addExports(bundle, spec);
414
415 String composites = addResources(bundle, spec);
416
417 ArrayList<String> contents = new ArrayList<String>();
418 contents.addAll(bundle.getContents());
419
420 if (contents.isEmpty()) {
421 if (!project.getSourcePkgs().isEmpty()) {
422 contents.addAll(project.getSourcePkgs());
423 } else {
424 contents.addAll(exports);
425 }
426 }
427
428 if (composites.length() > 0) {
429 if (spec.containsKey(Constants.BUNDLE_ACTIVATOR))
430 warnings.add("-activator ignored when -composites specified.");
431 spec.setProperty(Constants.BUNDLE_ACTIVATOR, COMPONENT_ACTIVATOR);
432 spec.setProperty(COMPONENT_FLAG, "true");
433 spec.setProperty(COMPONENT_LIST, composites);
434 // add activator pkg directly, to avoid needing to add jar to
435 // Bundle-ClassPath.
436 // split-package directive needed to stop Bnd whinging when using
437 // other bundles containing the component-activator.
438 contents.add(COMPONENT_ACTIVATOR_PKG + ";-split-package:=merge-first");
439 }
440
441 List<String> srcPkgs = addLibs(bundle, dest, spec);
442
443 contents.addAll(srcPkgs);
444 addContents(contents, spec);
445
446 IRequiredBundle fh = bundle.getFragmentHost();
447 if (fh != null) {
448 StringBuilder sb = new StringBuilder();
449 sb.append(fh.getSymbolicName());
450 addVersions(fh.getVersions(), sb);
451 spec.setProperty(Constants.FRAGMENT_HOST, sb.toString());
452 }
453
454 return spec;
455 }
456
457 private void addContents(List<String> contents, Properties spec) {
458 // add contents
459 StringBuilder sb = new StringBuilder();
460 for (String pkg : contents) {
461 if (sb.length() > 0)
462 sb.append(",");
463 sb.append(pkg);
464 }
465
466 if (sb.length() > 0)
467 spec.setProperty(Constants.PRIVATE_PACKAGE, sb.toString());
468 }
469
470 private void appendProperty(String key, String value, Properties p) {
471 String list = p.getProperty(key);
472
473 if (list == null) {
474 list = value;
475 } else {
476 list = list + "," + value;
477 }
478
479 p.setProperty(key, list);
480 }
481
482 private List<String> addLibs(IBldBundle bundle, String dest, Properties spec)
483 throws IOException {
484 // final String cleanVersion =
485 // Builder.cleanupVersion(bundle.getVersion());
486 final String name = bundle.getSymbolicName();
487
488 ArrayList<String> srcPkgs = new ArrayList<String>();
489 Map<String, Map<String, String>> libs = bundle.getLibs();
490
491 if (!bundle.getDownloadContents().isEmpty()) {
492 // implicitly add dljar
493 File fdest = new File(dest);
494 String dlname = fdest.getName().replaceFirst("\\.jar$", "-dl.jar");
495
496 HashMap<String, String> attr = new HashMap<String, String>();
497 attr.put(BldAttr.KIND_ATTRIBUTE, "codebase");
498 attr.put(BldAttr.PUBLISH_ATTRIBUTE, dlname);
499
500 HashMap<String, Map<String, String>> lib2 = new HashMap<String, Map<String, String>>();
501 lib2.putAll(libs);
502 lib2.put(dlname, attr);
503 libs = lib2;
504 }
505
506 StringBuilder items = new StringBuilder();
507
508 for (String jarpath : libs.keySet()) {
509 Map<String, String> attr = libs.get(jarpath);
510 String kind = attr.get(BldAttr.KIND_ATTRIBUTE);
511 String publish = attr.get(BldAttr.PUBLISH_ATTRIBUTE);
512
513 // first find the lib ..
514 String path = attr.get(BldAttr.PATH_ATTRIBUTE);
515 if (path == null)
516 path = jarpath;
517
518 File fsPath = bundle.resolve(path);
519
520 if (!fsPath.exists()) {
521 // try destDir
522 File destDir = new File(dest).getParentFile();
523 File file = new File(destDir, fsPath.getName());
524
525 if (!file.exists()) {
526 // try searching classpath
527 file = findInClasspathDir(fsPath.getName());
528 }
529
530 if (file != null && file.exists())
531 fsPath = file;
532 }
533
534 if (!fsPath.exists()) {
535 // XXX: find external bundle using name and version range?
536 // For now just let BND fail when it can't find resource.
537 }
538
539 appendProperty(Constants.INCLUDE_RESOURCE, jarpath + "=" + fsPath, spec);
540
541 if ("classpath".equals(kind)) {
542 String bcp = spec.getProperty(Constants.BUNDLE_CLASSPATH);
543 if (bcp == null || bcp.length() == 0)
544 spec.setProperty(Constants.BUNDLE_CLASSPATH, ".");
545 appendProperty(Constants.BUNDLE_CLASSPATH, jarpath, spec);
546 }
547
548 if (publish != null) {
549 String pubtype = attr.get(BldAttr.PUBTYPE_ATTRIBUTE);
550 if (pubtype == null)
551 pubtype = defaultPubtype;
552
553 if ("codebase".equals(kind)) {
554 String codebase = format(codebaseFormat, publish, name, pubtype);
555 String zone = attr.get(BldAttr.ZONE_ATTRIBUTE);
556 if (zone != null)
557 codebase += "&zone=" + zone;
558 appendProperty("RMI-Codebase", codebase, spec);
559 }
560
561 // add item to publish xml
562 items.append(format("<item name=\"%s\" path=\"%s\">\n", publish, jarpath));
563 items.append(format("<attribute name=\"type\" value=\"%s\"/>\n", pubtype));
564 items.append("</item>\n");
565 }
566 }
567
568 if (items.length() > 0) {
569 File publishFile = new File(dest.replaceFirst("\\.jar$", "-publish.xml"));
570 publishFile.deleteOnExit();
571 PrintWriter writer = new PrintWriter(new FileWriter(publishFile));
572
573 writer.println("<publish>");
574 writer.println(format("<attribute name=\"bundle.symbolic.name\" value=\"%s\"/>", name));
575 writer.print(items.toString());
576 writer.println("</publish>");
577 writer.close();
578
579 appendProperty(Constants.INCLUDE_RESOURCE, "publish.xml=" + publishFile, spec);
580 }
581
582 return srcPkgs;
583 }
584
585 private String addResources(IBldBundle bundle, Properties spec) {
586 Map<String, String> resources = bundle.getResources();
587 StringBuilder composites = new StringBuilder();
588
589 for (String composite : bundle.getComposites()) {
590 File path = bundle.resolve(composite);
591 String name = path.getName();
592
593 String bPath = COMPONENT_DIR + "/" + name;
594 resources.put(bPath, path.getPath());
595
596 if (composites.length() > 0)
597 composites.append(",");
598 composites.append(bPath);
599 }
600
601 StringBuilder sb = new StringBuilder();
602
603 for (String bPath : resources.keySet()) {
604 if (bPath.startsWith("@")) { // Bnd in-line jar
605 if (sb.length() > 0)
606 sb.append(",");
607 sb.append('@');
608 sb.append(bundle.resolve(bPath.substring(1)));
609 continue;
610 }
611
612 String fsPath = resources.get(bPath);
613 if ("".equals(fsPath))
614 fsPath = bPath;
615
616 File resolved = bundle.resolve(fsPath);
617
618 // fsPath may contain Bnd variable, making path appear to not exist
619
620 if (!resolved.exists()) {
621 // Bnd already looks for classpath jars
622 File found = findInClasspathDir(fsPath);
623 if (found != null) {
624 fsPath = found.getPath();
625 } else {
626 fsPath = resolved.getAbsolutePath();
627 }
628 } else {
629 fsPath = resolved.getAbsolutePath();
630 }
631
632 if (sb.length() > 0)
633 sb.append(",");
634 sb.append(bPath);
635 sb.append('=');
636 sb.append(fsPath);
637 }
638
639 if (sb.length() > 0)
640 spec.setProperty(Constants.INCLUDE_RESOURCE, sb.toString());
641
642 return composites.toString();
643 }
644
645 private List<IPackageImport> getImports(IBldBundle bundle) {
646 List<IPackageImport> imports = bundle.getImports();
647 Set<String> pkgs = new HashSet<String>();
648
649 for (IPackageImport pi : imports) {
650 pkgs.add(pi.getPackageName());
651 }
652
653 // add component activator imports
654 if (!bundle.getComposites().isEmpty()) {
655 for (String pkg : BundleBuilder.COMPONENT_ACTIVATOR_DEPS) {
656 if (pkgs.contains(pkg))
657 continue;
658 PackageImport pi = new PackageImport();
659 pi.setPackageName(pkg);
660 String versions = project.getDefaultPackageVersion(pkg);
661 if (versions != null)
662 pi.setVersions(VersionRange.parseVersionRange(versions));
663 imports.add(pi);
664 }
665 }
666
667 return imports;
668 }
669
670 private void addRequirements(IBldBundle bundle, Properties spec) {
671 StringBuilder sb = new StringBuilder();
672
673 // option;addMissingImports=true
674 // Lets Bnd calculate imports (i.e. specify *),
675 // which are then examined by augmentImports();
676
677 // option;omitUnusedImports=true (implies addMissingImports=true)
678 // When project contains multiple bundles which don't all use all
679 // imports,
680 // avoids warnings like:
681 // "Importing packages that are never referred to by any class on the Bundle-ClassPath"
682
683 if (omitUnusedImports && !addMissingImports) {
684 warnings.add("omitUnusedImports ignored as addMissingImports=false.");
685 omitUnusedImports = false;
686 }
687
688 List<IPackageImport> imports = getImports(bundle);
689
690 sb.setLength(0);
691
692 // allow existing header;Package-Import to specify ignored packages
693 sb.append(spec.getProperty(Constants.IMPORT_PACKAGE, ""));
694
695 for (IPackageImport pi : imports) {
696 switch (pi.getOSGiImport()) {
697 case AUTO:
698 if (omitUnusedImports)
699 continue; // added by Import-Package: * and fixed by
700 // augmentImports()
701 break;
702 case NEVER:
703 if (pi.isDependency())
704 continue; // resolve=compile
705 break;
706 case ALWAYS:
707 // Bnd will probably whinge that this import is not used.
708 // we omit it here and replace it in augmentImports,
709 // but only if addMissingImports is true;
710 // otherwise, if the import is used, Bnd will fail.
711 if (addMissingImports)
712 continue;
713 break;
714 }
715
716 if (sb.length() > 0)
717 sb.append(",");
718
719 if (pi.getOSGiImport().equals(IPackageImport.OSGiImport.NEVER)) {
720 sb.append("!");
721 sb.append(pi.getPackageName());
722 } else {
723 sb.append(pi.getPackageName());
724 addVersions(pi.getVersions(), sb);
725
726 if (pi.isOptional()) {
727 sb.append(";resolution:=optional");
728 }
729 }
730 }
731
732 if (addMissingImports) {
733 if (sb.length() > 0)
734 sb.append(",");
735 sb.append("*");
736 }
737
738 spec.setProperty(Constants.IMPORT_PACKAGE, sb.toString());
739
740 sb.setLength(0);
741 for (IRequiredBundle rb : bundle.getRequires()) {
742 if (sb.length() > 0)
743 sb.append(",");
744 sb.append(rb.getSymbolicName());
745 addVersions(rb.getVersions(), sb);
746 }
747
748 if (sb.length() > 0) {
749 spec.setProperty(Constants.REQUIRE_BUNDLE, sb.toString());
750 }
751 }
752
753 private List<String> addExports(IBldBundle bundle, Properties spec) {
754 List<IPackageExport> exports = bundle.getExports();
755 ArrayList<String> list = new ArrayList<String>();
756 StringBuilder sb = new StringBuilder();
757
758 for (IPackageExport export : exports) {
759 if (sb.length() > 0)
760 sb.append(",");
761 sb.append(export.getPackageName());
762 if (!export.getVersion().equals(Version.emptyVersion)) {
763 sb.append(";version=\"");
764 sb.append(export.getVersion());
765 sb.append("\"");
766 }
767 list.add(export.getPackageName());
768 }
769
770 if (sb.length() > 0) {
771 // EXPORT_CONTENTS just sets the Export-Package manifest header;
772 // it doesn't add contents like EXPORT_PACKAGE does.
773 spec.setProperty(Constants.EXPORT_CONTENTS, sb.toString());
774 }
775
776 return list;
777 }
778
779 private void addVersions(VersionRange range, StringBuilder sb) {
780 if (!range.equals(VersionRange.ANY_VERSION)) {
781 sb.append(";version=\"");
782 sb.append(range);
783 sb.append("\"");
784 }
785 }
786
787 private File findInClasspathDir(String file) {
788 for (File cp : classpath) {
789 if (cp.isDirectory()) {
790 File path = new File(cp, file);
791 if (path.exists()) {
792 return path;
793 }
794 }
795 }
796
797 return null;
798 }
799
800}