Talking about my stuff

September 1, 2011

Groovy with Gradle and Artifactory

Filed under: Uncategorized — agoodspeed @ 9:11 pm

I am trying to change my Ant builds to Gradle for various reasons. Gradle has a lot of advantages (just ask Hans Dockter if you don’t agree :-) ), so I did not want to simply convert my Ant builds or run them wrapped by Gradle. I wanted to do it right.

Well, I’m not sure whether I have or not, but I have learned a lot that could be helpful to others, or even to me when I get separated from this effort by some other distraction and have to come back to it.

For reference, I am using Groovy 1.7.5 and Gradle 1.0-milestone-1. Gradle 1.0-milestone-3 is currently available and hopefully 1.0 will be released soon so that I can go live with a real release. Unfortunately milestone 3 (and 2) has issues running on AIX which is my build environment (http://issues.gradle.org/browse/GRADLE-1479).

Dependency Management

We were not using dependency management which had us traveling down the road to jar hell, so this is one area that I had to come to terms with. I did not know much about it, and really do not still, but I got something working that appears to take care of my needs; hopefully that giddy feeling will last at least a little while.

Artifactory

Artifactory seemed to be a popular product to get started with, due in no small part to the fact that it has a free (“Open Source”) version; I am running Artifactory 2.3.4. There is also a plugin for Gradle which is helpful; I am using version 2.0.6+ (a snapshot build) of that, which fixes several problems I ran into (https://issues.jfrog.org/jira/browse/GAP-93, https://issues.jfrog.org/jira/browse/GAP-103). For completeness, I also inspired https://issues.jfrog.org/jira/browse/RTFACT-4329.

Out of the box Artifactory has a number of repositories defined. The repositories are of three different kinds – local, remote, and virtual; I’m not fired up about these names but that is what Artifactory calls them so I might as well too.

  • A local repository holds artifacts that you control, whether they are your code potentially published there by your build(s), or they are someone else’s code that you need to manage yourself.
  • A remote repository is related to some other accessible repository (such as Maven Central) and caches artifacts from the related repository when they are requested.
  • A virtual repository is a grouping of local and/or remote repositories that can be read from as one, essentially defining a new namespace that spans several others.
Local Repositories

There are six that come defined with Artifactory, but since I only use two I will mostly limit my discussion to them. Note that local repositories are really the only ones you can deploy artifacts to.

  • libs-release-local is where I publish my artifacts. So as part of my Gradle build I want to put significant resulting artifacts here.
  • ext-release-local is where I deploy artifacts that are provided by someone else but for which there is no existing remote repository I can use to resolve them. For me that is (so far) only the DB2 driver jar file, as I could not find a repository out there that I could retrieve it from.

There is also a plugins-release-local repository whose use is not immediately obvious to me. And for each of these “-release-local” repositories there is a “-snapshot-local” repository, which is presumably used for sticking nightly builds or similar “snapshots” that are supported but need to be isolated from approved releases. The difference is the setting of “Handle Releases” and “Handle Snapshots” in the repository settings.

Okay, so I use these two local repositories. I bet it would be useful to know how you get stuff into them!

  • libs-release-local receives releases through my Gradle build when the artifactoryPublish task is run. I will go into more depth later in this post, but for now it is worth noting that this is a task introduced by the Artifactory plugin in its “publish” closure.
  • ext-release-local receives artifacts most directly using the “Deploy” tab of the Artifactory console – http://{host}[:{port}]/artifactory/webapp/deployartifact.html. When you select the artifact and press “Upload!” you then select the local repository you want to receive it (ext-release-local for me) and then give it the target path you want to use for it under that repository. Note that if you select “Deploy as Maven Artifact” the target path will not be editable, but will be determined by the entries you make for the various Maven attributes. When you are done press “Deploy Artifact” and it should return with a message indicating that it was deployed successfully and the path to the deployed artifact.

If you did not deploy it as a Maven Artifact and have a POM deployed or generated for it you will need to go through the same process to deploy the configuration file, such as an Ivy XML file.

Remote Repositories

Remote repositories are at the heart of dependency management. If a repository already exists with the dependency you have (and most do) then you just need to make the reference to it and you have a proxy to it in Artifactory. It is a proxy in that it should be empty initially; when you retrieve a dependency Artifactory brings it down into the remote repository cache (if it is not there already) and then hands it to you.

Every remote repository automatically gets a second name space, so where you have the “repo1″ remote repository you also have the “repo1-cache” repository. The latter can only be used for retrieval; it will not cause something that is not already in the cache to be downloaded. A reference to the former will cause the artifact(s) to be brought down to the cache.

It is worth noting that a remote repository can be flagged as “Offline” which prevents the main name space from attempting to download artifacts not already in the cache. This represents an additional level of potential control for what gets into the repository. To me it is crazy to let stuff come into the cache from a remote repository without control, so I expect remote repositories to be offline under normal conditions.

Maven and Ivy

Much of my pain was related to mixing repository types. I quickly realized than almost everything in the way of a remote repository uses Maven. However Maven is one of the restrictive elements that Gradle is intended to liberate you from, so I had no interest in adopting it myself and preferred to use Ivy for artifacts under my control. At the same time I do not want my build to be concerned about how the (remote) repository is organized.

Experts readily advised that this is no problem using Artifactory. What took quite a while longer was to find out that it is no problem if you are using Artifactory Pro Version; it is a big problem if you are running the Open Source version as I was. So at this point I am nearing the end of my evaluation period for the Pro Version and am daunted by what I have yet to try that I will need in order to replace all of my builds with Gradle while using Artifactory.

With the Pro Version Artifactory includes the “Repository Layouts” add-on, which allows a request in one form to be mapped to a different form based on the underlying local or remote repository layout. So for my local repositories I use the “gradle-default” layout – effectively Ivy – and for my remote (Maven) repositories I use the “maven-2-default” layout (or whatever is required). This seems to get me to the promised land.

One thing to note though is that the UI does not allow you to change the Repository Layout for an existing repository. This makes some sense if there are artifacts in the repository, at least in a form that conflicts with the new layout, but for me that was not the case; it is very confusing since the default repositories seem comprehensive as long as you want to use Maven, yet using Maven is dubious if you have a choice in the matter.

The “safe” option is to set up new repositories effectively replacing the default ones only with a different layout; I availed myself to the “dangerous” option of going into Admin -> Advanced -> Config Descriptor and modifying the <repoLayoutRef> elements for those repositories. It seemed to work fine, which was kind of novel in my experience to this point.

Gradle

I set up a simple test project, largely because Artifactory support kept asking for one to demonstrate the various bugs I was running into. It is called gradleart to represent the attempt to use Gradle with Artifactory. The project (such that it is) is written in Groovy.

gradleart

There is only one class in the project proper, and that is the very simple com.mydomain.gradle.NothingMuch.groovy located under src/groovy in the project.

package com.mydomain.gradle

import org.apache.log4j.*
import com.ibm.db2.jcc.DB2Driver

class NothingMuch {
  static {
    Logger.getLogger("NothingMuch").log(Priority.FATAL, "Just saying.")
    println "Just said, 'Just saying.'"
    DB2Driver.newInstance()
  }
}

In addition there is only one test class, and that is the very simple com.mydomain.tests.gradle.NothingMuchTests.groovy located under test/groovy in the project.

package com.mydomain.tests.gradle

import com.mydomain.gradle.NothingMuch

class NothingMuchTests extends GroovyTestCase {
  void testClassLoad() {
    try {
      NothingMuch.classLoader.loadClass('com.mydomain.gradle.NothingMuch')
    } catch (Throwable t) {
      assert false, "Failed to load class com.mydomain.gradle.NothingMuch with Throwable ${t}"
    }
  }
}

This leads to the build.gradle file.

apply plugin: 'groovy'
apply plugin: 'artifactory'

// "name" is not settable in the build script, so assert that it has the correct value instead.
// This actually gets set in settings.gradle as:
// rootProject.name = 'gradleart'
assert name == 'gradleart'

// The generated artifact is part of the "com.mydomain" group (or "organisation").
group = 'com.mydomain'

artifactory {

  contextUrl = 'http://artifactory.mydomain.com/artifactory'

  publish {
    repository {
      repoKey = 'libs-release-local'
      // The publishuser and publishpassword authentication credentials are set in gradle.properties.
      //    publishuser=someuser
      //    publishpassword=somepassword
      username = publishuser
      password = publishpassword
      ivy {
        ivyLayout = '[organization]/[module]/ivy-[revision].xml'
        artifactLayout = '[organization]/[module]/[revision]/[module]-[revision](-[classifier]).[ext]'
        mavenCompatible = false
      }
    }
    defaults {
      publishPom = false
    }
  }
  resolve {
    repository {
      repoKey = 'libs-release'
      ivy {
        ivyLayout = '[organization]/[module]/ivy-[revision].xml'
        artifactLayout = '[organization]/[module]/[revision]/[module]-[revision](-[classifier]).[ext]'
        mavenCompatible = false
      }
    }
  }
}

dependencies {
  groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.5'
  compile group: 'junit', name: 'junit', version: '4.8.1'
  compile group: 'log4j', name: 'log4j', version: '1.2.15'
  compile group: 'com.ibm', name: 'db2jcc', version: '9'
}

sourceSets {
  main {
    groovy {
      srcDir 'src/groovy'
    }
  }
  test {
    groovy {
      srcDir 'test/groovy'
    }
  }
}

uploadArchives {
  doFirst {
    assert version && version != 'unspecified', "Version not specified. Specify as:\n\tgradle -Pversion=1.0 [task]\n"
  }
  uploadDescriptor = true
}
Advertisement

5 Comments »

  1. I’m intrigued why you think you need Artifactory Pro. I converted our Ivy filesystem-based repo to Artifactory and switched all our 3rd-party dependencies to be proxied from Maven Central, but kept the Ivy layout for our locally built artifacts. We have no problems mixing the Ivy and Maven schemes together.

    We also use Gradle in some areas and I haven’t worked out yet what the Artifactory Gradle plug-in is good for – we publish to Artifactory with standard Gradle.

    Comment by stevendick — September 5, 2011 @ 5:18 pm | Reply

    • I have been talking with Artifactory support both directly and through the email group throughout my efforts. Much of the information I have received has been from Yoav Landman who appears to have primary responsibility for the plugin. This is what he had to say regarding the purpose of the plugin.

      Please note that the plugin is separate from Artifactory – it is offered as a complementary method for integrating with Artifactory, and it allows one to get all the benefits of Artifactory’s Build Integration for Gradle builds – traceable artifacts and environment with build correlation, promotion, etc. – without using a CI server (this project is on github under Apache v2 with all our other build-info integrations). Apart from build-info integration, the plugin also offers optimized deployment, but if you don’t need any of these features you can always use a standard Gradle build against Artifactory without the plugin.

      Regarding the need for Artifactory Pro he told me this.

      Layout translation between repositories is indeed a feature of the Pro version and works independently of the Artifactory Gradle plugin. Using this feature you can resolve through a single virtual repository artifacts which reside in underlying repositories that are using different layouts. So, in your case, for example, by using repository layouts you’d be able to resolve through the “libs-release” virtual repo files from: 1) remote and local repositories that are/proxy Maven repositories and use the default maven-layout, 2) local repositories, such as “ext-release-local” that use the gradle-layout and host artifacts such as “com.ibm/db2jcc/ivy-9.xml”.

      If I did not use this I think I would effectively have to have knowledge of the underlying repository format represented in my builds which seems fragile to me. Perhaps I am wrong. I would welcome any input you might have on this as I am new to just about everything involved here.

      Comment by agoodspeed — September 6, 2011 @ 1:40 pm | Reply

  2. I wouldn’t make your journey with Gradle more complex with plugins unless you find a real need. Less moving parts makes it easier to learn.

    Here’s our list of repos in Gradle that mixes Maven and Ivy repos:

    repositories {
    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
    name = ‘infonic-hedgesphere’
    def repo = ‘http://artifactory:8081/artifactory/snapshot-local’
    addIvyPattern “$repo/[organisation]/[module]/[revision]/ivy-[revision].xml”
    addArtifactPattern “$repo/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]”
    descriptor = ‘optional’
    checkmodified = true
    }

    add(new org.apache.ivy.plugins.resolver.IBiblioResolver()) {
    name = ‘ibiblio’
    def repo = ‘http://artifactory:8081/artifactory/repository’
    m2compatible = true
    }

    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
    name = ‘infonic-3rd-party’
    def repo = ‘http://artifactory:8081/artifactory/repository’
    addIvyPattern “$repo/[organisation]/[module]/[revision]/ivy-[revision].xml”
    addArtifactPattern “$repo/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]”
    descriptor = ‘optional’
    checkmodified = true
    }
    }

    Comment by Steven Dick — September 6, 2011 @ 2:05 pm | Reply

    • Great! Thanks for the example. I will see what that does for me and report back/update my post.

      Do you see a usefulness to the publish capability of the plugin versus a pure Gradle approach? That does not require the Pro version so I don’t know that there is a penalty for using it. I would be interested in your Gradle approach regardless if you would not mind sharing it.

      Comment by agoodspeed — September 6, 2011 @ 3:09 pm | Reply

  3. I’d start with Gradle’s publishing support, then look at the Artifactory plugin if you find you are missing features. You already have a learning curve with Gradle and Artifactory, try to minimise the moving parts until you are more comfortable.

    Comment by Steven Dick — September 7, 2011 @ 11:48 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.