Skip to content

Artifactory and Grails

A while back, a colleague asked me how Artifactory and Grails work together.

I couldn’t point him to a really good resource on the ‘web then and I didn’t have a ready answer for him. But that was then, this is now. Let me tell you a story.

There are three parts.

Are you sitting comfortably? Then I’ll begin…

Artifactory As A Repository Proxy

Artifactory offers a Default Virtual Repository. It is simplest to just use this.

Repositories are configured in BuildConfig.groovy.

All you need is:

repositories {
    inherits true // Whether to inherit repository definitions from plugins

    mavenRepo id:"Artifactory" , url:"http://localhost:8081/artifactory/repo"
}

No other repositories need to be established here; Artifactory can deal with everything.

If you need special repositories , tell artifactory to add them to its list of Remote Repositories, rather than adding a new repository directly to Grails.

To borrow a slogan: “it just works.”

Publishing To Artifactory

You cannot publish to Artifactory’s Default Virtual Repository. Nor should you need or want to. It is easy and appropriate to create a new Local Repository for your work. Like this:

Once you have your destination repository up and running, go back to BuildConfig.groovy and turn on the magic. Add the following to the top level (ie not nested within anything else) of the file:

grails.project.repos.default = "AT"

grails {
    project {
        repos {
            AT {
                url = "http://localhost:8081/artifactory/AT/"
                username = "bob"
                password = "MyUnguessablePassword"
            }
        }
    }
}

grails.project.groupId = "org.bob"

All pretty clear, I hope.

It is not strictly necessary to define a default repository in BuildConfig.groovy; one can be specified on the command line (as ‐‐repository=AT) but this can make life a little easier…and who doesn’t want an easier life, eh?

Since we are working in a Maven-oriented world, it’s fairly important to set the groupId. The associated artifactId and version information is picked up from the application.properties file.

Grails’ release plugin does the heavy lifting, so add it to BuildConfig.groovy, thus:

plugins {
    ...

    build ":release:3.0.1"
}

All that’s needed now is to run:

grails maven-deploy

And Bob’s your uncle, as they say.

The end result:

Adding Manifest Data

It’s extremely common to want/need to put extra information into an application’s META-INF/MANIFEST.MF file.

In a Grails application, this is most easily done by hooking into the build lifecycle in scripts/_Events.groovy.

I’ve covered this before, but below is another take on the solution that attempts to pick up build information from a CI server such as Jenkins) or from a buildNumber.properties file, if such exists:

import static grails.build.logging.GrailsConsole.instance as CONSOLE

eventCompileStart = { kind ->
    grailsSettings.config['BUILD_NUMBER'] = determineBuildNumber()
}

// NB: Possible race condition here, but ignore for now
//     Assumes BUILD_NUMBER is an integer
private determineBuildNumber() {
    def buildNumber = 0
    def bnp = System.getenv('BUILD_NUMBER')
    if (bnp)
        buildNumber = val(bnp)
    else
        try {
            def bnPropsFile = new File(grailsSettings.baseDir, "buildNumber.properties")
            if (bnPropsFile.isFile() && bnPropsFile.canRead()) {
                def properties = new Properties()
                bnPropsFile.withReader { r ->
                    properties.load(r)
                }
                buildNumber = val(properties.getProperty('BUILD_NUMBER'))
                if (bnPropsFile.canWrite()) {
                    properties.setProperty('BUILD_NUMBER', "${buildNumber + 1}")
                    bnPropsFile.withWriter { w ->
                        properties.store(w, null)
                    }
                }
            }
        }
        catch (e) {
            CONSOLE.warn("Exception when reading/writing 'buildNumber.properties'; assuming '0': " + e.getMessage())

            buildNumber = 0
        }

    CONSOLE.info("Using BUILD_NUMBER=${buildNumber}")

    buildNumber
}

private val(String bn) {
    try {
        Integer.parseInt(bn)
    } catch (ignore) {
        CONSOLE.warn("Could not parse presented BUILD_NUMBER '${bn}' as Integer; assuming '0'")
        0
    }
}

eventCreateWarEnd = { warName, stagingDir ->
    ant.jar(destfile: warName, update: true) {
        manifest {
            attribute(name: 'BUILD_NUMBER', value: grailsSettings.config['BUILD_NUMBER'])
            attribute(name: 'Built-By', value: System.properties['user.name'])
            attribute(name: 'Build-Host', value: InetAddress.getLocalHost().getHostName())
        }
    }
}

Refer back to the previous picture to see the results of all this.

As an aside, I really appreciate Groovy’s easy integration of Ant. I probably could have used Ant to better effect in determineBuildNumber() but that may be the subject of a later story.

Now you have it, Grails and Artifactory, sitting in a tree…not quite K.I.S.S.I.N.G., but getting together pretty closely.

Tags: , ,

C, Java Enterprise Edition, JEE, J2EE, JBoss, Application Server, Glassfish, JavaServer Pages, JSP, Tag Libraries, Servlets, Enterprise Java Beans, EJB, Java Messaging Service JMS, BEA Weblogic, JBoss, Application Servers, Spring Framework, Groovy, Grails, Griffon, GPars, GAnt, Spock, Gradle, Seam, Open Source, Service Oriented Architectures, SOA, Java 2 Standard Edition, J2SE, Eclipse, Intellij, Oracle Service Bus, OSB