Using maven-bundle-plugin with the maven-shade-plugin - maven

I'm using the maven-shade-plugin to relocate some packages during the package phase of my build. I'm also using the maven-bundle-plugin to generate a manifest. The problem is the bundle plugin runs before the shade plugin (during the process-classes phase), and doesn't include any of my shaded packages in the generated manifest's exports.
How can I get these two plugins to play nice with each other, so that my relocated packages are treated like any other package by the bundle plugin?
--
By request, the Shade and bundle sections of my POM:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<filters>
<filter>
<artifact>cglib:cglib</artifact>
<includes>
<include>net/sf/cglib/core/**</include>
<include>net/sf/cglib/proxy/**</include>
</includes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>net.sf.cglib</pattern>
<shadedPattern>org.modelmapper.internal.cglib</shadedPattern>
</relocation>
<relocation>
<pattern>org.objectweb.asm</pattern>
<shadedPattern>org.modelmapper.internal.asm</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.7</version>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>
org.modelmapper,
org.modelmapper.builder,
org.modelmapper.config,
org.modelmapper.convention,
org.modelmapper.spi
</Export-Package>
<Private-Package>
org.modelmapper.internal.**
</Private-Package>
<Import-Package>
*
</Import-Package>
<Include-Resource>
{maven-resources},
{maven-dependencies}
</Include-Resource>
</instructions>
</configuration>
</plugin>
Taken from here

Another option is to dump maven bundle plugin altogether and use Maven Shade Plugin ManifestResourceTransformer to add the desired OSGI metadata to the manifest.
Take a look at xbean-asm-shaded/pom.xml for a example.
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>
org.apache.xbean.asm;org.modelmapper.builder; ...
</Export-Package>
<Import-Package>*</Import-Package>
<Private-Package>org.modelmapper.internal ...</Private-Package>
</manifestEntries>
</transformer>

Solution is very simple. You still can use maven-bundle-plugin and maven-shade-plugin at the same time. You just need to remember about the order. If you use bundle packaging maven bundle plugin will get executed during package phase before maven-shade. But that's not so wrong.
Here is the deal.
Use Private-Package: pkg.name.before.shading
Make maven-shade-plugin with one <include>null:null</include> - this will prevent shade plugin from creating empty jar
use maven-shade-plugin relocations - from pkg.name.before.shading to other.pkg.
You may see this trick working in FasterXML jackson-module-paranamer

I assume that after the compile phase is done you want to:
relocate some classes with the shade plugin
create a manifest with the bundle plugin
pack it all up with the jar plugin
The problem is the bundle plugin runs before the shade plugin
The bundle plugin is binded to process-classes phase which comes before the package phase to which the shade plugin is bound.
I suggest that you bind the shade plugin to process-classes phase also. Change shade plugin configuration like this:
<phase>process-classes</phase>
Since the shade plugin definition comes before the bundle plugin definition in the pom file, the shade plugin will run before the bundle plugin during the process-classes phase.

There is a neat transformer implementing just this functionality from Hazelcast - HazelcastManifestTransformer (ver 3.9). What it does is thoughtfully merging the Import-Package and Export-Package attributes, enabling the user to exclude extend/reduce the default merged result.
How to use it in your pom.xml:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<dependencies>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-build-utils</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="com.hazelcast.buildutils.HazelcastManifestTransformer">
<mainClass>...</mainClass>
<!-- the tag below is required due to a missing null-check it seems -->
<overrideInstructions></overrideInstructions>
</configuration>
</executions>
</plugin>
The override instructions (Export/Import-Package) are comma delimited package names, preceded with an exclamation mark whenever we want to exclude those specific ones from the list.
Hope this helps! I do realize that's an old question, but seems the Hazelcast's transformer isn't gaining much publicity.

Related

Running maven-shade and dockerfile-maven in different phases

My app uses maven-shade-plugin to pack things into single fatjar and then I would like to build a docker image using dockerfile-maven-plugin, my problem is that I can set the pom file properly so it would work.
What happens is that the docker plugin runs before the jar file was created...
I've tried to force the jar creation on prepare-package and the docker image build on package but it didn't work as expected...
any ideas?
EDIT: added pom snippet
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>${dockerfile-maven.version}</version>
<configuration>
<repository>test-docker-image</repository>
<tag>${docker.tag}</tag>
<buildArgs>
<JAR_FILE>${project.artifactId}-${project.version}-fat.jar</JAR_FILE>
<CONFIGURATION_FILE>configuration.json</CONFIGURATION_FILE>
</buildArgs>
</configuration>
<executions>
<execution>
<goals>
<goal>build</goal>
<!-- <goal>push</goal> -->
</goals>
<phase>install</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>io.vertx.core.Launcher</Main-Class>
<Main-Verticle>MyVerticle</Main-Verticle>
</manifestEntries>
</transformer>
</transformers>
<minimizeJar>false</minimizeJar>
<outputFile>${project.build.directory}/deploy/${project.artifactId}-${project.version}-fat.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
I had both of these same plugins listed in the same order you have (docker-maven-plugin before maven-shade-plugin) and was seeing the same issue. I discovered that Maven executes plugins in the same phase in the order that they are listed, so moving maven-shade-plugin to be first resolved the issue for me locally. (Older versions of Maven don't order plugins this way, so use the latest if possible.)
This doesn't explain why it is not working for you when you change them to use different phases, but I'd suggest at least trying it out that reordering. Also be sure that you are using the latest versions of related plugins, like maven-release-plugin.
I was still seeing the undesired behavior in my build environment after doing all of the above, due to the parent of my project containing an execution for docker-maven-plugin; the fact that I was customizing it and reordering it in my own project didn't help, although it is unclear why it worked locally. Maven build profiles can also have a similar impact on ordering. My solution there was to bind the docker-maven-plugin:build execution to the install phase.
The output of mvn help:effective-pom should let you see the exact listing of plugins, executions, and profiles so that you can see exactly what the ordering will be for your project. Note that profiles are executed bottom-to-top, which is the opposite of plugins!

How do I create an uber source jar with Maven?

Is there a well-known way to create an uber source jar? In other words, a jar of all the source code for a project and all its dependencies (or at least those that have a -sources.jar)?
I've looked into doing it with the maven-assembly-plugin, but using a dependencySet with includes of *.*.*.sources.* (or *.sources) doesn't work because those are not actually dependencies of the project, and I don't want to add them all.
You can use the maven-shade-plugin to create an uber jar. Just include the following within your <build> tag -
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>source-jar</id>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createSourcesJar>true</createSourcesJar>
<artifactSet>
<includes>
<include>...</include>
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
To modify the configuration, you can use Resource Transformers within org.apache.maven.plugins.shade.resource package.
And to define the contents of the jar, you can further use includes and excludes within the filters.
I found some information on working with sources in the maven-dependency-plugin.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>src-dependencies</id>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<classifier>sources</classifier>
<failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
<outputDirectory>${project.build.directory}/sources</outputDirectory>
<includeGroupIds>{your group prefix}</includeGroupIds>
<includes>**/*.java</includes>
<includeScope>runtime</includeScope>
</configuration>
</execution>
So if I do that, and then run a maven-assembly-plugin referencing the unpacked files, I can do it in two steps.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<execution>
<id>uber-source</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptors>
<descriptor>ubersource.xml</descriptor>
</descriptors>
<outputDirectory>${deploy.internal.directory}</outputDirectory>
<finalName>${project.artifactId}</finalName>
</configuration>
</execution>
</executions>
</plugin>
with a file set in the assembly descriptor ubsersource.xml:
<fileSet>
<directory>${project.build.directory}/sources</directory>
<outputDirectory>.</outputDirectory>
</fileSet>
And then I get my uber source jar...
There is perhaps a subtle distinction in the way the maven-assembly-plugin and maven-dependency-plugin treats sources. If you reference classifier sources in a dependencySet of an assembly descriptor, it looks for sources that are actual dependencies in your pom -- not that useful. However, in maven-dependency-plugin, referencing sources classifier means that sources of your dependencies. Hence why this solution works.
I also wrapped this up in my own plugin using mojo-executor to make it single step, and single declaration in my pom, but that's optional
This is a lot more pom code, but I like it better than the maven-shade-plugin because it does just what I want, and nothing more.

Maven shade plugin does not merge files

My goal is to merge two XML files (both named info.xml) which are in src/main/resources of two Maven modules (packaging:jar) into a target WAR archive.
proj1: contains src/main/resources/info.xml
proj2: contains src/main/resources/info.xml
web: web project which should contain a merged info.xml from proj1 and proj2. I declared the plugin in the web project:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.XmlAppendingTransformer">
<resource>info.xml</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
To build the web project and its modules I have a build project with:
<modules>
<module>proj1</module>
<module>proj2</module>
<module>web</module>
<modules>
I tried using a mvn clean package command in the build project and as result my web/target/web.war contains the unpacked libs (ueber.jar which I don't really want) and NO merged info.xml files.
What am I doing wrong?!
You have to configure Transformers to merge files, since it often requires extra logic, especially XML. See Resource Transformers for the details.

How to make maven bundlor to use my MANIFEST.MF instead of using a template and types detection?

I am developing an OSGi app with Virgo, and using maven bundlor to build the bundles. I want to use my MANIFEST.MF used by Virgo which includes bundle imports and a few package imports, but bundlor auto detects the classes used by my bundle and generates packages imports for them including the ones from the bundles in Import-Bundle header.
Is there a way to tell bundlor just use my already built MANIFEST.MF or disable the java type auto detection?
Well, just don't use bundlor then? Like said in the documentation, "Bundlor's main function is to scan an existing JAR file and determine its runtime dependencies.",
For example, with maven-bundle-plugin (in this example, I have a .war file, but that shouldn't matter)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<!-- add the generated manifest to the war -->
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
<failOnMissingWebXml>true</failOnMissingWebXml>
<packagingExcludes>WEB-INF/web.xml</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<supportedProjectTypes>
<supportedProjectType>jar</supportedProjectType>
<supportedProjectType>bundle</supportedProjectType>
<supportedProjectType>war</supportedProjectType>
</supportedProjectTypes>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-ClassPath>.,WEB-INF/classes,{maven-dependencies}</Bundle-ClassPath>
<Bundle-ManifestVersion>2</Bundle-ManifestVersion>
<Embed-Directory>WEB-INF/lib</Embed-Directory>
<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
<Import-Package>
javax.annotation,
javax.servlet;version="[2.5,3.0]",
javax.servlet.http;version="[2.5,3.0]",
org.osgi.service.http,
org.osgi.service.packageadmin,
org.osgi.framework;version="[1.5,2.0)",
org.jboss.logging;version="[3.0,4.0)"
</Import-Package>
<Private-Package>fi.eis.applications</Private-Package>
<Web-ContextPath>/spring-app</Web-ContextPath>
</instructions>
</configuration>
</plugin>
I could've left the maven-bundle-plugin also undefined and just placed a manifest file in WEB-INF.

How to register a custom built jar file as maven main artifact?

I have a project expected to deliver a jar file:
<packaging>jar</packaging>
but the jar is built in a custom way, so the default packaging done with jar:jar has been disabled
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>default-jar</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
but then when I want to apply shade:shade on the existing jar I get an error
The project main artifact does not exist.
I assume that maven doesn't know about the .jar file created by my custom tool. How to let it know, because antrun attachArtifact doesn't work
<attachartifact file="./bin/classes.jar" classifier="" type="jar"/>
the error I get is
An Ant BuildException has occured: org.apache.maven.artifact.InvalidArtifactRTException: For artifact {:jar}: An attached artifact must have a different ID than its corresponding main artifact.
So this is not the method to register main artifact... Is there any (without writing custom java plugin)?
Thanks,
Lukasz
I checked the sources of JarMojo and it gave me an idea how to solve it with Groovy (via gmaven)
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<id>set-main-artifact</id>
<phase>package</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
project.artifact.setFile(new File("./bin/classes.jar"))
</source>
</configuration>
</execution>
</executions>
</plugin>
and it works!:)
Something like this
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>attach-artifacts</id>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>${basedir}/bin/classes.jar</file>
<type>jar</type>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
While your solution may work for a build to the install+ phase or where there are no dependencies in the reactor, in cases where only building to the compile or test phase the unpackaged classes won't be found by dependencies.
Building to compile happens when using plugins like the maven-release-plugin.
Extending your chosen solution to include identifying the unpacked classes during compile
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<id>set-main-artifact-compile</id>
<phase>compile</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
project.artifact.setFile(new File("./bin/classes"))
</source>
</configuration>
</execution>
<execution>
<id>set-main-artifact</id>
<phase>package</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
project.artifact.setFile(new File("./bin/classes.jar"))
</source>
</configuration>
</execution>
</executions>
</plugin>
By default the maven-install-plugin will use the identified artifact along the lines of
${project.build.directory}/${project.finalname}.jar
So another option might go something like this
<build>
<directory>bin</directory>
<outputDirectory>bin/classes</outputDirectory>
<finalName>classes</finalName>
</build>
We were having the same problem, with getting the "attached artifact must have a different ID than its corresponding main artifact" error. We found the solution in the following excellent blog post:
embed-and-run-ant-tasks-and-scripts-from-maven
As detailed in this section, you can fix the problem by adding a classifier so Maven can distinguish between the ant-built jar and the maven-built jar. Since you're using antrun attachartifact, you'd need this:
<attachartifact file="./bin/classes.jar" classifier="foo" type="jar"/>
Note you'll also need to include that classifier (along with groupId, artifactId and version) whenever you want to grab this jar as a dependency in other projects.

Resources