Saturday, 5 November 2011

Automated Gradle project deployment to Sonatype OSS Repository

This post will walk you through the steps to deploy your gradle project to the Sonatype OSS staging repository.

Prerequisites


Configuring your build.gradle file

First you'll need to configure your build script (build.gradle file). A full working build script of my GradleFx project can be found here:
https://github.com/GradleFx/GradleFx/blob/develop/build.gradle

These are the steps:

Apply the maven and signing plugins. The signing plugin will sign your jar and pom files, which is required for deployment to the Sonatype OSS repository.
apply plugin: 'maven'
apply plugin: 'signing'
Specify the group and version of your project:
group = 'org.gradlefx'
version = '0.3.1'
Define tasks for source and javadoc jar generation. These jars are also required for Sonatype:

task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from 'build/docs/javadoc'
}

task sourcesJar(type: Jar) {
    from sourceSets.main.allSource
    classifier = 'sources'
}

Specify your artifacts:
artifacts {
    archives jar
    archives javadocJar
    archives sourcesJar
}
Configure the signing task by specifying which artifacts to sign, in this case all the artifacts in the archives configuration. This will not yet include the pom file, which will be signed later.
signing {
    sign configurations.archives
}
Then we need to configure the generated pom file, sign the pom file and configure the Sonatype OSS staging repository.
The 'beforeDeployment' line will sign the pom file right before the artifacts are deployed to the Sonatype OSS repository.
The 'repository' part configures the Sonatype OSS staging repository. Notice the sonatypeUsername and sonatypePassword variables, these are property variables to which I'll come back to later in this post.
The 'pom.project' section configures the generated pom files, you need to specify all this information because it's required for the Sonatype repository (and Maven Central).

uploadArchives {
    repositories {
        mavenDeployer {
            beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

            repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
              authentication(userName: sonatypeUsername, password: sonatypePassword)
            }

            pom.project {
               name 'GradleFx'
               packaging 'jar'
               description 'GradleFx is a Gradle plugin for building Flex and Actionscript applications'
               url 'http://gradlefx.github.com/'

               scm {
                   url 'scm:git@github.com:GradleFx/GradleFx.git'
                   connection 'scm:git@github.com:GradleFx/GradleFx.git'
                   developerConnection 'scm:git@github.com:GradleFx/GradleFx.git'
               }

               licenses {
                   license {
                       name 'The Apache Software License, Version 2.0'
                       url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                       distribution 'repo'
                   }
               }

               developers {
                   developer {
                       id 'yennicktrevels'
                       name 'Yennick Trevels'
                   }
                   developer {
                       id 'stevendick'
                       name 'Steven Dick'
                   }
               }
           }
        }
    }
}
That's it for the configuration of your build script.

Initial setup

There are a couple of thing you need to do before you can run the build automatically, like:
  • Register your project at Sonatype
  • Create a public key pair to sign your jar/pom files
  • Create a gradle properties file
Register your project at Sonatype
First you'll need to register your project at sonatype. Follow the "2. Sign Up" and "3. Create a JIRA ticket" sections on this page: https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide
Create a public key pair
A public key pair is needed to sign your jar/pom files. To generate this we need to dive into command line. Follow the "Generate a key pair" and "Distributing your public key" sections on this page: https://docs.sonatype.org/display/Repository/How+To+Generate+PGP+Signatures+With+Maven
This will generate a key that will be stored on your computer and on sks-keyservers.net. Remember the password because there is no way to reset or restore it when you forget it.
Create a gradle properties file
Now we'll have to create a gradle.properties file which will contain the public key and sonatype login information. Because this is sensitive information it's best to store this file in your ".gradle" directory (e.g. D:\Users\MyUsername\.gradle) and not under your project. This properties file is used by your gradle build script. The signing.* properties are used and defined by the signing task, the sonatypeUsername and sonatypePassword properties are the variables we defined earlier in our build script. So first create the "gradle.properties" file under D:\Users\MyUsername\.gradle Then add the following properties to the file and change their values:
signing.keyId=A5DOA652
signing.password=YourPublicKeyPassword
signing.secretKeyRingFile=D:/Users/MyUsername/AppData/Roaming/gnupg/secring.gpg

sonatypeUsername=YourSonatypeJiraUsername
sonatypePassword=YourSonatypeJiraPassword
the signing.keyId property is the public key id, you can list your keys with the "gpg --list-keys" command. With this command you'll get an output like this:
pub 2048R/A5DOA652 2011-10-16
uid MyUid
sub 2048R/F66623G0 2011-10-16

That's it for the initial setup.

Deployment to the Sonatype OSS Repository

Once you've done all the previous steps in this post you can deploy your project with just one single command: "gradle uploadArchives"

19 comments:

  1. Excellent article. The team at Sonatype found this great.

    ReplyDelete
  2. Hey Yennick,

    Note that the Gradle team have further simplified the 'signing' plugin in m5 such that you don't need to distinguish between published, archives, and signatures configurations. See http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-5+Release+Notes#Gradle1.0-milestone-5ReleaseNotes-Improvedsigningplugin

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Do you really need to add the (packaging)jar(\packaging)? It's the default for a POM. It's not there because the Maven code that Gradle uses to generate the POM doesn't add it.

    ReplyDelete
    Replies
    1. I had the same question so i looked into it a bit, when you use jar as your package type it is excluded from the generated pom because org.gradle.api.publication.maven.internal.DefaultMavenPom.writeNonEffectivePom calls org.apache.maven.project.MavenProject.writeModel which uses MavenXpp3Writer to generate the pom. On line 1011 you see why it does this

      http://grepcode.com/file/repo1.maven.org/maven2/org.apache.maven/maven-model/2.2.1/org/apache/maven/model/io/xpp3/MavenXpp3Writer.java

      When no packaging is declared, Maven assumes the artifact is the default: jar so it's just being efficient. If sonatype requires this then they are being stricter then they need be

      Delete
  6. @Chris Awesome! I'll try to update my article with those changes

    @Peter I'm quite sure it is required for synchronization with Maven Central (thus required for deployment on Sonatype OSS Repo), and I had some problems deploying my artifacts when I didn't specify it. See section "6. Central Sync Requirement" on https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide

    ReplyDelete
  7. I've been using Gradle to publish Maven artifacts since m4 and I just add

    packaging 'jar'

    inside pom.project section and it works fine with Sonatype...

    ReplyDelete
  8. Just wanted to add that, to be able to close the staging repository Sonatype OSS (oss.sonatype.org), I needed to include javadoc and source jars as well as the regular jar, which I accomplished by adding the following tasks and dependencies and updating the artifacts closure.


    task sourcesJar(type: Jar, dependsOn:classes) {
    classifier = 'sources'
    from sourceSets.main.allSource
    }
    // custom tasks for creating javadoc jar
    task javadocJar(type: Jar, dependsOn:javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
    }

    uploadArchives.dependsOn javadocJar, sourcesJar, jar

    // add the jars as artifacts
    artifacts {
    archives sourcesJar
    archives javadocJar
    archives jar
    }



    Awesome tutorial. Thanks!

    ReplyDelete
  9. Thanks for sharing Andrew! I'll update my article with this information.
    In my GradleFx project I've done it almost the same way, only I think you don't need the dependsOn statements on the sourcesJar and uploadArchives tasks. When I do a "gradle uploadArchives" on my project it automatically compiles it, so it's already part of the build livecycle.
    Here's my build script: https://github.com/GradleFx/GradleFx/blob/master/build.gradle

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. I created a google doc with a summary of this workflow in it (with some additional information).

    https://docs.google.com/document/d/12z99X2GfjLJqNL4Hcp8Z91Xt6csTYVyZY5U1eObg5ts/edit

    ReplyDelete
  12. I've updated the blog post:
    - removed the pom packaging hack that was being used
    - added the javadoc and sources jar code

    ReplyDelete
    Replies
    1. So the hack isn't required, then?

      Delete
    2. No, it isn't required anymore. I have removed it for quite a while now in my code and it's deploying fine.

      Delete
  13. Thanks, Yennick.

    FYI, this line:
    beforeDeployment { MavenDeployment deployment -> signPom(deployment) }

    should be changed to:
    beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

    Apparently, calling signPom directly is deprecated and will be removed in Gradle 2.0...

    ReplyDelete
    Replies
    1. Thanks Ken, I've updated the post.

      Delete
  14. Very helpful. I changed the version to append SNAPSHOT to the end and now the uploadArtifacts fails with this:
    > Error deploying artifact .... Error retrieving previous build number for artifact

    I'm sure I'm just not understanding something about snapshots.

    ReplyDelete
    Replies
    1. I'm also using SNAPSHOT versions for our CI build and I'm having no problem deploying it to sonatype. Two questions:
      1. Did you add "-SNAPSHOT" to the version (mind the dash)
      2. Instead of deploying to sonatype, are you deploying to a webdav repo? If so, seems like other people have the same problem http://issues.gradle.org/browse/GRADLE-2129

      Delete