So my friend Thiago Moreira posted the following (slightly edited)
message to our internal Slack:
Hey, so I've built a module for 7.4 GA5, but when it is
deployed to 7.4 GA16, I get a bunch of "unresolved
requirement" exceptions and the module won't be available. Is
there something I need to set so my module will work on both GA5
I was actually kind of surprised to hear this. Historically, when
building an artifact I always say to target the minimum version you
can run on such as GA1, and they would work even on later GAs as long
as the APIs weren't actually changed. Certainly this should be the
same in 7.4?
How was this possible in earlier 7.x versions?
In 7.x versions prior to 7.4, I always recommended (and adhered to
myself) the policy of targeting the earliest GA/FP that provided the
necessary functionality, api, whatever and let OSGi handle ensuring
your bundle was deployed in a container at that version or newer.
This was a solid strategy because when you define a dependency on,
say, portal-kernel for version 7.3 GA1, this will
internally be declared as a number of package dependencies, i.e.
dependencies on com.liferay.portal.kernel.util,
com.liferay.portal.kernel.dao, etc. As developers we
don't really see this unless we crack open the compiled jar and review
the MANIFEST.MF file's Import-Package declaration.
If you do actually crack it open and check out the
Import-Package line, you'll see a bunch of entries like:
This is the data that OSGi uses at runtime to determine if the
available package version(s) in the environment will satisfy the
requirement or whether you get the dreaded unresolved requirement exception.
Each package declaration in the manifest has its own version range,
so for example, the bundle I pulled the example from has a dependency
on the com.liferay.portal.kernel.exception package with a
version range of 8.4 or greater, but less than 9.
Under 7.0 through 7.3, the Liferay convention was to only change
major package version number on major releases, so we might
have seen the com.liferay.portal.kernel.exception package
be versioned at 7.x in 7.2, 8.x in 7.3, and we would expect 9.x in
The range then, for the [8.4,9) syntax, means that the
module itself ran on some fixpack version of 7.3 or later, but it
would not run on 7.4. This was a really effective way to build our
bundle once and know that, regardless of what fixpack was applied into
the environment, our module would continue to resolve.
So when Thiago shared his message, at first I thought there was
something wrong in his project. Certainly if he was compiling and
targeting GA5, the convention I assumed was still in place would mean
that the bundle would also work on GA16 too.
But, I found out that my assumption was wrong.
That's why we're here today ;-)
There are a number of good reasons for this change:
For the development team, though, this could introduce some new complexities:
Depending upon your perspective, you may fall on the side of
"Boy, am I glad Liferay made this change..." or you could be
"We can't abide this new model...".
The good news that I have for you my friends, there are options that
will make either side happy...
If you're in the camp that says it is good to rebuild your modules to
target a specific bundle release to ensure maximum compatibility, your
option is easy. Update the liferay.workspace.product
property in your gradle.properties file in your workspace to the new
target version and then do a clean build. All of your artifacts will
be built for the specific bundle release and you'll know if your code
is using a previously deprecated method or class that has been removed.
If you're in the camp that says a single build should resolve under
any U bundle and I'll deal with issues (such as
ClassNotFoundExceptions or NoSuchMethodExceptions when using a
deprecated object that was removed) when/if they come up, your option
is also easy. Just open the bnd.bnd file in the modules that you want
this coverage in and add the following:
These two BND directives effectively define the default range for
package version matching for you, going from [8.4,9) to [8.4,infinity).
This will allow OSGi to match on 8.4 or any greater version, but you
do still have control if you need to limit version range. For example,
if you knew that a class was only available in the
com.liferay.portal.kernel.exception package through
package version 12.x, you could add this restriction by explicitly
defining an Import-Package version:
This format applies a specific version range on a given package and
ensures that if your module is deployed at some 7.4 version that has
package version 15.x, your bundle will not resolve and lead to an
unresolved requirement exception.
Note the asterisk at the end of the directive, this is necessary so
any other imported packages can be pulled in as necessary for the code.
So depending upon which camp you're in, you now know what is coming
and you can prepare for it accordingly.
As to which camp I'm in, I find myself more on the side of a single
build, tested artifact used everywhere until it no longer works, so
I'm going with the bnd.bnd changes.
That said, my position is based on my being a lazy developer that is
not involved in all of the regular, active environment management
responsibilities, so my position is not going to be good for everyone.
If you ask me what the right camp is, or what camp Liferay
recommends, both answers are solidly in the rebuild your artifacts
targeting a release camp. Liferay and Support are much more concerned
with the guarantees stemming from rebuilt artifacts targeting the
specific release they are deployed to.
But, whatever camp you're in, hopefully you find the option here to
make that camp happy...