Content tagged: maven

Maven: Add Local JAR Dependency to Classpath

ed8f6b7f55088c791fa46a57461eb84fafc6faa5

Sun Sep 27 16:53:05 2015 -0700

I’ve been getting back into Maven lately, converting the build system behind several of my personal projects on GitHub into something a little more sane and well-travelled. For reasons yet-to-be formally discussed, I’ve embarked on a mass migration away from SBT — albeit, I still have a number of published projects backed by SBT.

SBT Rant

Although I’m still using it sparingly, SBT has left a bitter taste in my mouth. The long-and-short of it is that I’m tired of everything in SBT being a complicated puzzle backed by poor documentation — I just want to get stuff done. I wish I had the countless hours of my life back that I spent figuring out how to accomplish very specific (yet seemingly common) tasks with SBT. Build definitions do not need to be written in Turing-complete languages, and in my humble opinion, SBT is a perfect example of what not to do.

</rant>

Maven

I was refactoring a personal project to use Maven the other day, and stumbled across a need to “add a local JAR to my classpath”. That is, I have a .jar file on disk from many moons ago, that is not in any public Maven repository yet I need to add it to the compile scope of my project.

Bad: The system Scope

A quick search of the Interwebz clearly calls out a worst practice: using the Maven system scope.

The system scope was designed to deal with “system” related files — files sitting in some fixed location, like Java’s core rt.jar. To discourage bad behavior, the Maven contributors intentionally refused to make pathname expansion work correctly in the context of the <systemPath> tag in the system scope. In other words, ${basedir}/lib/foo.jar below will not resolve:

<dependencies>
    <!-- WRONG: DON'T DO THIS -->
    <dependency>
        <groupId>com.foo</groupId>
        <artifactId>bar</artifactId>
        <version>1.0</version>
        <scope>system</scope>
        <systemPath>${basedir}/lib/bar-1.0.jar</systemPath>
    </dependency>
</dependencies>

Don’t do this.

Good: Use a Local Repository

The best practice is to “publish” the .jar file to a local Maven repository nested within the project. Yes, you read that correctly, publish the .jar to a ~/.m2 like repo within your project that is checked into SCM!

Here’s how…

On disk, your project probably looks something like this:

project/
  src/main/java
  src/main/resources
  src/test/java
  pom.xml

1) Create a lib directory in your project root — this lib directory will act as a local Maven repository within the project.

cd project
mkdir lib

2) Download the .jar file to disk, and use mvn to publish the .jar to the lib directory.

In this example, I’m publishing the Gagawa library I wrote and open-sourced many years ago.

mvn org.apache.maven.plugins:maven-install-plugin:2.5.2:install-file \
  -Dfile=~/Desktop/gagawa-1.0.1.jar \
  -DgroupId=com.hp \
  -DartifactId=gagawa \
  -Dversion=1.0.1 \
  -Dpackaging=jar \
  -DlocalRepositoryPath=lib

If all went well, you can find your artifact published inside of lib.

project$ find lib
lib
lib/com
lib/com/hp
lib/com/hp/gagawa
lib/com/hp/gagawa/1.0.1
lib/com/hp/gagawa/1.0.1/gagawa-1.0.1.jar
lib/com/hp/gagawa/1.0.1/gagawa-1.0.1.pom
lib/com/hp/gagawa/maven-metadata-local.xml

Note the structure here mimics what you’d find in ~/.m2.

3) Now, in your pom.xml, declare the lib directory in your project a Maven repository.

<repositories>
    <repository>
        <id>my-local-repo</id>
        <url>file://${basedir}/lib</url>
    </repository>
</repositories>

4) And lastly, in your pom.xml declare a dependency on the local .jar like you would for any other classpath dependency.

<dependencies>
    <dependency>
        <groupId>com.hp.gagawa</groupId>
        <artifactId>gagawa</artifactId>
        <version>1.0.1</version>
    </dependency>
</dependencies>

At runtime, Maven will consult the local repo at ${basedir}/lib in addition to ~/.m2 and any other remote repositories you have defined.

Ship it!

Fail your Build on Java Compiler Warnings

2c8127e956e463d7dd061b870cc9fce3d256f974

Sat Sep 12 16:37:41 2015 -0700

I hate seeing compiler warnings in code, and anyone who argues that ignoring them is a fine software engineering policy, should be swiftly relieved of their position. Warnings call out mistakes, and occasional blatant stupidity that should not be ignored — heck just Google “pay attention to compiler warnings” for some fun anecdotes. Folks in software are generally helpful, and compiler writers don’t inject annoying warnings because they are mean-spirited. Instead, people want to help, and consequently, compiler warnings are there to help. I’ve personally worked on several stacks with literally thousands of compiler warnings peppered throughout the code — it’s miraculous that some of those applications worked at all.

To combat warning hell, I’ve made it a personal best practice to do two things:

  1. In mature code bases, never introduce more warnings and never ignore existing warnings I happen to stumble across. Ever. If I see a warning in an area of code that I’m working on, I’ll clean it up — no excuses.
  2. In new projects, starting from scratch, set my build tool to immediately fail the build on any warning. In other words, treat warnings as compilation errors!

The latter is surprisingly easy and very effective at forcibly setting a high bar before entropy can gain a foothold.

Here’s how, with a few popular build tools:

Ant

If you’re still using Ant, set a series of <compilerarg> tags in your <javac> tasks. Of course, this goes in your build.xml:

<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="libraries">
  <compilerarg value="-Xlint:all"/>
  <compilerarg value="-Xlint:-processing"/>
  <compilerarg value="-Xlint:-serial"/>
  <compilerarg value="-Werror"/>
</javac> 

Maven

Using the maven-compiler-plugin add a few <compilerArgs> to your configuration within your pom.xml:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.3</version>
  <configuration>
    <source>1.8</source>
    <target>1.8</target>
    <compilerArgs>
      <arg>-Xlint:all</arg>
      <arg>-Xlint:-processing</arg>
      <arg>-Xlint:-serial</arg>
      <arg>-Werror</arg>
    </compilerArgs>
  </configuration>
</plugin>

Gradle

To fail the build on any compiler warning, in main source and in test source, set this in your build.gradle:

tasks.withType(JavaCompile) {
  options.compilerArgs << "-Xlint:all" << "-Xlint:-processing" << "-Xlint:-serial" << "-Werror"
}

SBT

In the unlikely event that you’re building a pure Java project or Java source with SBT, set this in your project/Build.scala:

lazy val projectSettings = Defaults.coreDefaultSettings ++ Seq(
  scalacOptions ++= Seq(
    "-deprecation", "-unchecked", "-feature", "-Xlint", "-Xfatal-warnings", "-encoding", "utf8"
  ),
  javacOptions ++= Seq(
    "-Xlint:all,-processing,-serial", "-Werror", "-encoding", "utf8", "-g"
  )
)

The Scala compiler scalac equivalent to -Werror is -Xfatal-warnings (apparently).

A few notes

The magic is in -Werror which is documented here. When set, -Werror terminates compilation when warnings are found.

I’m also passing -Xlint:-processing which disables any annotation processor warnings from JARs on the compile classpath. And lastly, -Xlint:-serial disables any warnings complaining of Serializable classes that do not have an explicit serialVersionUID field. Ok, yes, certainly one could argue that ignoring complaints about a missing serialVersionUID field is dangerous, but I’ll let you be the judge.

Cheers!