Non-Java Binary Dependencies in Maven

binary-pillow Suppose you have a Java Server application, and some of the runtime binaries in that application are external to your application. Generated image files, compiled Silverlight components in your pages, or resource files which are managed by an external team.

Much like the jar files used by your application, these external binaries can be seen as dependencies, with versions. This blogpost assumes your project is built with Maven 2, because the real world isn't always a greenfield project.

Because Maven is designed around jar file dependencies, and a lot of it's internal decisions are based on file extensions, it looks like this problem can not be tackled with Maven. But there is a way to do this. It will decouple your sub-projects and make version and dependency management much better.

**Uploading the binaries files in a remote repository**

The hardest part of this problem is getting the binary uploaded to your maven repository. It seems that there is no other way than disquising it as a jar file, which only leads to confusion. But it turns out that you can, actually upload a .exe file to the repository. To do so, place a pom.xml file in the root of the project from which you want to upload the binary file. You do not have to mavenize the whole project, we only use Maven to do the upload to the repository.

Find the file you want to deploy. It can be placed anywhere but I prefer to use the target directory so it looks maven-esque. Make sure you choose the "pom" packaging type so maven does not generate a jar for you.

[sourcecode language="xml" autolinks="false" highlight="11,16,23,24" padlinenumbers="true"] com.rolfje.example executablestuff pom 1.0-SNAPSHOT Some executable component org.codehaus.mojo build-helper-maven-plugin 1.8 attach-artifacts package attach-artifact ${basedir}/target/myexecutable.exe exe [/sourcecode]

When you run "maven install", your exe file will be pushed to your local repository (more on using a remote repository further down).

Downloading the binaries files from the remote repository In the project that will depend on your binary file, you want to copy it from your repository into your target directory at the compile stage. You can do this by adding the following plugin configuration to your pom:

[sourcecode language="xml" autolinks="false" highlight="14,15,16,17,18" padlinenumbers="true"] org.apache.maven.plugins maven-dependency-plugin copy-dependency compile copy com.rolfje.example executablestuff exe true target [/sourcecode]

Multiple artifacts in a single dependency When the non-java project produces multiple files with the same extension, you will notice that the trick I just described will not work because you can only specify one file with one extension. This is because maven uses the extension to locate the exact file.

In order to deploy multiple files, it is best you bundle them in a zip and unpack them at the other end. In the non-java project, add the assembly plugin:

[sourcecode language="xml" autolinks="false" highlight="2,15,17" padlinenumbers="true"] maven-assembly-plugin 2.2-beta-2 generate-assembly package single src/maven/descriptor.xml ${artifactId} target [/sourcecode]

We use so the zipfile will always have the same name in the target directory, which will make build-helper-maven-plugin configuration easier. It also makes it easier for non-maven scripts to find the file. Upon deploying to the repository, maven will fix the filename so don't worry about that too much.

The contents of src/maven/descriptor.xml can look like this:

[sourcecode language="xml" autolinks="false" highlight="21,22" padlinenumbers="true"]

zip

false

target/binaries *.exe *.dll [/sourcecode]

In the java project, you don't need to change much. Instead of "copy", you tell the dependency plugin to unzip the file into a directory:

[sourcecode language="xml" autolinks="false" highlight="3,9,16,18" padlinenumbers="true"] org.apache.maven.plugins maven-dependency-plugin copy-dependency compile unpack com.rolfje.example executablestuff zip true target/unpacked [/sourcecode]

Deploying to a remote repository If the two builds run on two different machines, you may need an external repository. For larger projects, I'd recommend an external, locally managed repository not just for disctribution, but also for speed. Your team will have almost instant access to libraries, speeding up your (initial) builds. Installing a local jFrog Artifactory is a good choice.

To make deployment to this external repository work, you need to add the following to your non-java pom file:

[sourcecode language="xml" autolinks="false" padlinenumbers="true"]

artifactory.releases Artifactory releases http://myartifactory.example.com/artifactory/libs-release-local artifactory.snapshots Artifactory snapshots http://myartifactory.example.com/artifactory/libs-snapshots-local [/sourcecode]

If you get an error like:

Error deploying artifact: Failed to transfer file: ://myartifactory.example.com/artifactory/libs-snapshots-local/com.rolfje.example/1.0-SNAPSHOT/executablestuff-1.0-20130604.103146-1.pom. Return code is: 401

Your artifactory is probably not configured to do anonymous deployments. Make sure you can access the artifactory with a userid and password, and add those to the ~/.m2/settings.xml of the machine which will deploy the binary artifacts.

I advise to use a special "deployment user" to do this, so you can share this configuration between builds and not depend on a user changig his password. The correct way to store a password for the Artifactory is explained here.  Using the DESede encrypted password on your Artifactory profile page, the maven settings.xml file of the buildserver user can look like this:

[sourcecode language="xml" autolinks="false" padlinenumbers="true"] artifactory.releases your-repository-username {DESede}kIniw826kaluA1OPa865A== artifactory.snapshots your-repository-username {DESede}kIniw826kaluA1OPa865A== [/sourcecode]

And then, of course, you need your java project to know where to download the dependencies so add this to the pom.xml file in your binary project:

[sourcecode language="xml" autolinks="false" padlinenumbers="true"] central http://myartifactory.example.com/repo true fail true fail [/sourcecode]

If you've reached the end of this blogpost without problems, you should now be able to build your binaries, have them uploaded with correct versions to your external repository, and your Maven/Java build server will download the correct versions of these dependencies at build time. Switching back and forth between tags on your Java project will automatically fetch the correct versions of the binaries, the same way as it does for your jar dependencies.

Happy coding!