caipivara
7/20/2016 - 10:43 AM

Android Library Publishing Maven Artifacts via gradle

Android Library Publishing Maven Artifacts via gradle

apply plugin: 'maven'
apply plugin: 'signing'

def isReleaseBuild() {
    return !VERSION_NAME.contains("SNAPSHOT");
}

def getReleaseRepositoryUrl() {
    println "Reading Maven repository Release URL from gradle.properties 'RELEASE_REPOSITORY_URL'"
    if (hasProperty('RELEASE_REPOSITORY_URL')) {
        return RELEASE_REPOSITORY_URL;
    }
    throw new InvalidUserDataException("RELEASE_REPOSITORY_URL is not defined");
}

def getSnapshotRepositoryUrl() {
    println "Reading Maven repository Snapshot URL from gradle.properties 'SNAPSHOT_REPOSITORY_URL'"
    if (hasProperty('SNAPSHOT_REPOSITORY_URL')) {
        return SNAPSHOT_REPOSITORY_URL;
    }
    throw new InvalidUserDataException("SNAPSHOT_REPOSITORY_URL is not defined");
}

def getRepositoryUsername() {
    println "Reading Maven repository username from gradle.properties 'mavenRepositoryUsername'"
    if (hasProperty('mavenRepositoryUsername')) {
        return mavenRepositoryUsername;
    }
    throw new InvalidUserDataException("mavenRepositoryUsername is not defined, check your user ~/.gradle/gradle.properties file");
}

def getRepositoryPassword() {
    println "Reading Maven repository password from gradle.properties 'mavenRepositoryPassword'"
    if (hasProperty('mavenRepositoryPassword')) {
        return mavenRepositoryPassword;
    }
    throw new InvalidUserDataException("mavenRepositoryPassword is not defined, check your user ~/.gradle/gradle.properties file");
}

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

                pom.groupId = GROUP
                pom.artifactId = POM_ARTIFACT_ID
                pom.version = VERSION_NAME

                repository(url: getReleaseRepositoryUrl()) {
                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
                }
                snapshotRepository(url: getSnapshotRepositoryUrl()) {
                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
                }

                pom.project {
                    name POM_NAME
                    packaging POM_PACKAGING
                    description POM_DESCRIPTION
                    url POM_URL

                    scm {
                        url POM_SCM_URL
                        connection POM_SCM_CONNECTION
                        developerConnection POM_SCM_DEV_CONNECTION
                    }

                    licenses {
                        license {
                            name POM_LICENCE_NAME
                            url POM_LICENCE_URL
                            distribution POM_LICENCE_DIST
                        }
                    }

                    developers {
                        developer {
                            id POM_DEVELOPER_ID
                            name POM_DEVELOPER_NAME
                        }
                    }
                }
            }
        }
    }

    signing {
        required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
        sign configurations.archives
    }

    task androidJavadocs(type: Javadoc) {
        // execute only if I'm publishing on maven
        onlyIf { gradle.taskGraph.hasTask(uploadArchives) }

        // all the sources of the current module
        source = android.sourceSets.main.java.srcDirs
        // the Android SDK classpath
        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
        // all the dependencies classpaths
        classpath += configurations.compile

        // Honestly I do not remember why it's a good idea to exclude these
        exclude '**/BuildConfig.class'
        exclude '**/R.class'
        exclude '**/R$*.class'

        options {
            // Java reference
            links("http://docs.oracle.com/javase/8/docs/api/");

            // dependencies API references (I should probably move these in the project or something)
            links("http://reactivex.io/RxJava/javadoc/");
            links("https://google.github.io/gson/apidocs/");

            // Android reference is not standard javadoc so I need to use offline directory
            linksOffline("http://d.android.com/reference/", "${android.sdkDirectory}/docs/reference")

            // Java 8 javadoc is more strict, This disable that strictness
            if (JavaVersion.current().isJava8Compatible()) {
                addStringOption('Xdoclint:none', '-quiet')
            }
        }
        // uncomment to avoid failing the build if javadoc fails
//        failOnError false
    }

    task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
        classifier = 'javadoc'
        from androidJavadocs.destinationDir
    }

    task androidSourcesJar(type: Jar) {
        classifier = 'sources'
        from android.sourceSets.main.java.sourceFiles
    }

    artifacts {
        archives androidSourcesJar
        archives androidJavadocsJar
    }
}

What is this

This gitst contains a script to push Android libraries as artifacts on a maven repository using the gradle build system.

It is somewhate a fork of Chris Banes gradle push script.

This was me while trying to understand how to setup maven publishing with gradle:

Documentation is absent or very lacking and I found no script handling javadoc properly for Android. When I figured it out I decided to write this.

This script is not thought for libraries with variants.

Setup

  1. Copy the maven_push_library.gradle in your repository root.

  2. set these properties in your project gradle.properties file:

     RELEASE_REPOSITORY_URL=<uri-to-deploy-your-releases-artifacts>
     SNAPSHOT_REPOSITORY_URL=<uri-to-deploy-your-snapshots-artifacts>
     VERSION_NAME=1.0.0
     VERSION_CODE=1
     GROUP=<your-group-id>
     POM_LICENCE_NAME=
     POM_LICENCE_URL=
     POM_LICENCE_DIST=
     POM_DEVELOPER_ID=
     POM_DEVELOPER_NAME=
     POM_URL=https://<url-to-your-project-home>
     POM_SCM_URL=https://<url-to-your-repository-web-browsable>
     POM_SCM_CONNECTION=scm:git@<your-repository-remote>
     POM_SCM_DEV_CONNECTION=scm:git@<your-development-repository-remote>
    

this assume the group and version name are the same for all your modules; feel free to override them in each module gradle.properties file

  1. set these properties in each module gradle.properties file:

     POM_NAME=<your-module-name>
     POM_ARTIFACT_ID=<your-module-artifact-id>
     POM_PACKAGING=aar
     POM_DESCRIPTION=<an-optional-description>
    
  2. set these properties in your USER ~/.gradle/gradle.properties file:

     mavenRepositoryUsername=<your-maven-repo-username>
     mavenRepositoryPassword=<your-maven-repo-password>
    
  3. add this to your root project build.gradle script file:

     subprojects {
         group = GROUP
         version = VERSION_NAME
     }
    

this is needed for dependencies between modules. You can also set it for each module

  1. add this line at the bottom of the Android library modules you want to publish:

     apply from: '../maven_push_library.gradle'
    

Signing

You need to sign your artifacts if you need/want to deploy your modules in a public maven repository (mavenCentral, jCenter, ...).

I never needed it because I publish my company modules in a private repository, there are detailed instruction on how to setup for signing here.

Basically you need to create a key file using gpg, then set it up to sign your artifacts. This is a security measure to make sure no one else can release stuff on your behalf, provided you keep the key safely.

Publishing on AWS S3

I've experimented a little in using Amazon S3 as maven server.

To do so you need to add this to your maven_push_library.gradle file:

configurations {
    mavenWagons
}

dependencies {
    mavenWagons 'org.springframework.build:aws-maven:5.0.0.RELEASE'
}

afterEvaluate { project ->
    uploadArchives {
        repositories {
            mavenDeployer {
                configuration = configurations.mavenWagons
                
                // keep whats here
            }
        }
    }
}

This tell gradle maven plugin script to use a different wagon to publish on maven, specifically the aws-maven wagon.

Then you have to change your REPO Urls to something like:

RELEASE_REPOSITORY_URL=s3://<bucket-name>/releases
SNAPSHOT_REPOSITORY_URL=s3://<bucket-name>/snapshots

Be carefule tough: the aws-maven wagon is a bit outdated and does not know about some new AWS Regions (example: Frankfurt - eu-central-1) and will fail if your bucket is not in one of the supported regions.

To setup the S3 repository you just need to create a bucket, a IAM user that can write and read from that bucket and set the AWS Access Key as username and the AWS Secret Key as password. I'm not going into details here cause this is basic AWS knowledge you can find anywhere.

Playing with IAM Roles and Groups you can also manage to get some manual user management.

Other resources

Check out Square maven push script.

Have a look at this guide on how to publish on jcenter.

To setup AWS S3 for maven there are different guides: 1, 2. Many fail in suggesting the policy for the S3 user which is missing a non-intuitive permission.

What about the new maven-publish plugin?

At some point gradle introduced a new maven-publish plugin that is meant to replace the old maven plugin.

I found very few scripts for android using this new plugin.

None say anything about the Javadoc artifact generation or more complex behavior. Furthermore I've read there are issues with the dependencies generation in the pom.xml.

The documentation is very lacking for both maven and maven-publish plugin on Android so I'll stick on the most used one for now.