Bnd macro expansion: ${@class} works but ${@version} doesn't

Hi,

I am trying to use a ${@version} macro in a @Requirement annotation inside a Gradle project. I am using Gradle 7.3.2 and Bnd 6.1.0. However, Bnd is refusing to expand ${@version} to the Gradle ${project.version} value. I don’t think this can be a syntax problem because it happily expands ${@class}. Nor can I see anything in the documentation to suggest I am doing anything wrong.

Could this be a bug caused by Bnd’s recent support for Gradle’s configuration cache, please?

Cheers,
Chris

I don’t think the configuration cache support could be involved since the @xxx macro values are set in the code from known data values.

It does require that the package has a specified version. If there is no version value known for the package, the @version value will not be set.

OK, now I’m confused. I thought packages already have versions, which are (by default) the same as the bundle version unless overridden individually in the package-info file using:

@aQute.bnd.annotation.Version

My bundle definitely has a version, even if none of its packages are actually exported. (Not that exporting one of them makes any difference, mind.) However, I am trying to version a “capability” and so am fine with using the bundle version here.

Cheers,
Chris

So the bottom line is that this does what I expect:

@Requirement(
    namespace = MY_NAMESPACE,
    name = MY_NAME,
    version = "${@version}"
)
@Version("${project.version}")
package my.package;

whereas this does not:

@Requirement(
    namespace = MY_NAMESPACE,
    name = MY_NAME,
    version = "${project.version}"
)
package my.package;

Errmm…?

Cheers,
Chris

This only happens at the end of making the bundle for exported packages and is generally bad practice and people should not do that.

This will not work. The string for the version must be a valid OSGi version as a compile time constant. Bnd does not mutate the bytecode generated by the java compiler to replace the compile time constant string ${project.version} with the value of a macro evaluation.

The macro evaluation for the Requirementannotation processing does not replace the compile time constants in the bytecode. It just processes the values into information to generate the manifest.

The version in the Version annotation must be a real OSGi version string.

The string for the version must be a valid OSGi version as a compile time constant.

No, not really. I’m using this

@org.osgi.annotation.versioning.Version("${api_version}")

all over my code (and therefore hope that things don’t change). However, api_version is defined in bnd.bnd and not taken from the gradle script.

Well your generated bundle has invalid package-info.class files which do not contain the actual package version value for your package. So when Bnd see those jars on the build path of another jar, it will process the package-info.class file to locate the version of the package and will use the being-built bundle’s bnd property values to evaluate the version value.

So the value of api_version in the bundle being built will be used rather than the correct value from the previously built bundle.

It is a bad idea to use anything other than a real OSGi version string in the Version annotation. You want the actual OSGi version baked into the generated class file. Not some value which is interpreted later and can easily be wrong. The semantic version of a package is intrinsic to the source code of the package and thus is itself source code.

I’m not sure that I have fully understood this:

So bnd does not take the version information from the other jar’s MANIFEST.MF, it retrieves the information from the package-info.class?

And:

Assuming that the replacer() applies the macro substitution, why is it invoked in the first place, if the version string is only supposed to contain a literal version? (Looks a bit like a trap, because as a user you simply try (macro processing is used everywhere in bnd) find that the generated MANIFEST.MF is okay, and use that “feature” – which is very useful if you have an API that consists of more that one package and you want the versions to move in sync.)

Finally:

Not really. Honestly, my assumption has always been that the annotation is used to generate the information in MANFEST.MF (and up to now, I had never cause to doubt this, tutorials usually mention annotation as a convenient way to provide information that ends up in MANIFEST.MF). I wonder how many OSGi users are aware that there is OSGi related information which is not in MANIFEST.MF but “spread” in the code base.

But, okay, I’ll move the version information back into the bnd.bnd. Maybe this “central” point of maintenance isn’t a bad idea after all.

Bnd uses the version information in the package-info.class file as the source of truth. The use of the package-info.class file as the source of version information allows one to build against compiler output folders (version=project in Bnd Workspace model) and see the package versions.

It has been that way for almost forever (at least since 2010). It is probably not a good idea, but that is what the code has done historically. We could remove this, but it would upset someone :slight_smile:

Really you do. The semantic version of a package is an intrinsic part of a package like the name of the package and the members of the package. It is not an external attribute which can be assigned later just like one should not add members to a package later (aka split packages.)

At runtime, all OSGi metadata is sourced from the manifest. However tools like Bnd are not an OSGi runtime and use annotation information for OSGi information. The end result of building a bundle is that all the information is in the manifest for the OSGi runtime, but Bnd needs to work with code which is not in a built bundle and thus uses information in CLASS retention annotations in class files.

Maybe consider to issue a warning if the string doesn’t match the OSGi version pattern. Wouldn’t break anything and might prevent new users such as @chrisr3 and me from following the wrong track.

That is a fair idea. Can you please open the issue?

Done