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"

Friday, 6 May 2011

Building a swc with Gradle

After a search for a better build tool, having used Ant & Maven for a couple of years, I stumbled upon Gradle. A build tool that provides the benefits of Ant & Maven combined into one:

  • Dependency Management
  • Better multi-project support
  • Flexibility
Gradle build script are written in Groovy, which allows you to define every step of the build process.
Now, as a Flex developer the first thing I wanted to try out was to build a swc file. I tried to incorporate some multi-project structure into it.
My project structure looks like this:

The settings.gradle file declares which the subprojects are of the mainproject:

include 'subproject'

The build script of the main project contains the configuration needed by every subproject. Gradle provides a "subprojects { ... }" structure for this in which you can place all this code. I'm using maven repositories from which my dependencies will be retrieved:

subprojects {

    //retrieve the FLEX_HOME environment variable
    FLEX_SDK_HOME = System.getenv()['FLEX_HOME']

    configurations {
        compile
        test
    }

    repositories {
        mavenCentral()
        mavenRepo name: 'yoolab-releases',     
                  urls: "http://projects.yoolab.org/maven/content/repositories/releases"
        mavenRepo name: 'yoolab-snapshots',    
                  urls: "http://projects.yoolab.org/maven/content/repositories/snapshots"
    }

    //dependency versions
    def spring_actionscript_core_version = '1.2-SNAPSHOT'
    def as3commons_collections_version = '1.1'
    def as3commons_eventbus_version = '1.1'
    def as3commons_lang_version = '0.3.2'
    def as3commons_logging_version = '2.0'
    def as3commons_reflect_version = '1.3.4'
    def flexunit_version = '0.90'

    dependencies {
        compile group: 'org.springextensions.actionscript',
                name: 'spring-actionscript-core',
                version: spring_actionscript_core_version,
                ext: 'swc'
        compile group: 'org.as3commons',
                name: 'as3commons-collections',
                version: as3commons_collections_version,
                ext: 'swc'
        compile group: 'org.as3commons',
                name: 'as3commons-eventbus',
                version: as3commons_eventbus_version,
                ext: 'swc'
        compile group: 'org.as3commons',
                name: 'as3commons-lang',
                version: as3commons_lang_version, ext: 'swc'
        compile group: 'org.as3commons',
                name: 'as3commons-logging',
                version: as3commons_logging_version,
                ext: 'swc'
        compile group: 'org.as3commons',
                name: 'as3commons-reflect',
                version: as3commons_reflect_version,
                ext: 'swc'

        test group: 'com.adobe.flexunit',
             name: 'flexunit',
             version: flexunit_version,
             ext: 'swc'
    }
}


The build.gradle file of the subproject contains a compile task (don't mistake this with the 'compile' configuration which I declared in the mainproject's build file). This task can be executed by calling the "gradle compile" command from a command line and it will build the swc file.

task compile << {
    println '[compile] Compiling release SWC'
    println '[compile] Using Flex SDK at: ' + FLEX_SDK_HOME

    def projectName = 'subproject'
    def projectVersion = '1.0-SNAPSHOT'

    //initialize compc parameters
    def compiledSwcFileLocation = buildDir.getAbsolutePath() + '/' + projectName + '-' + projectVersion + '.swc'
    def mainSourceLocation = projectDir.getAbsolutePath() + '/src/main/actionscript/'

    ant.java(jar: FLEX_SDK_HOME + '/lib/compc.jar',
             dir: FLEX_SDK_HOME + '/frameworks',
             fork: true,
             resultproperty: 'swcBuildResult',
             errorProperty: 'errorString') {
      arg(value: "-output=" + compiledSwcFileLocation)
      arg(value: "-include-sources=" + mainSourceLocation)
      //add every dependency path
      configurations.compile.files.each{
         dep -> arg(value: "-external-library-path+=" + dep.path)
      }
    }

    //handle failed compile
    if(ant.properties.swcBuildResult == '1') {
       throw new Exception("compc task failed: \n" + ant.properties.errorString);
    }
}


Gradle has some awesome support for Ant tasks (maybe even better then using them in Ant itself). As you can see I'm using the java Ant task to execute the compc compiler, and I'm providing it some ant property names which I can use after the ant task has been finished.

Gradle takes some getting used to (certainly if you've never used Groovy before), but it's totally worth the effort. I'm going to investigate further into this and see how it scales to bigger projects.