Bnd - Bundle Tool
Read this very interesting introduction into bnd by Costin Leau, Creating OSGi bundles
Introduction
The bnd tool helps you create and diagnose OSGi R4 bundles. The key functions are:
- Show the manifest and JAR contents of a bundle
- Wrap a JAR so that it becomes a bundle
- Create a Bundle from a specification and a class path
- Verify the validity of the manifest entries
The tool is capable of acting as:
Table Of Contents
Background
Traditionally, JAR files are made with the SUN jar tool, the jar ant task, or the Maven packager. All these tools share the same concept. The developer creates a directory image of the jar by copying files to a directory; this directory is then jarred. Obviously this method works well.
The Bnd tool works different, it recognizes Java classes and packages and constructs the JAR from the classpath. It can create a JAR from packages the sources, directories or other JAR files. You never have to copy files around, the instructions that Bnd receives are sufficient to retrieve the files from their original location, preprocessing or filtering when required.
The Jar is constructed from 3 different arguments:
Export-Package Private-Package Include-Resource
Private-Package and Export-Package contain instructions. Instructions are patterns + attributes and directives, looking like normal OSGi attributes and directives. For example:
Export-Package: com.acme.*;version=1.2
Each instruction is applied to each package on the classpath in the definition order. That is, if an earlier instruction matches, the later instruction never gets a chance to do its work. If an instruction matches its attributes and properties are applied to the packages. The difference between the Private-Package argument and the Export-Package arguments is that the export version selects the packages for export. If the packages overlap between the two, the export wins.
An instruction can also be negative when it starts with a '!'. In that case the package is excluded from the selection. For example:
Export-Package: !com.acme.impl, com.acme.*;version=1.2
Note that the instructions are applied in order. If the ! instruction was at the end in the previous example, it would not have done its work because the com.acme.* would already have matched.
The Include-Resource argument can be used to copy resources from the file system in the JAR. This is useful for licenses, images, etc. The instructions in the argument can be a directory, a file, or an inline JAR. The default JAR path is the the root for a directory or the filename for a file. The path can be overridden. Instructions that are enclosed in curly braces, like {license.txt}, are pre-processed, expanding any macros in the file.
Once the JAR is created, the bnd program analyzes the classes and creates an import list with all the packages that are not contained in the jar but which are referred to. This import list is matched against the Import-Package instructions. Normally, the Import-Package argument is *; all referred packages will be imported. However, sometimes it is necessary to ignore an import or provide attributes on the import statement. For example, make the import optional or discard the import:
Import-Package: !com.acme.*, *;resolution:=optional
The arguments to bnd are normal given as a set of properties. Properties that begin with an upper case are copied to the manifest (possibly after processing). Lower case properties are used for macro variables but are not set as headers in the manifest.
After the JAR is created, the bnd program will verify the result. This will check the resulting manifest in painstaking detail.
The bnd program works on a higher level then traditional jarring; this might take some getting used to. However, it is much more elegant to think in packages than that it is to think in files. The fact that Bnd understand the semantics of a bundle allows it to detect many errors and allows bundles to be created with almost no special information.
Bnd will not create an output file if none of the resources is newer than an existing output file.
The program is available in several forms: command line, ant task, maven plugin, and an Eclipse plugin.
Quick Start
Assume we need to create a bundle in Eclipse. Each Java project in Eclipse has a set of sources and a class path. Bnd therefore knows, about all the classes. However, it does not know how you want to structure your JARs/Bundles. It therefore needs a description file, the bnd file. If you create such a file, you should give it the same as name as the bundle symbolic name with a .bnd extension. For example aQute.example.bnd is a well chosen name. If the name is not bnd.bnd, the file name without the .bnd extension is the default for your bundle symbolic name.
Lets build a bundle for the aQute OSGi tutorial Chat example. This bundle has 2 packages.
- aQute.service.channel
- aQute.tutorial.chat
The aQute.service.channel package must be exported and the other package may remain private. All packages that are referred from the source code must be imported. To achieve this, the following manifest will suffice:
Export-Package: aQute.service.channel; version=1.0 Private-Package: aQute.tutorial.chat
This is all you need! In Eclipse, you can select the bnd file and run the Make Bundle command. This will create a JAR with the proper content:
META-INF MANIFEST.MF aQute/service/channel Channel.class aQute/tutorial/chat Chat$ChannelTracker.class Chat.class
You can run the same command from the command line
bnd aQute.tutorial.chat.bnd
Now take a look at the JAR file's manifest. With the command line version of you can do this with bnd aQute.tutorial.chat.jar. Otherwise just open the JAR with WinZip.
Manifest-Version: 1 Bundle-Name: aQute.tutorial.chat Private-Package: aQute.tutorial.chat Import-Package: aQute.service.channel;version=1.0, org.osgi.framework; version=1.3, org.osgi.util.tracker;version=1.3 Bundle-ManifestVersion: 2 Bundle-SymbolicName: aQute.tutorial.chat Export-Package: aQute.service.channel;version=1.0 Bundle-Version: 0
As you can see, bnd filled in a number of headers. The first header, Manifest-Version is required by the JAR standard. Bundle-Name is derived from the Bundle-SymbolicName because we did not specify it. The Private-Package header specifies the packages that ended up not exported. The Import-Package header comes from the packages that were referred to from the contained packages (private and exported). As you can see, bnd picked up versions for the imported packages. These versions come from the manifest of the JARs that source these packages, or from the packageinfo file. The Export-Package shows the export of the service package. On top of this, bnd has verified that all your headers really match the OSGi specifications, or you will get errors and warnings.
Bnd file format
The bnd format is very similar to the manifest. Though it is read with the Properties class, you can actually use the ':' as separator to make it look more like a manifest file. The only thing you should be aware of is that the line continuation method of the Manifest (a space as the first character on the line) is not supported. Line continuations are indicated with the backslash ('\' \u005C) as the last character of the line. Lines may have any length. You can add comments with a # on the first character of the line:
# This is a comment
White spaces around the key and value are trimmed. See Properties for more information about the format.
There are different instructions in the properties file:
| Type | Example | Description |
|---|---|---|
| Manifest headers | Bundle-Description: ... | When the first character is a upper case character. These headers are copied to the manifest or augmented by bnd. |
| Variables | version=3.0 | Variables are lower case headers. Headers can contain references to other headers using macro expansion. Variables are not copied to the manifest. See Macros |
| Directives | -include: deflts.bnd | Directives start with a '-' sign. A directive is an instruction to bnd to do something special. See Directives |
Bnd Directives
| Instruction | Format | Description |
|---|---|---|
-classpath | LIST | Add the listed files to the current class path. The files must be addressed relative to the properties file. The files must be either a JAR file or a directory. For example:-classpath= acme.jar, junit.jar, bin |
-debug | true|false | Generate debugging information. This will save embedded jar files in the target directory when they are generated on the fly. |
-donotcopy | REGEX | During copying of files from the classpath, file system, or other places, this filter is used to prevent copies. For example, normally CVS and .svn directories should not be copied. The default is therefore (CVS|.svn). Example: -donotcopy= (CVS|.svn|.+.bak|~.+) |
| -include | LIST | This property will include the list of files in the given order. The files are relative from the bnd file itself. If this directive is used inside an included properties file, then the including file is the base. Includes are very useful to keep headers like Bundle-Vendor, Bundle-Copyright central. If the extension of the file is .mf, then the file is parsed as a manifest file. By default, a property defined in an include file override earlier definitions, this implies that any property in the bnd file is overridden if defined in an include file. The include files are read in the order they are listed where later files override earlier files. If there are multiple definitions for the same property, then the last definitions wins. If the path of an included file starts with a ~, then it will not override earlier set properties. You can use properties like ${user.home} in file names. If the file does not exist, an error is generated. If the filename is prefixed with a '-' sign then no error is generated when the file is absent. For example: -include= ~${user.home}/deflts.bnd, META-INF/MANIFEST.MF-include= a.props, ~META-INF/MANIFEST.MF |
-nope | true|false | Do not build a bundle |
-exportcontents | LIST of PATTERN | The content of this header augments the Export-Package header, but only for the manifest calculation. That is, Export-Package is used to calculate the contents of the JAR, but then this instruction can be used change or add instructions for the manifest generation. |
| -failok | true | false | In certain cases, errors should not abort the creation of the bundle. For example test cases often require the creation of an invalid JAR. If this flag is set to true, errors will create a target bundle (when possible) and errors are only listed. When failok is false, the default, any error will not create a target bundle and will delete the bundle file. Example: -failok= true |
-manifest | FILE | Overrides the generation of a manifest and uses the given file instead. |
-nomanifest | false|true | Generate a JAR without a manifest |
-output | PATH | Store the file (if applicable) under the path |
-removeheaders | LIST of string | Removes the given headers from the output manifest. This feature can be useful if you are wrapping a bundle and it contains for example a Require-Bundle header. |
-nouses | true|false | Do not calculate the uses: directive. |
-plugin | LIST of PLUGIN | Define the plugins that bnd should use. A plugin is a class that is used at certain phases in the bundle generation. That place is defined by the interfaces it implements. Plugins are defined in plugins. |
-sources | true|false | Include sources |
Export-Package | LIST of PATTERN | The Export-Package header lists the packages that the bundle should export, and thus contain. See ExportPackage. |
Include-Resource | LIST of iclause | The Include-Resource instruction makes it possible to include arbitrary resources; it contains a list of resource paths. See Include Resource. |
Private-Package | LIST of PATTERN | The Private-Package header lists the packages that the bundle should contain but not export. See Private Package. |
Import-Package | LIST of PATTERN | The Import-Package header lists the packages that are required by the contained packages. See Import Package. |
Conditional-Package | LIST of PATTERN | experimental Works as private package but will only include the packages when they are imported. When this header is used, bnd will recursively add packages that match the patterns until there are no more additions. |
Bundle- | The Bundle-SymbolicName header can be set by the user. The default is the name of the main bnd file, or if the main bnd file is called bnd.bnd, it will be the name of the directory of the bnd file. An interesting variable is ${project} that will be set to this default name. | |
Bundle-Name | If the Bundle-Name is not set, it will default to the Bundle-SymbolicName. | |
Bundle- | 2 | The Bundle-ManifestVersion is always set to 2, there is no way to override this. |
Bundle-Version | VERSION | The version of the bundle. If no such header is provided, a version of 0 will be set. |
Service-Component | LIST of component | See Service Component Header. |
Basic Types
Export-Package
The bnd definition allows the specification to be done using patterns, a modified regular expression. All patterns in the definition are matched against every package on the class path. If the pattern is a negating pattern (starts with !) and it is matched, then the package is completely excluded. Normal patterns cause the package to be included in the resulting bundle. Patterns can include both directives and attributes, these items will be copied to the output. The list is ordered, earlier patterns take effect before later patterns. The following examples copies everything on the class path except for packages starting with com. The default for Export-Package is "*", which can result in quite large bundles. If the source packages have an associated version (from their manifest of packageinfo file), then this version is automatically added to the clauses.
Export-Package= !com.*, *
Exports are automatically imported. This features can be disabled with a special directive on the export instruction: -noimport:=true. For example:
Export-Package= com.acme.impl.*;-noimport:=true, *
Bnd will automatically calculate the uses: directive. This directive is used by the OSGi framework to create a consistent class space for a bundle. The Export-Package statement allows this directive to be overridden on a package basis by specifying the directive in an Export-Package instruction.
Export-package = com.acme.impl.*;uses="my.special.import"
However, in certain cases it is necessary to augment the uses clause. It is therefore possible to use the special name <<USES>> in the clause. Bnd will replace this special name with the calculated uses set. Bnd will remove any extraneous commas when the <<USES>> is empty.
Export-package = com.acme.impl.*;uses="my.special.import,<<USES>>"
Split packages
Bnd traverse the packages on the classpath and copies them to the output based on the instructions given by the Export-Package and Private-Package headers. This opens up for the possibility that there are multiple packages with the same name on the class path. It is better to avoid this situation because it means there is no cohesive definition of the package and it is just, eh, messy. However, there are valid cases that packages should be merged from different sources. For example, when a standard package needs to be merged with implementation code like the osgi packages sometimes (unfortunately) do. Without any extra instructions, bnd will merge multiple packages where the last one wins if the packages contain duplicate resources, but it will give a warning to notify the unwanted case of split packages.
The -split-package: directive on the Export-Package/Private-Package clause allows fine grained control over what should be done with split packages. The following values are architected:
merge-first | Merge split packages but do not add resources that come later in the classpath. That is, the first resource wins. This is the default, although the default will generate a warning |
merge-last | Merge split packages but overwrite resources that come earlier in the classpath. That is, the last resource wins. |
first | Do not merge, only use the first package found |
error | Generate an error when a split package is detected |
For example:
Private-Package: test.pack;-split-package:=merge-first
Private Package
The method of inclusion is identical to the Export-Package header, the only difference is, is that these packages are not exported. This header will be copied to the manifest. If a package is selected by noth the export and private package headers, then the export takes precedence.
Private-Package= com.*
Import Package
The Import-Package header lists the packages that are required by the contained packages. The default for this header is "*", resulting in importing all referred packages. This header therefore rarely has to be specified. However, in certain cases there is an unwanted import. The import is caused by code that the author knows can never be reached. This import can be removed by using a negating pattern. A pattern is inserted in the import as an extra import when it contains no wildcards and there is no referral to that package. This can be used to add an import statement for a package that is not referred to by your code but is still needed, for example, because the class is loaded by name.
For example:
Import-Package: !org.apache.commons.log4j, com.acme.*,
com.foo.extra
During processing, bnd will attempt to find the exported version of imported packages. If no version or version range is specified on the import instruction, the exported version will then be used though the micro part and the qualifier are dropped. That is, when the exporter is 1.2.3.build123, then the import version will be 1.2. If a specific version (range) is specified, this will override any found version. This default an be overridden with the -versionpolicy command.
If an explicit version is given, then ${@} can be used to substitute the found version in a range. In those cases, the version macro can be very useful to calculate ranges or drop specific parts of the version. For example:
Import-Package: org.osgi.framework;version="[1.3,2.0)"
Import-Package: org.osgi.framework;version=${@}
Import-Package: org.osgi.framework;version="[${version;==;${@}},${version;=+;${@}})"
If an imported package uses mandatory attributes, then bnd will attempt to add those attributes to the import statement. However, in certain (bizarre!) cases this is not wanted. It is therefore possible to remove an attribute from the import clause. This is done with the -remove-attribute: directive or by setting the value of an attribute to !. The parameter of the -remove-attribute directive is an instruction and can use the standard options with !, *, ?, etc.
Import-Package: org.eclipse.core.runtime;-remove-attribute:common,*
Or
Import-Package: org.eclipse.core.runtime;common=!,*
Include Resource
The resources will be copied into the target jar file. The iclause can have the following forms:
iclause ::= inline | copy
copy ::= '{' process '}' | process
process ::= assignment | simple
assignment ::= PATH '=' simple
simple ::= PATH parameter*
inline ::= '@' PATH ( '!/' PATH? ('/**' | '/*')? )?
parameters ::= 'flatten' | 'recursive' | 'filter'
In the case of assignment or simple, the PATH parameter can point to a file or directory. It is also possible to use the name.ext path of a JAR file on the classpath, that is, ignoring the directory. The simple form will place the resource in the target JAR with only the file name, therefore without any path components. That is, including src/a/b.c will result in a resource b.c in the root of the target JAR.
If the PATH points to a directory, the directory name itself is not used in the target JAR path. If the resource must be placed in a subdirectory of the target jar, use the assignment form. If the file is not found, bnd will traverse the classpath to see of any entry on the classpath matches the given file name (without the directory) and use that when it matches. The inline requires a ZIP or JAR file, which will be completely expanded in the target JAR (except the manifest), unless followed with a file specification. The file specification can be a specific file in the jar or a directory followed by ** or *. The ** indicates recursively and the * indicates one level. If just a directory name is given, it will mean **.
The simple and assigment forms can be encoded with curly braces, like {foo.txt}. This indicates that the file should be preprocessed (or filtered as it is sometimes called). Preprocessed files can use the same variables and macros as defined in the macro section.
The recursive: directive indicates that directories must be recursively included.
The flatten: directive indicates that if the directories are recursively searched, the output must not create any directories. That is all resources are flattened in the output directory.
The filter: directive is an optional filter on the resources. This uses the same format as the instructions. Only the file name is verified against this instruction.
Include-Resource: @osgi.jar,
{LICENSE.txt},
acme/Merge.class=src/acme/Merge.class
Plugins
Plugins are objects that can extend the functionality of bnd. They are called from inside bnd when a certain action should take place. For example, bnd uses a repository and plugins provide the actual repository implementations. Or for example, the SpringComponent analyzes the Spring files and adds references found in that XML to the imports.
A plugin is defined as:
PLUGIN ::= FQN ( ';' <directive|attribute> )*
The following directive is defined for all plugin:
path: | A path to the jar file that contains the plugin |
bnd current supports the following plugin types:
/**
* An optional interface for plugins. If a plugin implements
* this interface then it can receive the remaining attributes
* and directives given in its clause as
* well as the reporter to use.
*/
public interface Plugin {
/**
* Give the plugin the remaining properties.
* When a plugin is declared, the clause can contain
* extra properties. All the properties and directives
* are given to the plugin to use.
* @param map attributes and directives for this plugin's clause
*/
void setProperties(Map<String,String> map);
/**
* Set the current reporter. This is called at init time.
* This plugin should report all errors and warnings
* to this reporter.
* @param processor
*/
void setReporter(Reporter processor);
}
public interface AnalyzerPlugin {
/**
* This plugin is called after analysis. The plugin
* is free to modify the jar and/or change the classpath
* information (see referred, contained).
* This plugin is called after analysis of the JAR
* but before manifest generation.
*
* @param analyzer
* @return true if the classpace has been modified so that the bundle
* classpath must be reanalyzed
* @throws Exception
*/
boolean analyzeJar(Analyzer analyzer) throws Exception;
}
public interface SignerPlugin {
/**
* Sign the current jar. The alias is the given certificate
* keystore.
*
* @param builder The current builder that contains the
jar to sign
* @param alias The keystore certificate alias
* @throws Exception When anything goes wrong
*/
void sign(Builder builder, String alias) throws Exception;
}
public interface RepositoryPlugin {
/**
* Return a URL to a matching version of the given bundle.
*
* @param bsn
* Bundle-SymbolicName of the searched bundle
* @param range
* Version range for this bundle,"latest"
* if you only want the
* latest, or null when you want all.
* @return A list of URLs sorted on version, lowest version
* is at index 0.
* null is returned when no files with the given
* bsn ould be found.
* @throws Exception
* when anything goes wrong
*/
File[] get(String bsn, String range) throws Exception;
/**
* Answer if this repository can be used to store files.
*
* @return true if writable
*/
boolean canWrite();
/**
* Put a JAR file in the repository.
*
* @param jar
* @throws Exception
*/
File put(Jar jar) throws Exception;
/**
* Return a list of bsns that are present in the repository.
*
* @param regex if not null, match against the bsn and if
* matches, return otherwise skip
* @return A list of bsns that match the regex parameter
* or all if regex is null
*/
List<String> list(String regex);
/**
* Return a list of versions.
*/
List<Version> versions(String bsn);
}
public interface MakePlugin {
/**
* This plugin is called when Include-Resource detects
* a reference to a resource that it can not find in the
* file system.
*
* @param builder The current builder
* @param source The source string (i.e. the place
* where bnd looked)
* @param arguments Any arguments on the clause in
* Include-Resource
* @return A resource or null if no resource
* could be made
* @throws Exception
*/
Resource make(Builder builder, String source,
Map<String,String> arguments) throws Exception;
}
Make
Make plugins kick in when the Include-Resource header tries to locate a resource but it cannot find that resource. The -make option defines a number of patterns that are mapped to a make instruction.
For example, if you have
Include-Resource: com.acme.Abc.ann
If no such resource is found, bnd will look in the -make instruction. This instruction associates a pattern with a plugin type. For example:
-make: (*.jar); type=xyz; abc=3; def="$1"
The first name part of the clause is matched against the unfound resource. All plugins are called sequentially until one returns non-null. The arguments on the -make clause are given as parameters to the make plugin. Normally all Make Plugins should verify the type field.
bnd has a bnd and a copy Make Plugin.
Buildin Plugins
Some plugins are provided by bnd itself.
| aQute.lib.spring.SpringComponent | AnalyzerPlugin | This component will analyze the Spring XML for Spring DM and will add any classes in these files to the referred set of classes. |
| aQute.bnd.make.MakeBnd | MakePlugin | If Include-Resource can't find a referred resource it will use the make plugins to see if any of them can make the requested resource. The MakeBnd has type=bnd. This plugin can recursively call bnd to create embedded jars that are not created sequentially |
| aQute.bnd.make.MakeCopy | MakePlugin | Copies resources from other places in the file system |
| aQute.lib.deployer.FileRepo | RepositoryPlugin | Provides a file based repository. |
| aQute.bnd.maven.MavenRepository | RepositoryPlugin | Provides an interface to a the default maven repository in the user's home directory. Can be used with aQute.bnd.maven.MavenGroup to support converting bundle symbolic names to group and artifact id. |
Service-Component Header
The Service-Component header is compatible with the standard OSGi header syntax. Any element in the list that does not have attributes must have a resource in the JAR and is copied as is to the manifest. However, simple components can also be defined inline, and it is even possible to pickup annotations.
The syntax for these component definitions is:
component ::= <name> ( ';' parameter ) *
parameter ::= provide | reference | multiple | optional
| reference | properties | factory | servicefactory
| immediate | enabled | implementation
| activate | deactivate | modified | configuration-policy
| version
reference ::= <name> '=' <interface-class>
( '(' <target-filter> ')')? cardinality?
cardinality ::= '?' | '*' | '+' | '~'
provide ::= 'provide:=' LIST
multiple ::= 'multiple:=' LIST
optional ::= 'optional:=' LIST
dynamic ::= 'dynamic:=' LIST
factory ::= 'factory:=' true | false
servicefactory := 'servicefactory:=' true | false
immediate ::= 'immediate:=' true | false
enabled ::= 'enabled:=' true | false
configuration-policy ::= "configuration-policy:=' ( 'optional' | 'require' | 'ignore' )
activate ::= 'activate:=' METHOD
modified ::= 'activate:=' METHOD
deactivate::= 'activate:=' METHOD
implementation::= 'implementation:=' <implementation-class>
properties::= 'properties:=' key '=' value \
( ',' key '=' value ) *
If the name of the component maps to a resource, or ends in XML, or there are attributes set, then that clause is copied to the output Service-Component header.
If the name can be expanded to one or more classes that have component annotations (they must be inside the JAR), then each of those classes is analyzed for its component annotations. These annotations are then merged with the attributes from the header, where the header attributes override annotations. The expansion uses the normal wildcard rules. For example, biz.aQute.components.* will search for component annotated classes in the biz.aQute.components package or one of its descendants. The classes must be present in the JAR. If no classes with annotations can be found for the name then it is assumed to be name or implementation class name without annotations.
The name of the component is also the implementation class (unless overridden by the implementation: directive). It is then followed with a number of references and directives. A reference defines a name that can be used with the locateService method from the ComponentContext class. If the name starts with a lower case character, it is assume to be a bean property. In that case the reference is augmented with a set<Name> and unset<Name> method according to the standard bean rules. Bnd will interpret the header, read the annotations if possible, and create the corresponding resources in the output jar under the name OSGI-INF/<id>.xml.
The supported annotations in the aQute.bnd.annotations.component package are:
| Component |
|---|
Annotated the class, indicates this class is required to be a component. It has the following properties:
| provide | Class[] | Service interfaces, the default is all directly implemented interfaces |
| name | String | Name of the component |
| factory | Boolean | Factory component |
| servicefactory | Boolean | Service Factory |
| immediate | Boolean | Immediate activation |
| configurationPolicy | OPTIONAL, REQUIRE, IGNORE | Configuration Policy |
| enabled | Boolean | Enabled component |
| Reference |
|---|
On a method. Indicates this method is the activate method. It has the following attributes
| name | String | Name of the reference. Default this is the name of the method without set on it. |
| service | Class | The service type, default is the argument type of the method. The unset method is derived from this name. I.e. setXX will have an unsetXX method to unset the reference. |
| type | Character | Standard cardinality type '?', '*', '+','~' |
| Activate, Modified, and Deactivate |
|---|
The life cycle methods. These annotations have no properties.
Assume the JAR contains the following class"
package com.acme; import org.osgi.service.event.*; import org.osgi.service.log.*; import aQute.bnd.annotation.component.*; @Component public class AnnotatedComponent implements EventHandler { LogService log; @Reference void setLog(LogService log) { this.log=log; } public void handleEvent(Event event) { log.log(LogService.LOG_INFO, event.getTopic()); } }
The only thing necessary to register the Declarative Service component is to add the following Service-Component header:
Service-Component: com.acme.*
This header will look for annotations in all com.acme sub-packages for an annotated component. The resulting XML will look like:
OSGI-INF/com.acme.AnnotatedComponent.xml: <?xml version='1.0' encoding='utf-8'?> <component name='com.acme.AnnotatedComponent'> <implementation class='com.acme.AnnotatedComponent'/> <service> <provide interface='org.osgi.service.event.EventHandler'/> </service> <reference name='log' interface='org.osgi.service.log.LogService' bind='setLog' unbind='unsetLog'/> </component>
The following example shows a component that is bound to the log service via the setLog method without annotations:
Service-Component=aQute.tutorial.component.World; \
log=org.osgi.service.log.LogService
The Service Component Runtime (SCR) offers a variety of options on the reference. Some options like the target can be used by adding the target filter after the interface name (this likely requires putting quotes around the interface name+filter).
References can be suffixed with the following characters to indicate their cardinality:
Char Cardinality Policy
? 0..1 dynamic
* 0..n dynamic
+ 1..n dynamic
~ 0..1 static
1 static
For a more complex example:
Service-Component=aQute.tutorial.component.World; \
log=org.osgi.service.log.LogService?; \
http=org.osgi.service.http.HttpService; \
PROCESSORS="xierpa.service.processor.Processor(priority>1)+"; \
properties:="wazaabi=true"
The previous example will result in the following service component in the resource OSGI-INF/aQute.tutorial.component.World.xml:
<?xml version="1.0" encoding="utf-8" ?>
<component name="aQute.tutorial.component.World">
<implementation class="aQute.tutorial.component.World" />
<reference name="log"
interface="org.osgi.service.log.LogService"
cardinality="0..1"
bind="setLog"
unbind="unsetLog"
policy="dynamic" />
<reference name="http"
interface="org.osgi.service.http.HttpService"
bind="setHttp"
unbind="unsetHttp" />
<reference name="PROCESSORS"
interface="xierpa.service.processor.Processor"
cardinality="1..n"
policy="dynamic"
target="(priority>1)" />
</component>
The description also supports the immediate, enabled, factory, target, servicefactory, configuration-policy, activate, deactivate, and modified attributes. Refer to the Declarative Services definition for their semantics.
If any feature of the V1.1 namespace is used, then bnd will declare the namespace in the component element. A specific namespace version can be set with the version directive.
Bnd also supports setting the policy and cardinality through the following directives:
multiple:= LIST names of references that have x..n optional:= LIST names of references that have 0..x dynamic:= LIST names of references that are dynamic
Macros
A simple macro processor is added to the header processing. Variables allow a single definition of a value, and the use of derivations. Each header is a macro that can be expanded. Notice that headers that do not start with an upper case character will not be copied to the manifest, so they can be used as working variables. Variables are expanded by enclosing the name of the variable in ${<name>} (curly braces) or $(<name>) (parenthesis). Additionally, square brackets [], angled brackets <>, double guillemets «», and single guillemets ‹› are also allowed for brackets. If brackets are nested, that is replace;acaca;a(.*)a;[$1] will return [cac].
For example:
version=1.23.87.200109111023542
Bundle-Version= ${version}
Bundle-Description= This bundle has version ${version}
There are a number of built in properties that are set by bnd:
| Property name | Description |
|---|---|
project | Name of the project. This is the name of the bnd file without the .bnd extension. If this name is bnd.bnd, then the directory name is used. |
project.file | Absolute path of the main bnd file. |
project.name | Just the name part of the file path |
project.dir | The absolute path of the directory in which the bnd file resides. |
There are also a number of macros that perform basic functions. All these functions have the following basic syntax:
macro ::= '${' function '}'
| function
| '$(' function ')'
| '$<' function '>'
| '$«' function '»'
| '$‹' function '›'
function ::= name ( ':' argument ) *
| Function | Arguments | Description |
|---|---|---|
filter | ; list ; regex | The filter macro iterates over the given list and only includes elements that match the given regular expression (regex). The following example includes only the jar files from the list: list= a,b,c,d,x.jar,z.jar |
filterout | ; list ; regex | The filterout macro iterates over the given list and removes elements that match the given regular expression (regex). The following example strips the jar files from the list: list= a,b,c,d,x.jar,z.jar |
env | ; name | Provide the value of the given environment variable |
sort | ; list | Sort the given list using string sorting collation. For example: List= ${sort:acme.jar, harry.jar, runner.jar, alpha.jar, bugs.jar} |
classes | QUERY | Provides a query function to find classes to fullfil certain criteria. See the classes macro |
join | ( ; list ) * | Joins a number of lists into one. It may seem that this can be easily accomplished by just placing two macro expansions after each other. The result of this will not be a list, unless a ',' (colon) is placed in between. However, when one of the lists is empty, the colon will be superfluous. The join handles these cases correctly. Any number of lists may be given as arguments. List= ${join;a,b,c;d,e,f} |
if | ; condition ; true ( ; false ) ? | If the condition is not empty, the true part is returned, else the false part is returned. If not false part is supplied, the empty string is returned. The condition is trimmed before tested. For example: Comment: ${if;${version};Ok;Version is NOT set!!!!} |
now | Returns the current Date as string. Created-When: ${now} | |
fmodified | ; file-path-list | Return the highest modification time of the given file path. The returned value is based on the epoch of Java, it is therefore a long. Last-Modified: ${long2date;${fmodified;${files}}) |
long2date | ; long | Parse the long and turn it into a date. Last-Modified: ${long2date:${fmodified:${files}}) |
replace | ; list ; regex ; replacement | Replace all elements of the list that match the regular expression regex with the replacement. The replacement can use the 0-9 back references defined in the regular expressions. The macro uses item.replaceAll(regex,replacement) method to do the replacement.For example, to add a .jar extension to all files listed, use the following: List = ${replace;${impls};$;.jar} |
toclassname | ; list | Replace a class path (with slashes and class at the end) to a class name (with dots). |
toclasspath | ; list [ ; suffix ] | Replace a class name (with dots) to a classpath (with slashes and suffix at the end). The default suffix is .class. The suffix may be empty |
findname | ; regex [ ; replacement ] | Find the paths to any resources that matches the regular expression, replace the name with the replacement of the regex. Notice that the regex is only executed on the name of the resource, that is, without the slashes. |
findpath | ; regex [ ; replacement ] | Find the paths to any resources that matches the regular expression, replace the path with the replacement of the regex. Notice that the regex is executed on the path of the resource, that is, with the slashes. |
version | ; mask ; version | This macro can modify a version by dropping parts from the end, incrementing parts, or decrementing parts. The mask is a string containing from 1 to 4 characters. The characters have the following meaning: = the actual version part + increment the actual version part - decrement the actual version part For example, ${version;=+;1.2.3.q} will become 1.3. |
classes macro
The classes macro provides a query function in an analyzed bundle. While analyzing, the Analyzer stores each found class on the Bundle-Classpath with some key information. A simple query language is used to query this dictionary. For example, if you want to make a manifest header with all public classes in the bundle:
Public-Classes: ${classes;PUBLIC}
The query language is conjunctive, that is, all entries form an AND. For example, if you want to find all PUBLIC classes that are also not abstract you would do:
PublicConcrete-Classes: ${classes;CONCRETE}
The query can also parameters. This is a pattern that must match some aspect of the class. For example, it is possible to query for classes that extend a certain base class:
Test-Cases: ${classes;CONCRETE;EXTENDS;junit.framework.TestCase}
All pattern matching is based on fully qualified name and uses the globbing model.
The following table specifies what query options there are:
| Query | Parameter | Description |
|---|---|---|
| IMPLEMENTS | PATTERN | The class must implement at least one interface that matches the given pattern. This takes inheritance into account as long as intermediates can be found the classpath |
| EXTENDS | PATTERN | The class must implement at least one interface that matches the given pattern. This takes inheritance into account as long as intermediates can be found the classpath. |
| IMPORTS | PATTERN | The class must use a type from another package that matches the given pattern |
| NAMED | PATTERN | The class fqn must match the given pattern. |
| ANY | Matches any class | |
| VERSION | PATTERN | The class format of the given class must match the given version. The version is given as "<major>/<minor>", like "49/0". To select classes that are Java 6, do ${classes;VERSION;49/*} |
| CONCRETE | Class must not be abstract | |
| ABSTRACT | Class must be abstract | |
| PUBLIC | Class must be public | |
| ANNOTATION | PATTERN | The class must have an annotation that matches the pattern. The set of annotations is all annotations in the class, also the annotations on fields and methods. |
Caveat
bnd will attempt to use the resources on the classpath if a super class or interface that is referenced from an analyzed class is not in the class space. However, bnd does not require that all dependencies are available on the classpath. In such a case it is not possible to do a complete analysis. For example, if A extends B and B extends C then it can only be determined that A extends C if B can be analyzed.
Strategic
Version Policy
The version policy defines how an import is bound to a matched export when the import is not decorated with a version. Assuming that the exporter has a version, than it would be desirable to take that version, adapt it in a specific way to a range and use that as the import version. This is exactly what the -versionpolicy allows. The value of this option is a string that will be used for the value of the import version.
A hard coded string would not be very valuable, it is therefore possible to use the ${@} macro. This macro will contain the actual export version when the import version is calculated. This actual version can be processed by the ${version} macro to create a truncated or incremented version. This is best elucidated with some examples. In the following examples the export version is 1.2.3.q.
| -versionpolicy | version |
|---|---|
| ${@} | 1.2.3.q |
| ${version;==;${@}} | 1.2 (default) |
| [${version;==;${@}},${version;=+;${@}}) | [1.2,1.3) |
| [${version;==;${@}},${version;+;${@}}) | [1.2,2) |
Command Line
The command line tool can be invoked in several different ways:
- bnd general-options cmd cmd-options
- bnd general-options <file>.jar
- bnd general-options <file>.bnd
General Options
| General Option | Description |
|---|---|
| -failok | Same as the property -failok. The current run will create a JAR file even if there were errors. |
| -exceptions | Will print the exception when the software has ran into a bad exception and bails out. Normally only a message is printed. For debugging or diagnostic reasons, the exception stack trace can be very helpful. |
print ( -verify | -manifest | -list | - all ) * <file>.jar +
The print function will take a list of JAR file and print one or more aspect of the JAF riles. The following aspects can be added.
- -verify - Verify the JAR for consistency with the specification. The print will exit with an error if the verify fails.
- -manifest - Show the manifest
- -list - List the entries in the JAR file
- -all - Do all (this is the default.
bnd print -verify *.jar
build ( -classpath LIST | -eclipse <file> | -noeclipse | -output <file> ) * <file>.bnd +
The build function will assemble a bundle from the bnd specification. The default name of the output bundle is the name of the bnd file with a .jar extension.
- -classpath - A list of JAR files and/or directories that should be placed on the class path before the calculation starts.
- -eclipse - Parse the file as an Eclipse .classpath file, use the information to create an Eclipse's project class path. If this option is used, the default .classpath file is not read.
- -noeclipse - Do not parse the .classpath file of an Eclipse project.
- -output - Override the default output name of the bundle or the directory. If the output is a directory, the name will be derived from the bnd file name.
bnd build -classpath bin -noeclipse -output test.jar xyz.bnd
wrap ( -classpath (<file>(','<file>)*)-output <file|dir> | -properties <file> ) *
-ignoremanifest? <file>.jar *
The wrap command takes an existing JAR file and guesses the manifest headers that will make this JAR useful for an OSGi Service Platform. If the output file is not overridden, the name of the input file is used with a .bar extension. The default bnd file for the header calculation is:
Export-Package: * Import-Package: <packages inside the target jar>
If the target bundle has a manifest, the headers are merged with the properties.
The defaults can be overridden with a specific properties file.
- -output - Set the output file or directory
- -classpath - Sets the classpath as a comma separated list
- -properties - Use a special property file for the manifest calculation.
- -ignoremanifest - Do not include the manifest headers from the target bundle
bnd wrap -classpath osgi.jar *.jar
eclipse
List the Eclipse information in the current directory.
bnd eclipse
Eclipse Plugin
The bnd.jar file is a complete plugin. To install this plugin, place it in the eclipse/plugin directory (or extension directory) of your Eclipse installation and restart (!). The plugin will provides a 'Make Bundle' context menu when you select a file that ends with .bnd. Two menus are shown when you select a JAR file. You can 'Wrap JAR', turning it into a bundle with all imports and exports set (the extension will be .bar), or you can use 'Verify Bundle', and verify the bundle for compliance to the spec. Any errors or warnings are listed in a dialog box.
Additionally, the plugin registers an editor for JAR files. The editor shows the full output of the print command.
Ant Task
The bnd.jar file can also be used as an ANT task. The following example shows how you can use it from an ANT file.
<target name="build">
<taskdef resource="aQute/bnd/ant/taskdef.properties"
classpath="bnd.jar"/>
<bnd
classpath="src"
eclipse="true"
failok="false"
exceptions="true"
files="test.bnd"/>
</target>
You can set the following attributes:
| Attribute | Description |
|---|---|
| classpath | Command separated list of file names. File paths are relative from the ant project file. |
| eclipse | True or false. True if the eclipse .classpath file should be read (default). |
| failok | Succeed even if there are errors. |
| exceptions | If errors occur, show the exception stack trace. |
| files | A comma separated list of bnd files |
| sourcepath | A source path |
| output | Where the output should go |
The following tasks have also been added:
| task name | Class | attributes |
| bndeclipse | EclipseTask | prefix='project.' |
| bndexpand | ExpandPropertiesTask | propertyFile='<file>' |
| bndwrap | WrapTask | jars='<list>', output='<dir>', definitions='<dir>', classpath='<file-list>' |
Maven
The Maven plugin is described at [[http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html |Felix maven plugin]]. Defaults for Maven are:
Bundle-SymbolicName: <groupId>.<artifactId>
Bundle-Name: project.getName();
Bundle-Version: <version>
Import-Package: *
Export-Package: <groupId>.<artifactId>.* (unless Private-package is set)
Bundle-Description: project.getDescription()
Bundle-License: project.getLicenses())
Bundle-Vendor: project.getOrganization();
Include-Resource: src/main/resources
Errors and Warnings
There are many errors and warnings. The messages should be self explanatory.



