nosewheelie

Technology, mountain biking, politics & music.

Deploying a Maven artefact without a repository using Ant

without comments

You know the drill, you’re working on an open source project (or two), and users start demanding Maven support. Why you ask yourself? Why, would anyone want to use Maven. But then, they assure you that it’s not by choice. That yes, they know Maven sucks, that it makes the simple hard and moderate impossible. But they’ve been forced to use it, and they’d like help. Now you know Ant has problems, but Maven? Seriously?

Seriously. So you decide to add in Maven support. After all, you’re running the project for the people, and having Maven support would help adoption, so it’s all good (and you get to write lame blog posts too!).

Now here’s where the fun begins…

The problem is that you’re using a tool other than Maven to build the project(s), and you have no Maven repository into which to deploy your project artefacts. The example I’ll show is using Google Code, but this could be any Subversion service, the process should be similar - with the exception of the SVN commands - for exposing a repository over HTTP. I’m also using Ant as the build tool, again, this should be replicable in other tools.

To save you the hassle of find them yourself, here’s the two links you’ll need. Firstly, here’s the Guide to uploading artifacts to the Central Repository, and secondly, here’s the Maven Deploy Plugin Usage overview. Be warned though, like most things Maven, this documentation is pretty scarce, for example what URL schemes does the deploy-file plugin take? Will it accept svn://... URLs? Who knows, but this is what worked for me.

All in all, the process is fairly simple once you figure it all out. The thing we’ll be building is called a Maven Bundle, and is simply a jar (zip) file containing certain content. We’ll then “deploy” this into a local “repository”, creating the necessary directory structure.

  1. Ensure your project is creating three artefacts, a main jar, a sources jar, and a javadoc jar. The last two (source & javadoc) are optional, however they seem to be standard for most Maven bundles, and Maven aware tools (such as IntelliJ) will automatically link the source and javadoc jars to the main classes.

    As detailed in the guide, they need to be named as follows:

    ${artifactId}-${version}.jar
    ${artifactId}-${version}-sources.jar
    ${artifactId}-${version}-javadoc.jar
    

    Note that if your project also contains Scala code, and Scaladoc (as Functional Java & Instinct do), I’ve not looked into how to get these hooked up into the Maven bundle. Lift is using Maven so I guess it’s possible…

  2. Create a Maven POM file, the guide linked above shows you the minimum you’ll need to get started (there are rules in the guide, that tell you what you must and can’t have in a POM for deployment). Here’s Instinct’s POM:

    <?xml version="1.0"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelversion>4.0.0</modelversion>
        <groupid>com.googlecode.instinct</groupid>
        <artifactid>instinct-core</artifactid>
        <packaging>jar</packaging>
        <name>Instinct Core</name>
        <description>Instinct Behaviour Driven Development (BDD) Framework</description>
        <version>0.1.9</version>
        <url>http://instinct.googlecode.com/</url>
        <licenses>
            <license>
                <name>The Apache Software License, Version 2.0</name>
                <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
                <distribution>repo</distribution>
            </license>
        </licenses>
        <scm>
            <url>http://instinct.googlecode.com/svn/</url>
        </scm>
        <dependencies>
          ...
        </dependencies>
    </project>
    
  3. You’ll now need to build your bundle, the contents should look like this:

    pom.xml
    ${artifactId}-${version}.jar
    ${artifactId}-${version}-sources.jar
    ${artifactId}-${version}-javadoc.jar
    

    Here’s some Ant XML showing how this can be done:

    <property name="build.dir" value="build"/>
    <property name="release.dir" value="${build.dir}/release"/>
    <property name="maven-bundle.jar" value="instinct-0.1.9-bundle.jar"/>
    <target name="-maven-bundle" depends="-jar,-sources-jar,-javadoc">
        <mkdir dir="${release.dir}"/>
        <jar destfile="${release.dir}/${maven-bundle.jar}">
            <fileset dir="${basedir}" includes="pom.xml"/>
            <fileset dir="${build.dir}" includes="${project.jar}"/>
            <fileset dir="${build.dir}" includes="${sources.jar}"/>
            <fileset dir="${build.dir}" includes="${javadoc.jar}"/>
        </jar>
    </target>
    

    And here’s what it builds:

    $ unzip -l build/release/instinct-0.1.9-bundle.jar Archive:  build/release/instinct-0.1.9-bundle.jar
      Length     Date   Time    Name
     --------    ----   ----    ----
            0  08-08-08 22:48   META-INF/
           98  08-08-08 22:48   META-INF/MANIFEST.MF
         2136  08-08-08 12:36   pom.xml
       378565  08-08-08 22:48   instinct-0.1.9.jar
       315685  08-08-08 15:28   instinct-0.1.9-sources.jar
       693521  08-08-08 22:48   instinct-0.1.9-javadoc.jar
     --------                   -------
      1390005                   6 files
    
  4. Create a spot in your svn for the maven artefacts, this will become your “repository”. I added mine here: http://instinct.googlecode.com/svn/artifacts/maven/.
  5. Take a checkout of this directory, you’ll need to store it somewhere local to the project, you’ll need its path later. I checked mine out into a peer directory to my project, so from the project it’s accessed as “../artifacts/maven”. This will be a local version of your repository.
  6. You’ll now need to “deploy” your classes jar into this local repository, as follows (documented in the deploy plugin usage):

    $ mvn deploy:deploy-file -Durl=file://../artifacts/maven -DrepositoryId=local-svn-artifacts -Dfile=instinct-core-0.1.9.jar -DpomFile=pom.xml
    

    I tried to get the deploy-file goal to deploy to a remove SVN directly with no luck, YMMV. Again, the documentation is lacking here.

    Note that the deploy-file goal does not accept bundles, it needs to be just your classes jar. If you want your sources and javadoc there as well, you’ll need to copy and hash them also (see Ant target below).

    Alternatively, you can have your build tool do this for you:

    <property name="maven-bundle.jar" value="${project.shortname}-${project.version.full}-bundle.jar"/>
    <property name="maven-repo.dir" value="${basedir}/../artifacts/maven"/>
    <property name="maven-repo-release.dir" value="${maven-repo.dir}/com/googlecode/instinct/instinct-core/${project.version.full}"/>
    <exec dir="${basedir}" executable="mvn" failonerror="true" os="Mac OS X,Linux">
      <arg value="deploy:deploy-file"/>
      <arg value="-Durl=file://${maven-repo.dir}"/>
      <arg value="-DrepositoryId=local-svn-artifacts"/>
      <arg value="-Dfile=${build.dir}/${project.jar}"/>
      <arg value="-DpomFile=${basedir}/pom.xml"/>
    </exec>
    <copy file="${release.dir}/${maven-bundle.jar}" todir="${maven-repo-release.dir}"/>
    <copy file="${build.dir}/${sources.jar}" todir="${maven-repo-release.dir}"/>
    <copy file="${build.dir}/${javadoc.jar}" todir="${maven-repo-release.dir}"/>
    <checksum file="${maven-repo-release.dir}/${sources.jar}" algorithm="MD5" forceOverwrite="yes" fileext=".md5"/>
    <checksum file="${maven-repo-release.dir}/${sources.jar}" algorithm="SHA" forceOverwrite="yes" fileext=".sha1"/>
    <checksum file="${maven-repo-release.dir}/${javadoc.jar}" algorithm="MD5" forceOverwrite="yes" fileext=".md5"/>
    <checksum file="${maven-repo-release.dir}/${javadoc.jar}" algorithm="SHA" forceOverwrite="yes" fileext=".sha1"/>
    
  7. Check the structure of the local repository, it should look something like this:

    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9.jar
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-javadoc.jar.sha1
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-sources.jar
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9.jar.md5
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-javadoc.jar
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-sources.jar.md5
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-bundle.jar
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-javadoc.jar.md5
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9.jar.sha1
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9.pom
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9.pom.md5
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9.pom.sha1
    maven/com/googlecode/instinct/instinct-core/0.1.9/instinct-core-0.1.9-sources.jar.sha1
    maven/com/googlecode/instinct/instinct-core/maven-metadata.xml
    maven/com/googlecode/instinct/instinct-core/maven-metadata.xml.md5
    maven/com/googlecode/instinct/instinct-core/maven-metadata.xml.sha1
    
  8. If you’re happy with the local repository, commit it into your (svn) repository. Instinct’s 0.1.9 release was deployed into this URL: http://instinct.googlecode.com/svn/artifacts/maven/com/googlecode/instinct/instinct-core/0.1.9/.
  9. You’re now done with the technical side of building a bundle. You’ll then need to log a JIRA issue for the Maven developers to upload your bundle (following the instructions in the guide). Then wait, and hope… Here’s the Instinct 0.1.9 request.

There is another way that users can download your Maven artefacts without them needing to be deployed to the central Maven repository, it’s detailed in the introduction to Maven repositories. Basically client projects of your project need to add a repository to their POM as follows (substituting the ID and URL belong for that of your repository):

<project>
  ...
  <repositories>
    <repository>
      <id>instinct-repository</id>
      <url>http://instinct.googlecode.com/svn/artifacts/maven/</url>
    </repository>
  </repositories>
  ...
</project>

Note that the process I’ve described of uploading to the central repository process is a manual one. Looking at the Maven issues for bundle uploads, it appears that if you don’t provide rsync or ssh access to your repository, you need to make manual upload requests. Others who’ve followed a similar process to this one are asking whether the Maven sync can happen automatically, to which I’ve not seen an answer. The documentation isn’t overly clear on this last point. A friend of mine asked on the Maven IRC channel yesterday and was told syncing over HTTP “just works”, though this isn’t evident by the details in the guide, the issues being reported nor the list of automatically synced repositories.

For those interested, these instructions were gleaned from looking at other projects that deploy Maven artefacts, including GWT-Maven and the Struts 2 Maven Plugin repository.

Written by Tom Adams

August 11th, 2008 at 8:25 am

Posted in Functional, Instinct, Java

Tagged with , ,

Leave a Reply