Finding Bundle Dependencies

We can use the "bnd" command to see what the bundle imports are.

Introduction

So many times I have answered the question, "How do I find out what import packages my bundle needs?" with the tired and unsatisfactory response that uses the following process:

  1. Build your module.
  2. Deploy your module.
  3. Use Gogo to see why your bundle doesn't start, often from an Unresolved Requirement on an Import Package.
  4. Either include the lib in your jar or use the Import-Package bnd.bnd property to exclude the package.
  5. Go to  step 1, repeat until no further Unresolved Requirements are found.

Yeah, so this is really a pain, but it was the only way I knew of how to see what the imports are that your module needs.

Until Today.

Introducing Bnd

The Bnd tool (available https://bnd.bndtools.org/) is the heart of building an OSGi bundle.  Whether you're using Blade, Gradle, Maven, etc it doesn't matter; under the covers you are likely invoking Bnd to build the module.

Most of us don't know about Bnd only because we're lazy developers. Well okay, maybe not lazy, but we're only going to learn new tools if we have to. But if we can do our jobs without knowing every detail of the process, we're generally fine with it.

It is Bnd which is responsible for applying the directives in your bnd.bnd file and generating the bundle full of the OSGi details necessary for your bundle to deploy and run.

As it turns out, the Bnd tool knows a heck of a lot more about our bundles than we do.

To find this out, though, we need the Bnd tool installed.

Follow the instructions from https://bnd.bndtools.org/chapters/120-install.html on 3.1 to install the command line client.

Bnd Printing

The command line tool actually gives you a full basket of tools to play with, you can find the whole list here: https://bnd.bndtools.org/chapters/860-commands.html

Honestly I have not really played with many of them yet.  I surely need to because there are definitely useful nuggets of gold in them there hills.

For example, the one nugget I've found so far is the print command.

I'm talking specifically about using bnd print --impexp to list imports and exports as described https://bnd.bndtools.org/chapters/390-wrapping.html in section 20.3.

Turns out this command will list the imports and exports Bnd has identified for your module.

I turned this on one of my own modules to see what I would get:

bnd print --impexp build/libs/com.example.hero.rules.engine.simple-1.1.0.jar 
[IMPEXP]
Import-Package
  com.example.data.model                 {version=[1.0,2)}
  com.example.hero.rules.engine          {version=[1.1,2)}
  com.example.hero.rules.model           {version=[1.0,2)}
  com.example.hero.rules.service         {version=[1.0,2)}
  com.liferay.portal.kernel.log          {version=[7.0,8)}
  com.liferay.portal.kernel.util         {version=[7.3,8)}
  com.liferay.portal.kernel.uuid         {version=[6.2,7)}
  javax.xml.datatype                     
  javax.xml.namespace                    
  javax.xml.parsers                      
  org.w3c.dom                            
  org.w3c.dom.bootstrap                  
  org.w3c.dom.ls                         
  org.xml.sax                            

Cool, huh? I can see that my module wants to import stuff that I have defined off in the API module, but I can also see that I'm leveraging portal-kernel as well as XML processing.

POI Portlet

One of the frequent requests is what is necessary to use POI in a portlet. Let's find out together, shall we?

So I created a simple module using Blade and use the following build.gradle file:

dependencies {
  compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
  compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0"
  compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0"
  compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1"
  compileOnly group: "jstl", name: "jstl", version: "1.2"
  compileOnly group: "org.osgi", name: "osgi.cmpn", version: "6.0.0"

  compileInclude group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17'
}

The only thing I added here was the compileInclude directive. As we all know, this will automagically include the declared dependency and some of the transitive dependencies in the bundle. But what many of us have seen, if you deploy this guy you would still get Unresolved Reference messages.

Well, using Bnd we can now see why that is:

bnd print --impexp build/libs/com.example.poi-1.0.0.jar 
[IMPEXP]
Import-Package
  com.liferay.portal.kernel.portlet.bridges.mvc {version=[1.0,2)}
  com.microsoft.schemas.office.powerpoint 
  com.microsoft.schemas.office.word      
  com.sun.javadoc                        
  com.sun.tools.javadoc                  
  javax.crypto                           
  javax.crypto.spec                      
  javax.imageio                          
  javax.imageio.metadata                 
  javax.imageio.stream                   
  javax.portlet                          {version=[2.0,3)}
  javax.security.auth.x500               
  javax.servlet                          {version=[3.0,4)}
  javax.servlet.http                     {version=[3.0,4)}
  javax.swing                            
  javax.xml.bind                         
  javax.xml.bind.annotation              
  javax.xml.bind.annotation.adapters     
  javax.xml.crypto                       
  javax.xml.crypto.dom                   
  javax.xml.crypto.dsig                  
  javax.xml.crypto.dsig.dom              
  javax.xml.crypto.dsig.keyinfo          
  javax.xml.crypto.dsig.spec             
  javax.xml.parsers                      
  javax.xml.transform                    
  javax.xml.transform.dom                
  javax.xml.transform.stream             
  javax.xml.validation                   
  javax.xml.xpath                        
  junit.framework                        
  org.apache.commons.logging             
  org.apache.crimson.jaxp                
  org.apache.jcp.xml.dsig.internal.dom   
  org.apache.poi.hsmf                    
  org.apache.poi.hsmf.datatypes          
  org.apache.poi.hsmf.extractor          
  org.apache.poi.hwpf.extractor          
  org.apache.tools.ant                   
  org.apache.tools.ant.taskdefs          
  org.apache.tools.ant.types             
  org.apache.xml.resolver                
  org.apache.xml.resolver.tools          
  org.apache.xml.security                
  org.apache.xml.security.c14n           
  org.apache.xml.security.signature      
  org.apache.xml.security.utils          
  org.bouncycastle.asn1                  
  org.bouncycastle.asn1.cmp              
  org.bouncycastle.asn1.nist             
  org.bouncycastle.asn1.ocsp             
  org.bouncycastle.asn1.x500             
  org.bouncycastle.asn1.x509             
  org.bouncycastle.cert                  
  org.bouncycastle.cert.jcajce           
  org.bouncycastle.cert.ocsp             
  org.bouncycastle.cms                   
  org.bouncycastle.cms.bc                
  org.bouncycastle.operator              
  org.bouncycastle.operator.bc           
  org.bouncycastle.tsp                   
  org.bouncycastle.util                  
  org.etsi.uri.x01903.v14                
  org.junit                              
  org.junit.internal                     
  org.junit.runner                       
  org.junit.runner.notification          
  org.openxmlformats.schemas.officeDocument.x2006.math 
  org.openxmlformats.schemas.schemaLibrary.x2006.main 
  org.w3c.dom                            
  org.w3c.dom.events                     
  org.w3c.dom.ls                         
  org.xml.sax                            
  org.xml.sax.ext                        
  org.xml.sax.helpers                    
Export-Package
  com.example.poi.constants              {version=1.0.0}

Nuts!

Now we can see just what OSGi is going to want us to deal with. We can add the compileInclude directives for these artifacts if we want to include them, or we could mask them using the ! syntax for the bnd.bnd Import-Package directive, or even mark them as optional by listing them in the Import-Package directive with the resolution:=optional instruction, ala:

Import-Package:\
  ...\
  org.apache.tools.ant.*;resolution:=optional,\
  ...

Conclusion

During my testing, I did find that this is not the perfect solution. Not even the Bnd tool will process transitive dependencies correctly.

For example, we can see from above that BouncyCastle is imported, but what we can't see are any transitive dependencies to BouncyCastle that might be lurking if we decide to include BouncyCastle in. We would have to re-run the bnd print --impexp command again, but that will still be better than the old tired answer I used to have to give.

Enjoy!

Blogs

After 3 years, the problem stated in your conclusion remains, and the only way to describe it is still "Nuts!". For example, I need to have this today to make things work, import and exclude the same package: ​​​​​​​<pre> ​​​​​​​Import-Package:\ ​​​​​​​... org.apache.poi.*;resolution:=optional,\ ​​​​​​​... ​​​​​​​!org.apache.poi.*,\ ​​​​​​​... ​​​​​​​</pre> ​​​​​​I think probably because there is no clear ownership of this problem: Liferay? bnd? osgi? felix? eclipse?