A Detour on the Road to Maven

I’ve been progressing quite nicely along my Road to Maven, that was until today when I tried to take a detour.

My preferred way of launching a Java application is to build a class path using a lib/*.jar file matcher. Maven makes setting up my lib directory with all the project’s dependent jars very easy by using the Maven Dependency Plugin:

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-to-lib</id>
            <phase>package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>${lib.dir}</outputDirectory>
              <includeScope>compile</includeScope>
              <includeScope>runtime</includeScope>
            </configuration>
          </execution>
        </executions>
      </plugin>

In collaboration with the Maven Ant Plugin that copies the application jar:

      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-jar</id>
            <phase>package</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <copy
                  file="${project.build.directory}/${project.build.finalName}.jar"
                  toDir="${lib.dir}" />
              </tasks>
            </configuration>
          </execution>
          <execution>
            <id>clean</id>
            <phase>clean</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <delete dir="${lib.dir}" />
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>

(and also performs the clean)

This worked great and I was able to run my application after each mvn package command.

I’m using Eclipse and m2e and I decided I could save some time by changing the Eclipse Default Output Folder to lib/myapp.jar (yes a folder, not a jar, called myapp.jar), and this would save the need to run Maven after every code change that I wanted to see in my application.

Unfortunately changing the output folder was a short cut too far for m2e and it punished me by removing my application classes from the class path so I could no longer run any tests from Eclipse. It took a frustrating hour of trying things before I happened across the answer in a Jira ticket. m2e ignores the eclipse setting for Output Folder when building a Launch Configuration and uses the default target/classes folder regardless.

I don’t need m2e! I’ll just use a standard Eclipse project and add the contents of my lib directory manually to the class path. Of course I’d forgotten the tests. What I need, said my Ant brain, is a target with two tasks. One that copies the compile and runtime scope dependencies to the lib directory and another that copies the test scope dependencies to a test/lib directory.

One goal – two configurations in Maven? Maven says no. The only way is to use profiles. It looks like this.

  <profiles>
    <profile>
      <id>copy-to-lib</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <configuration>
              <outputDirectory>${lib.dir}</outputDirectory>
              <includeScope>compile</includeScope>
              <includeScope>runtime</includeScope>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
    <profile>
      <id>copy-to-test</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <configuration>
              <outputDirectory>${test.lib.dir}</outputDirectory>
              <includeScope>test</includeScope>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

Which are run with a mvn dependency:copy-dependencies -Pcopy-to-lib followed by a mvn dependency:copy-dependencies -Pcopy-to-test.

So far so good, except that the copy-to-test copies too much because of transitive dependencies and with <excludeTransitive>true</excludeTransitive> it copies too little!

By now I’ve spent most of the afternoon lost in the Maven jungle and I just need to cut my losses and get back on track. Back on the road again. In a few minutes I’d changed my application class path to be target/classes:target/lib/*.jar and I’d gone back to m2e by deleting the eclipse project (but not the source) and the .classpath and .project files and used the File -> Import -> ExistingMavenProject. Safe again.

I’m learning that Convention over Configuration means conforming, and conforming means you have to learn the rules. Ignorance is no excuse in the eyes of the Maven. Leave the road and you will be punished.

Comments are closed.