2

I am trying to setup a LWJGL project using Maven. I am using the example "getting started" source code from the official website.

This includes a few lines accessing LWJGL's manifest attributes, such as a simple version check:

System.out.println("Hello LWJGL " + Version.getVersion() + "!");

This runs without any problems in the Eclipse environment (of course after having built the project with Maven), but when running clean install and then running **-jar-with-dependencies.jar through cmd, the following exception get's thrown:

java.lang.NullPointerException
        at org.lwjgl.system.APIUtil.apiGetManifestValue(APIUtil.java:97)
        at org.lwjgl.Version.getVersion(Version.java:33)
        at HelloWorld.run(HelloWorld.java:43)
        at HelloWorld.main(HelloWorld.java:130)

This is because the Manifest object created by APIUtil does not include any attributes - but only in the built version by Maven.

Why is this? Is my pom.xml buggy, or is LWJGL 3.0.0 just not ready for this?

This is my pom.xml:

<properties>
    <mainClass>HelloWorld</mainClass>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <finalName>${project.artifactId}-${project.version}.jar</finalName>
</properties>

<dependencies>
    <dependency>
        <groupId>org.lwjgl</groupId>
        <artifactId>lwjgl</artifactId>
        <version>3.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.lwjgl</groupId>
        <artifactId>lwjgl-platform</artifactId>
        <version>3.0.0</version>
        <classifier>natives-windows</classifier>
    </dependency>
    <dependency>
        <groupId>org.lwjgl</groupId>
        <artifactId>lwjgl-platform</artifactId>
        <version>3.0.0</version>
        <classifier>natives-linux</classifier>
    </dependency>
    <dependency>
        <groupId>org.lwjgl</groupId>
        <artifactId>lwjgl-platform</artifactId>
        <version>3.0.0</version>
        <classifier>natives-osx</classifier>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>${mainClass}</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
Frithjof
  • 2,214
  • 1
  • 17
  • 38

1 Answers1

2

This errors happens because LWJGL 3.0.0 is looking inside the Manifest a property called "Implementation-Version", but when you made the uber-jar, this property was not set.

This is not really an issue with how you made the uber-jar: the Manifest that was created by maven-assembly-plugin looks like:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: Me
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_102
Main-Class: HelloWorld

You can see it inside META-INF/MANIFEST.MF of the jar-with-dependencies. This file does not have a "Implementation-Version" property. This is normal: when this executable JAR was created, all the MANIFEST of all dependencies were (rightfully) ignored, only to generate one containing the "Main-Class", just so that the JAR is executable.

The uber-jar cannot contain what is inside each of the dependencies manifest. For example, "Implementation-Version" is a property that is present in the manifest of multiple libraries, so which one should it keep? (There can be only one Manifest at the end, in the uber-jar). So the issue comes up because we're making an executable JAR, which can only have 1 Manifest so it cannot aggregate all the properties inside each of the dependencies manifest.

There are 2 possible solutions:

  1. Ignore it. After all, this is not really an error.
  2. Don't make an executable jar by embedding all the dependencies inside a single JAR, but create a ZIP assembly with each dependencies inside a lib folder: this way, each Manifest will be kept. This is done by telling the maven-jar-plugin to add a Manifest entry for the main class with the addition of the classpath and creating a custom assembly descriptor.

    <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.0.2</version>
        <configuration>
            <archive>
                <manifest>
                    <mainClass>${mainClass}</mainClass>
                    <addClasspath>true</addClasspath>
                </manifest>
            </archive>
        </configuration>
    </plugin>
    <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.6</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>single</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <descriptors>
                <descriptor>/path/to/assembly.xml</descriptor>
            </descriptors>
        </configuration>
    </plugin>
    

    where /path/to/assembly.xml is the path to the assembly descriptor, relative to the location of the POM, being:

    <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
      <id>dist</id>
      <formats>
        <format>zip</format>
      </formats>
      <dependencySets>
        <dependencySet>
          <outputDirectory>lib</outputDirectory>
          <useProjectArtifact>true</useProjectArtifact>
        </dependencySet>
      </dependencySets>
    </assembly>
    

    With such a configuration, running mvn clean install will create a ZIP file artifactId-version-dist.zip. Unpacking it and running (replacing <finalName> with the finalName of your JAR)

    java -jar lib\<finalName>.jar
    

    will print the version without any issues.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Great answer, very clear. I do not quite like the second option, as it makes it more difficult for the user to run the program. Is there perhaps an option to include the jars within the main jar? Would that work? Also, just ignoring it is not an option either, as LWJGL calls these methods anyway in static blocks when certain classes are initialized (e.g. Libary calls checkHash() which compares the actual library hash with a value stored in the manifest). – Frithjof Aug 25 '16 at 19:54
  • @Frithjof No including the libraries jar inside the main jar won't work. The jars would be treated as resources and the classes they contain won't be in the classpath (see http://stackoverflow.com/q/183292). In the second option, it is as easy as the first one: you need to unpack a ZIP and run the same command as before. If the concern is that there are lots of jars (so it is confusing which one to run), you could add a shell script (`start.bat` / `start.sh`) at the root of the ZIP and execute this shell script instead. (Pretty much like what the Maven distribution itself comes bundled.) – Tunaki Aug 25 '16 at 20:05
  • @Frithjof Another solution would be to have the main artifact not inside the `lib` folder but outside of it, at the root of the ZIP. Then you can run `java -jar finalName.jar` and need not worry about what is inside `lib`. Changes needed: 1. `false` (instead of `true`) in the assembly descriptor 2. Add in the descriptor `${project.build.directory}/${project.finalName}.jar/` to copy the main JAR and 3. Add `lib` in Jar Plugin configuration. – Tunaki Aug 25 '16 at 20:21