Maven - how can I add an arbitrary classpath entry to a jar?
Asked Answered
C

4

67

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.

Conative answered 2/10, 2009 at 14:59 Comment(4)
Out of interest, why do you want to add a classpath entry rather than specify a dependency and let Maven calculate the classpath?Gilbreath
It's kind of a complicated deployment situation - in a nutshell I have a jar that is in a well known location in the deployment target, and I want to add an absolute path to it. The dependency is known by maven, but it won't be deployed. In another similar case I want to add the same dependency with a different name to the classpath. It's one of those crazy one-off things that you run into when working in a corporate environment :)Conative
I guess that playing with dependency scope is not an option (I'm thinking to "provided"). Yeah, I agree, that would be a nasty work around.Allembracing
I actually did mess around with the "provided" and "dependency" scopes, but those don't work quite the way you might expect.Conative
P
85

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>
Preordain answered 5/5, 2011 at 6:14 Comment(2)
BEWARE: This approach is incompatible with JAR indexing! See https://mcmap.net/q/189924/-executable-jar-ignores-its-own-class-path-attribute for more information.Mcdevitt
@Laf Thanks! It worked for me. But I had to exclude addClasspath, coz it was adding everything(lib, jars etc.) to my manifest, and I only needed one folder(config/). Still, thanks!Would
G
18

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

Gilbreath answered 2/10, 2009 at 16:16 Comment(7)
I know how to use filtering, but I couldn't figure out how to filter the classpath into a file. Is there a built-in property for the classpath?Conative
I included a link to the filtering section of the getting started guide, you just need to configure the resources directory that contains the manifest to enable filtering. The page includes an example to do thisGilbreath
Thanks, but what is the magic maven property that inserts the classpath into the filtered manifest file?Conative
Thanks, it's the first time I've tried generating a filter file, opens some interesting possibilities...Gilbreath
Yeah, you can do all sorts of useful stuff with filtering.Conative
-1: This approach is incompatible with JAR indexing. See https://mcmap.net/q/189924/-executable-jar-ignores-its-own-class-path-attributeMcdevitt
The only bad tiny thing with this solution is that I'm getting Error assembling JAR: Unable to read manifest file (line too long) because the expanded ${classpath} value in the manifest.mf doesnt have the needed 72 characters line width, logical because there are many artifacts. Did someone have a similar experience? I'm asking because a fix would be welcome as I would like to use Rich Sellers approach: I need to filter one certain artifact off the classpath before the jar file is generated.Counterweigh
D
5

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

Diffidence answered 21/4, 2010 at 11:53 Comment(0)
S
0

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>
Stebbins answered 29/7, 2021 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.