Maven copy-dependencies + shade - classpath management - java

To package a maven project with its dependencies, among many solutions, one may use maven-dependency-plugin with its goal copy-dependencies to get the dependencies in a folder besides, or one may use maven-shade-plugin to get all the code in a single jar.
I actually do both: I choose to have external dependencies (e.g. apache commons) as external libs, and my own dependencies (I have a multi-module maven parent project) shaded into a unique jar.
And it works, except for the classpath. I copy-dependencies with option excludeGroupIds to exclude my own maven group id. I shade with option to include only my own maven group id. Before that, I jar with option to add classpath to the manifest. All set, it works. But my classpath also contains my own dependencies that were actually shaded in the final jar.
It is no big deal, because the result works even with this erroneous classpath. But I wonder if there is a simple means to have the correct classpath, in order not to expose my internal structure to my users.
Here is a basic example demonstrating the problem:
<groupId>com.foo.bar</groupId>
<artifactId>com.foo.bar.launcher</artifactId>
<dependencies>
<dependency>
<groupId>com.foo.bar</groupId>
<artifactId>com.foo.bar.utils</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeGroupIds>com.foo.bar</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>com.foo.bar:*</include>
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
The resulting manifest contains this:
Class-Path: lib/com.foo.bar.utils-0.0.1.jar lib/commons-lang3-3.8.1.jar while the com.foo.bar.utils one does not exist.

If you look into the following mvnrepository link maven shade plugin depends upon maven dependency tree. As per the above pom.xml maven dependency plugin, you have excluded com.foo.bar dependency. You can omit the maven-dependency-plugin to create fat jar. It is not mandatory to use in case of shade plugin.
You can use the following command to check and copy all the dependencies used in the project.
mvn dependency:copy-dependencies
https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-shade-plugin/3.2.1

Related

maven assembly plugin: add a file into a dependency jar

There is this maven module, say prj-package-module, to package the project artifacts into a tar file using maven-assembly-plugin. There are also jars added as dependencies in the prj-package-module/pom.xml and packaged into the tar file.
Now the requirement is to add a file, prj-pakacge-module/src/main/resources/file.xml to one of these dependency jars before packaging into the tar file. How can I achieve this?
Edit: The file is a JNLP with list of dependency jars dynamically added to it. For security reasons, Javaws also requires the JNLP file to be added a jar and the jar to be signed. This is where I hit the problem.
Here is a maven solution to dynamically unpack an existing dependency, add (copy) a resource to the unpacked folder, repack (jar) the hole and as such get a modified copy of the initial jar.
A sample pom file doing exactly that for the junit dependency:
<build>
<plugins>
<!-- unpack step -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>unpack</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<type>jar</type>
<outputDirectory>${project.build.directory}/unpack-tmp</outputDirectory>
<includes>**/*.class,**/*.xml</includes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<!-- add the additional resource step -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/unpack-tmp</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<include>test.properties</include>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- repack step -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>repack</id>
<phase>prepare-package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classesDirectory>${basedir}/target/unpack-tmp</classesDirectory>
<finalName>junit-modified</finalName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
What it is actually doing:
As part of the prepare-package phase we are adding three steps to create a modified jar from a dependency
First step: unpack the dependency via the Maven Dependency Plugin and its unpack goal: an unpacked folder will be created to the tmp folder unpack-tmp
Second step: copy the concerned resource via the Maven Resources Plugin and its copy-resources goal.
Third step: re-pack the whole via the Maven Jar Plugin and its classic jar goal
Running the sample above, the junit-modified.jar file will appear in the target folder of the maven project.
The order of the plugin configurations above is important to respect the steps flow as part of the same phase.
Then, you have one additional file to add to your fat-jar, which is indeed the modified jar you were probably looking for.
If you don't need a dynamic approach, a better approach would be to do it once and have a classified version of that dependency as explained in this other SO post.
Alternatively, move it to a Maven profile so that at least it is not part of the default build.
It seems that you're trying to modify a dependency of the maven project before packaging it into the final artifact of the project.
This is dodgy. If that jar/library (the dependency) is yours, then modify the pom of that jar, rebuild it, and then have the current project resolve the latest artifact.
If, otherwise, the dependency jar is not yours, then it should suffice to have the resource (prj-pakacge-module/src/main/resources/file.xml) in the current project (the one you're building now), as the end result will be the same (as long as the assembly plugin is set to flatten all artifact jars in the target uber/fat jar...

maven generated jaxb classes - ClassNotFoundException

I have a maven model project, where I am generating jaxb class by maven command - clean install
and the jaxb classes are generated under target folder and jar file is generating under .m2 repository folder.
Now on my other project adding this jar as a dependency with proper group id and artifactId.
But I am getting ClassNotFoundException and compile error for those generated jaxb classes.
I am updating my question to add more details.
The Pom File of Model Project.
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.8.3</version>
<executions>
<execution>
<id>spf-ssp-generate</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<generateDirectory>${project.build.directory}/jaxbclasses/pqr/xyz</generateDirectory>
<generatePackage>abc.vo.apply.v1</generatePackage>
<schemaIncludes>
<include>MyXSD.xsd</include>
</schemaIncludes>
</configuration>
</execution>
</executions>
<configuration>
<schemaDirectory>src/main/resources</schemaDirectory>
<extension>true</extension>
<args>
<arg>-XtoString</arg>
<arg>-Xequals</arg>
<arg>-XhashCode</arg>
</args>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>0.6.4</version>
</plugin>
</plugins>
</configuration>
</plugin>
</plugins>
</build>
On clean install it generates the class files under.
target
jaxbclasses
pqr
xyz
with the package name - abc.vo.apply.v1
I have another Two Maven project(jar), suppose as, A & B.
Now I can use the jaxb model project as a maven dependency, and it compile fine.
Now My Web project is not a Maven project - it is a Liferay based on Ant.
I manually copy the A, B and The Jaxb Model project in to lib folder.
It compile fines. but I am getting ClassNotFoundException.
I am adding another answer, which I think is more accurate.
In order to make your JAXB project compile, I had to add this dependency:
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>0.6.5</version>
</dependency>
which obviously won't be automatically part of your classpath for Liferay.
When I ran mvn dependency:list, I got this:
org.jvnet.jaxb2_commons:jaxb2-basics-tools:jar:0.6.4:compile
org.jvnet.jaxb2_commons:jaxb2-basics:jar:0.6.4:compile
commons-lang:commons-lang:jar:2.2:compile
commons-logging:commons-logging:jar:1.1.1:compile
com.google.code.javaparser:javaparser:jar:1.0.8:compile
org.jvnet.jaxb2_commons:jaxb2-basics-runtime:jar:0.6.4:compile
commons-beanutils:commons-beanutils:jar:1.7.0:compile`
which means that you need to put these in the lib directory of your Liferay installation as well.
This is most likely due to the fact that the target directory is never included in the jar file by default. Try configuring the JAXB classes to be generated under (say) target/generated. Then, add this to the build plugin section of the POM:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-java-sources</id>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>

How to set additional Class-Path entries in manifest with onejar Maven plugin?

Is there a way to add an arbitrary classpath entry to a JAR file manifest using onejar-maven-plugin?
I found the way to configure maven-jar-plugin to do this, but it appears that there is no such option for onejar-maven-plugin.
This is not done to find additional classes (otherwise why use the onejar plugin, right?), but rather to locate a configuration file that must be external to the JAR.
Is there a direct solution or a workaround for this?
Is the usage of the one-jar plugin really required?
You can achieve the same goal (packaging in one single jar your application AND all the required dependencies, including transitive ones, AND add configuration for Class-Path AND using a more stable/standard plugin) applying the following approach:
Configure the Class-Path entry in your application Jar using the Maven Jar Plugin and the approach you mentioned in the question
Use the Maven Assembly Plugin to package one single JAR including dependencies, as explained here, in another stackoverflow question/answer.
An example of one-jar executable file (without using the one-jar plugin) could be as following:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!-- your further configuration here -->
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.sample.MainApp</mainClass>
<!-- your further configuration here -->
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
If you need to further play with classpath and Maven, I would suggest to also check this question here on stackoverflow.
Adding arbitrary manifest entries is possible in 1.4.5:
<plugin>
<groupId>org.dstovall</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.5</version>
<executions>
<execution>
<configuration>
<manifestEntries>
<Build-Status>Yes</Build-Status>
</manifestEntries>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
The onejar-maven-plugin project doesn't seem to be in active development anymore, so you might want to switch to other solutions (e.g. maven-assembly-plugin) eventually.
The plugin is not available on Maven Central. Someone else put up a version of it to Maven Central with a different group ID.
Additional libraries can be added to the classpath at the time of launch.
The property one-jar.class.path can be used
one-jar.class.path
Extra classpaths to be added to the execution environment. Use platform independent path separator '|'
Example: --one-jar.class.path="./lib/two.jar|/opt/lib/three.jar"
Source: http://one-jar.sourceforge.net/index.php?page=details

maven multi module dependency version

I have a multi-module maven project: Module A depends on Module B. When Module B is built it is named ModuleB.jar and copied to target directory. Now in Module A's pom I have to put
<dependency>
<groupid>com.mycompany.app</groupid>
<artificatId>ModuleB</artifactId>
<version>1.0</version>
</dependency>
I have to put the version number, but then now I have two same Module B jars: ModuleB.jar (no version in name) and ModuleB-1.0.jar along with ModuleA.jar in the target directory.
I prefer to keep the version number out from the jar's final names since we have other legacy apps depending on these jars
Is there anyway to add Module B as a dependency in Module A's pom without the version number? Any clean solutions at all or am I just making life difficult for myself?
EDIT:
To clarify: These modules are built from a parent pom as jars and are copied, along with their dependencies, to a target directory outside of the project's parent directory. Hence the reason why there are two ModuleB jars: one when Module B is compiled and packaged with the finalname set as ModuleB.jar and one when Module A is compiled and packaged along with its dependencies, which includes Module B but with the name ModuleB-1.0.jar
Thanks!
The copy-dependency execution (I could strip out the version during the copy but then I would lose versions on 3rd party jars such as Spring jars =/ ):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${build.dir}/lib</outputDirectory>
<overWriteIfNewer>true</overWriteIfNewer>
<stripVersion>false</stripVersion>
</configuration>
</execution>
</executions>
</plugin>
So the problem was that I was copying my internal modules twice: once when running maven-jar-plugin (with no version in finalname) and once when running maven-dependency-plugin, and both plugins are outputting to the same target directory giving me ModuleB.jar and ModuleB-1.0.jar.
So in the parent pom I configured the plugins:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<outputDirectory>${build.dir}/lib</outputDirectory>
</configuration>
</plugin>
and
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${build.dir}/lib</outputDirectory>
<overWriteIfNewer>true</overWriteIfNewer>
<stripVersion>false</stripVersion>
<excludeGroupIds>com.mycompany.app</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
The first plugin is building all my child modules and jar'ing them to the output directory specified. the second plugin is copying all my module's dependencies to the same output directory in the first plugin sans the internal modules using <excludeGroupIds>
Thank you for helping me realize there was a simple solution!

Maven checkstyle https configuration

I have the following maven check style plugin configuration
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<consoleOutput>true</consoleOutput>
<configLocation>https://someUtl.com/file.xml</configLocation>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
Pay attention on
<configLocation>https://someUtl.com/file.xml</configLocation>
file.xml can be downloaded by browser, but it require a login and password. Is there a way to specify these login/password in maven or in plugin configuration?
Underneath, this uses Plexus which in turn pretty much does URL.openStream().
This answer shows how an Authenticator can be used for that in Java code, but I was unable to find a Maven equivalent for that. I'm inclined to say that it's not possible.
Alternatively, you might be able to download the file in a separate mojo execution, then point the configLocation to the downloaded file, which could be anywhere down your target folder.
I think that this answer gives a few nice ideas about how to download files in Maven. Their first is that if your file is a Maven artifact, you could use the Maven Dependency Plugin.
And then we come full circle, because if your Checkstyle configuration were to be contained in a Maven artifact, you would not have to set configLocation to a remote location, but you'd add that artifact as a dependency of your Checkstyle plugin execution. Since Maven defines everything in terms of dependencies, that is my way to go, and that is exactly how I set up my own Checkstyle configurations.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.totaalsoftware.incidentmanager</groupId>
<artifactId>checkstyle-config</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<configuration>
<configLocation>checkstyle.config.xml</configLocation>
...
</configuration>
</plugin>
Clearly, in the above example, checkstyle.config.xml resides in the root of the checkstyle-config JAR.

Categories