Confusing behaviour as Bnd runs tests against empty bundles

I suspect this is less a bug in Bnd than a bug in my understanding of Bnd, but I couldn’t find it documented anywhere. Now that I understand it better, I thought I’d write it down.

I’ve been trying to set up a Bnd workspace to build in CI. It makes sense to me to structure the pipeline this way:

  • First stage: one step that builds all the bundles
  • Later stages: many parallel steps that run tests and do other things with bndrun files

I come from a Gradle world, where you don’t need to cache many files between pipeline jobs because it’s often just as cheap for Gradle to recreate them on demand or fetch them from the remote Gradle cache. I’m also using Gradle to drive Bnd.

I noticed that Bnd doesn’t handle build dependencies as cleverly as Gradle. If Bnd sees bnd.identity;id='foo' in a bndrun file, it won’t add :foo:jar to the Gradle task graph to make sure that bundle exists.

However, TestOSGi tasks do depend on the tasks that build the relevant test bundle and everything on its buildpath. In my rather badly structured workspace, this can add up to a lot of task dependencies. I observed that most of them would go away if the bundles were in a repository. Great! I could define a release repository, run release in the first stage of the pipeline, cache cnf/release between jobs, and not need to cache anything from */target!

But it didn’t work. My test bndruns would resolve OK, but the tests would hang until killed.

With -runtrace set to true, I noticed that the container was loading the workspace bundles from e.g. foo/target/foo.jar instead of cnf/release/foo/foo-1.0.jar. But it wasn’t running :foo:jar, so where did that file come from?

It turns out that although the resolver is happy to use bundles from any repository, the tester always gets workspace bundles from the implicit workspace repository. The workspace repository expects all the bundles in the workspace to exist at all times. If a bundle doesn’t exist, Bnd silently creates it from the contents of its classes directory (target/gradle/classes in my setup). If the classes directory doesn’t exist, Bnd creates the bundle anyway, without any classes.

I suppose this is a feature in the context of an IDE, but it’s clearly not designed for pipelines where intermediate files can vanish in between stages of the build.

Some of my bundles have Bundle-Activator headers. If the activator class isn’t present, Bnd creates a Bundle-Import for its package. This import can’t be resolved at runtime, so the tests hang. (I do have -testunresolved: true in my bndrun, so I don’t understand why I don’t get a quick failure. Maybe it’s just too broken.)

I got my tests working by caching */target from the first pipeline step instead of cnf/release. This means I don’t need the release repository any more, so I have removed it.

It’s worth noting that I’ve sometimes seen Bnd build empty bundles in other contexts, both in Gradle (i.e. jar tasks) and Eclipse. I suspect this is because my project started life as an old-fashioned set of Eclipse plugins and has some resource files mixed in with the Java sources. At some point Bnd copies the resources into the classes directory and accidentally overwrites the compiler output. I guess I need to reorganise my source trees.