Maven appassembler Plugin: Dealing with Long Classpaths on Windows

ShriKant Vashishtha

When it comes to generating command-line scripts for Java applications, Maven "appassembler" plugin comes handy. Its "assemble" goal does all the maven magic, i.e. searching the dependencies used for creating the Java application, adding them into the classpath of resultant script and finally copying all relevant jars to a single place. It was all working very nicely until I stumbled across the problem of long classpaths in the Windows OS.

Irrespective of whether you use DOS prompt or cygwin, Windows limits the length of environment variables. Though there are various options to overcome this problem using Java 6 wildcard classpath, mapping the path to some drive etc, they all look like workarounds to me as problem can recur again anytime.

After some amount of research, I found appassembler plugin resolves this issue with booter-windows and booter-unix daemons. From the outset it looked like daemon does something else as the name is a bit misleading but in reality it's a generic way to start your Java applications. The booter-unix/booter-windows platforms were introduced for the sole purpose of resolving the long classpath issue (see MAPPASM-43) in appassembler maven plugin. Here's how it looks like in the pom you are working with:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>appassembler-maven-plugin</artifactId>
    <configuration>
        <repositoryLayout>flat</repositoryLayout>
        <installArtifacts>false</installArtifacts>
        <target>${project.build.directory}/appassembler</target>
        <defaultJvmSettings>
            <initialMemorySize>512M</initialMemorySize>
            <maxMemorySize>1024M</maxMemorySize>
            <extraArguments>
                <extraArgument>-DconfigFile=../../etc/config.properties</extraArgument>
                <extraArgument>-Dlog4j.configuration=../../etc/log4j.properties</extraArgument>
            </extraArguments>
        </defaultJvmSettings>
        <configurationDirectory>etc</configurationDirectory>
        <daemons>
            <daemon>
                <id>applicationName</id>
                <mainClass>com.xebia.appassebler.sample.Main</mainClass>
                <platforms>
                    <platform>booter-unix</platform>
                    <platform>booter-windows</platform>
                </platforms>
                <environmentSetupFileName>app-env</environmentSetupFileName>
            </daemon>
        </daemons>
    </configuration>
    <executions>
        <execution>
            < phase>package</phase>
            <goals>
                 <goal>generate-daemons</goal>
                 <goal>create-repository</goal>
            </goals>
        </execution>
    </executions>
</plugin>

If you look at the code carefully, you'll find "app-env" as a parameter to "environmentSetupFileName" tag. It is quite useful in passing environment variables to the resultant script. Using app-env (for UNIX) and app-env.bat (for Windows) environment setup files, I could pass a modified $REPO environment variable so that the copied repository containing dependencies is shared between booter-unix and booter-windows folders as follows:

|-- booter-unix

|   |-- bin

|   |   |-- applicationName
|   |   `-- app-env

|   `-- etc

|       `-- applicationName.xml

|-- booter-windows

|   |-- bin

|   |   |-- app-env.bat

|   |   `-- applicationName.bat

|   `-- etc

|       `-- applicationName.xml

|-- etc

|   |-- config.properties

|   `-- log4j.properties

`-- repo

    |-- <dependency1>.jar

    |-- <dependency2>.jar

    |-- ...

To copy the environment files and result of appassembler:generate-daemons maven goal to the appropriate locations we can use "maven-assembly-plugin" as follows:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptor>src/main/conf/descriptor.xml</descriptor>
    </configuration>
    <executions>
       <execution>
          <goals>
              <goal>single</goal>
          </goals>
          <phase>package</phase>
       </execution>
    </executions>
</plugin>

Here's what src/main/conf/assembly.xml looks like:

<assembly>
    <id>bin</id>
    <formats>
        <format>tar.gz</format>
        <format>zip</format>
        <format>dir</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>target/appassembler/booter-unix</directory>
            <outputDirectory>/booter-unix</outputDirectory>
            <excludes>
                <exclude>*.bat</exclude>
                <exclude>*.cmd</exclude>
            </excludes>
            <lineEnding>unix</lineEnding>
            <fileMode>0744</fileMode>
        </fileSet>
        <fileSet>
            <directory>target/appassembler/booter-windows</directory>
            <outputDirectory>/booter-windows</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>target/appassembler/repo</directory>
            <outputDirectory>/repo</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>src/main/conf</directory>
            <includes>
                <include>app-env.bat</include>
            </includes>
            <outputDirectory>/booter-windows/bin</outputDirectory>
        </fileSet>
        <fileSet>
            <directory>src/main/conf</directory>
            <includes>
                <include>app-env</include>
            </includes>
            <outputDirectory>/booter-unix/bin</outputDirectory>
            <lineEnding>unix</lineEnding>
        </fileSet>
    </fileSets>
</assembly>

Now, if you run "mvn clean package", it provides you the structure as mentioned in listing 2. You can execute the script from booter-windows/bin/applicationName.bat on Windows without worrying about long classpaths now.

Comments (3)

  1. jonas - Reply

    November 22, 2009 at 2:47 pm

    What about just using the assembly plugin with the the jar-with-dependencies output descriptor which will explode all dependencies for you inside the assembled jar? What's the point of having externalized dependencies in separate .jar files? If you need to keep configuration files external just add the "etc" directory to the class path, e.g. java -cp ./etc:myproject-jar-with-dependecies.jar com.MainClass

  2. ShriKant Vashishtha - Reply

    November 23, 2009 at 5:16 pm

    assembly plugin can be another way to get away with the long classpath issue. However many people may not want to explode all dependencies in one single jar because of several reasons. The solution mentioned works for them.

  3. Alexander - Reply

    December 21, 2009 at 1:46 pm

    Looks like you reinvented the wheel

Add a Comment