Maven - how can I add an arbitrary classpath entry to a jar? - maven-2

I have an unusual situation where I need to add an arbitrary classpath entry (that points to a jar file) into the manifest of an executable jar. (This is for a Swing desktop application.)
The maven-jar-plugin generates the "Class-Path" entry for the jar manifest using the maven dependencies, and there doesn't appear to be any way of adding arbitrary entries.
I also looked at hard-coding the arbitrary classpath entry into the batch file that starts the application, using the "-classpath" parameter, but I can't figure out how to get Maven to filter the classpath into a batch file.

I found that there is an easy solution for this problem. You can add a <Class-Path> element to <manifestEntries> element, and set <addClassPath>true</addClassPath> to <manifest> element. So value of <Class-Path> element is added to class-path automatically. Example:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addClasspath>true</addClasspath>
<mainClass>your.main.Class</mainClass>
</manifest>
<manifestEntries>
<Class-Path>../conf/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>

Update: Here's how to filter a classpath into a custom manifest.
The maven-dependency-plugin's build-classpath goal can be configured to output the classpath to a file in the properties format (i.e. classpath=[classpath]). You then configure the filters element to use the generated classpath file, and configure the resources directory to be filtered.
For example:
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>build-classpath</goal>
</goals>
</execution>
</executions>
<configuration>
<outputFilterFile>true</outputFilterFile>
<outputFile>${project.build.directory}/classpath.properties</outputFile>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>
${project.build.outputDirectory}/META-INF/MANIFEST.MF
</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
<filters>
<filter>${project.build.directory}/classpath.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
Then specify the following in src/main/resources/META-INF/Manifest.MF:
Bundle-Version: 4.0.0
...
Classpath: ${classpath};[specify additional entries here]
Note: there is a bug with this processing using the standard window path separator (\), the generate path is stripped of separators (note it works fine on Linux). You can get the classpath to be generated correctly for Windows by specifying <fileSeparator>\\\\</fileSeparator> in the build-classpath goal's configuration.
You can customise the manifest in the jar-plugin's configuration. To do so you'd add something like this to your pom.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
...
<configuration>
<archive>
<index>true</index>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<mode>development</mode>
<url>${pom.url}</url>
<key>value</key>
</manifestEntries>
</archive>
</configuration>
...
</plugin>
The full archiver specification provides quite a few options. See the examples page for options on configuring the classpath.
If none of these work for you, you can define your own Manifest, set up properties containing the required entries and use a filter to populate the manifest with those properties

Try to do it like they do in this bug, i.e. merge entries using manifestEntries/Class-Path element
https://issues.apache.org/jira/browse/MJAR-41

I was able to get a slightly modified version of Rich Seller's approach working, avoiding the Error assembling JAR: Unable to read manifest file (line too long) issue that was mentioned in the comments.
I wanted to get all dependencies copied via the dependency-maven-plugin referenced in the .jar file's Manifest Class-Path. I could not use the <addClasspath>true</addClasspath> option of the Maven Jar Plugin as that put too much in my Jar Classpath (I'm only copying a selection of dependencies over).
Here's how I got this to work.
First I use the Maven Dependency Plugin to do the copying and at the same time build a classpath variable. Using the <outputProperty> I put this in a property rather than a file:
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
<goal>build-classpath</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- These properties are for build-classpath. It creates a classpath for the copied
dependencies and puts it in the ${distro.classpath} property. The jar Class-Path
uses spaces as separators. Unfortunately <pathSeparator> configuration property
does not work with a space as value, so the pathSeparator is set to a character
here and this is then replaced later using the regex-property plugin. -->
<prefix>lib</prefix>
<outputProperty>distro.classpath</outputProperty>
<pathSeparator>:</pathSeparator>
</configuration>
</execution>
</executions>
</plugin>
The syntax of the Jar Manifest Class-Path uses a space as separators. While the dependency plugin has a <pathSeparator> property, this one unfortunatly ignores the value if it is a space. So I just hardcode that one to some value and then use the build-helper-maven-plugin to replace it to that space I need:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<!-- Here the value of property for the jar the Class-Path is replaced to have a space
as separator. Unfortunately <replacement> does not work if a single space if specified
so this uses the surrounding .jar and lib to provide some content. -->
<name>distro.classpath.replaced</name>
<value>${distro.classpath}</value>
<regex>[.]jar[:]lib</regex>
<replacement>.jar lib</replacement>
</configuration>
</execution>
</executions>
</plugin>
Here, also the <replacement> value doesn't work if it's just a space, so I'm surrounding it with the text that exists around it.
Finally I can use the Maven Jar Plugin to pick up the property that was replaced with the space as separator. Because I pass the value of the classpath here in the maven definition (rather than picking it up from a filtered file) the line length constraints of the Manifest file will automatically be handled, and no 'line too long' problems appear:
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>org.acme.Main</mainClass>
</manifest>
<manifestEntries>
<!-- Include the computed classpath with all copied dependencies in the jar here -->
<Class-Path>${distro.classpath.replaced}</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>

Related

How to remove duplicate content from pom.xml for Maven?

I ran into such a situation, when packaging a project using maven, I'd like both the source package and the binary package, and they have the same manifest.mf file. Then I have to write the same entry in both plugin configuration of maven-source-plugin and maven-jar-plugin, like this:
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestEntries>
<Artifiact>${project.name}</Artifiact>
<Version>${project.version}</Version>
<Vendor>${project.organization.name}</Vendor>
<Built-By>Shiva Wu</Built-By>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifestEntries>
<Artifiact>${project.name}</Artifiact>
<Version>${project.version}</Version>
<Vendor>${project.organization.name}</Vendor>
<Built-By>Shiva Wu</Built-By>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
It's really not convenient to modify both of them while changing settings. Is there a better way to resolve this?
Thanks:)
There is no better way to handle it than what you are doing.
A couple extra things though. You can stick these configurations into a reuseable "corporate" or "standard" parent POM within the plugin management section and then you won't have to specify them again in any other pom. See here for details:
Best way to share portions of a Maven pom.xml across unrelated projects?
The other thing I notice is that your personal name should be substituted with a variable that should be set from within your settings.xml file. This will help increase build portability.

Disable the default-jar execution

I am using Maven Assembly plugin to pack a jar file.
But when I run mvn package, maven always trigger the [jar:jar {execution: default-jar}] to create a default jar file.
So I will have 2 jar files (one created by Assembly plugin and one created by Maven jar which i don't want to be created).
How can I turn off the default-jar execution?
In my pom.xml, I am using: <packaging>jar</packaging>.
I don't want to change it to <packaging>pom</packaging>.
(...) So i will have 2 jar files (one created by assembly plugin and one created by maven jar which i dont want to be created).
Looks like you're doing pretty complicated things. Maybe Maven is not the right tool in your case.
How can I turn off the execution: default-jar.
You can set the <phase> of the corresponding execution to something unknown, like none:
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>default-jar</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- this is used for inheritance merges -->
<phase>package</phase>
<!-- append to the packaging phase. -->
<goals>
<goal>single</goal>
<!-- goals == mojos -->
</goals>
</execution>
</executions>
</plugin>
This seems to work as long as you're providing something else to be installed, like an assembly (I only tested install). But of course, this is a hack.
While not a direct answer to the question, you could exclude the jar created by maven jar using <useProjectArtifact>false</useProjectArtifact>

maven: add arbitrary file as a servlet context resource

I have a maven war project which produces webapp.war, and a maven 'skin' project which produces skin.zip (a file full of resources and XML files). Now I want to add this zip file as a servlet context resource (e.g WEB-INF/skin.zip).
I tried using overlays, but it expands the zip file into WEB-INF instead of placing the un-expanded file there:
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>com.mycompany</groupId>
<artifactId>skin</artifactId>
<type>zip</type>
<targetPath>WEB-INF</targetPath>
</overlay>
</overlays>
</configuration>
</plugin>
Is there any way to prevent it from expanding the resource -- or somehow stick the file in there (without using ant-plugin).
Note: type is a totally unnecessary and unhelpful configuration element -- it does not tell the plugin how to expand the artifact, as you might expect -- it tells it how to FIND it. For example if you change type from zip to jar, it complains that it can't find the artifact (in the most unhelpful way possible).
I tried using overlays, but it expands the zip file into WEB-INF
Yes, that's what overlays do, the content is unpacked to be merged with the war. That's just not the right tool in your case.
Is there any way to prevent it from expanding the resource -- or somehow stick the file in there
I would use the Maven Dependency Plugin and its dependency:copy goal:
dependency:copy takes a list of artifacts defined in the plugin configuration section and copies them to a specified location, renaming them or stripping the version if desired. This goal can resolve the artifacts from remote repositories if they don't exist in local.
And bind it on the prepare-package phase. Below, some starting point:
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-prepare-package</id>
<phase>prepare-package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.mycompany</groupId><!-- or ${project.groupId} -->
<artifactId>skin</artifactId>
<version>X.Y.Z</version><!-- or ${project.version} -->
<type>zip</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
[...]
</project>
See Copying specific artifacts for more examples.

Maven - Add directory to classpath while executing tests

The Junits I have in my project need to load property files from the classpath. How can I specify the directory of those property files so that Maven will set that in the classpath before running the tests?
You can also add new test resource folders.
<build>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
<testResource>
<directory>${project.basedir}/src/test/something_else</directory>
</testResource>
</testResources>
</build>
The first path, src/test/resources, is the default. Assuming you still want the default path to be used, make sure it's included. (The testResources tag overwrites your defaults, so if you don't include the default path explicitly, it will stop being used.)
You can use the build-helper-maven-plugin to specify additional test-resource directories as follows. Using the configuration below, the contents of the test-resources directory will be copied to the target/test-classes directory during the generate-test-sources phase:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.12</version>
<executions>
<execution>
<id>add-test-resource</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>path/to/additional/test/resources</directory>
<excludes>
<exclude>**/folder-to-exclude/**</exclude>
</excludes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
If you just want to put your property files someplace on disk and don't want to copy those property files to target/test-classes during the build, you can do it this way
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>/add/this/to/path</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
Why not just use test/resources and place your properties in the classpath from that point. They'll only be there for the test phase.
If you have multiple resource environment you can use maven profile and put your various resources according to the profile you are testing.
test/resources/uat
test/resources/prod
test/resources/dev
But usualy if you need that you are making integration test then you don't need the build-helper-maven-plugin.
The maven-resources-plugin has a copy-resources goal that will allow you to copy resources. For example:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>additional-resources</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.testOutputDirectory}</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/conf</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
This will copy the contents of the conf folder in the base of your project to the target/test-classes folder (unless you modified project.build.testOutputDirectory) which will be added to the classpath during your unit tests.

Can I set the project version with a buildnumber-maven-plugin?

I'm trying to add the svn.revision to project version as a build number and can't seem to do so. My jar has the correct name durin packaging, but its installed in the my local repository it is as if ${buildNumber} is/was undefined when the version was set.
I get foo-1.0.0-SNAPSHOT-${buildNumber} instead of foo-1.0.0-SNAPSHOT-304
Any idea what I'm doing wrong or is adding a revision to the project version a bad idea? Thanks for the help.
<project>
...
<version>1.0.0-${release.identifier}-${buildNumber}</version>
<properties>
<release.identifier>SNAPSHOT</release.identifier>
</properties>
...
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<executions>
<execution>
<id>useLastCommittedRevision</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<useLastCommittedRevision>true</useLastCommittedRevision>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
...
</project>
The problem has two parts:
You're trying to set the buildNumber into the version before it is resolved so it will always be ${buildNumber} rather than the resolved value.
Instead of trying to dynamically change the version, you should set the buildNumber into the finalName element in the build. This will create the artifacts with the intended name in the local repository.
The install plugin will ignore the finalName and deploy it as 1.0.0-SNAPSHOT regardless, I don't know of a way to address that. The buildNumber is added to the Manifest if you configure the plugin as below.
So your configuration would be something like:
<version>1.0.0-${release.identifier}</version>
...
<build>
<finalName>${project.artifactId}-${project.version}-${buildNumber}</finalName>
...
</build>
I would avoid using build numbers on SNAPSHOT projects.
Maven provides the SNAPSHOT keyword to signify a volatile project in active development. So if you reference a project with a SNAPSHOT dependency version, Maven will automatically check for updates and keep your dependencies in sync.
If you then add a build number to the end of that version, you will have to manually update the dependencies, so you lose any benefit of having the SNAPSHOT suffix.
I personally avoid using build numbers where possible anyway. If I have to update a project, I just bump the version number, or use a suffix like beta-2 or RC2. If you need to track the revision in the SNAPSHOT, I'd recommend adding it to the Manifest so you can check where the build originated, but use the standard SNAPSHOT suffix to allow Maven to resolve the versions normally. The configuration below shows how to add the revision to the Manifest.
As far as your configuration is concerned, it looks OK to me assuming your SCM url is set up correctly. If you have no SCM configuration in your POM that may be the problem.
Can you run with -X and check for any output from the plugin indicating why it isn't setting the property?
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>0.9.4</version>
<executions>
<execution>
<id>useLastCommittedRevision</id>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.1</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Implementation-Build>${buildNumber}</Implementation-Build>
</manifestEntries>
</archive>
</configuration>
</plugin>
Add this after the buildnumber-maven-plugin:
<plugin>
<groupId>io.github.michaldo</groupId>
<artifactId>nashorn-maven-plugin</artifactId>
<version>0.0.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>eval</goal>
</goals>
<configuration>
<script>
$project.artifact.version = "${buildNumber}";
</script>
</configuration>
</execution>
</executions>
</plugin>
And the buildNumber will be recognized by packaging and deploy.