Skip to content

Instantly share code, notes, and snippets.

@danielesegato
Last active March 30, 2023 11:44
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielesegato/3ea6f99c968ce0b795c5390844ad4ff7 to your computer and use it in GitHub Desktop.
Save danielesegato/3ea6f99c968ce0b795c5390844ad4ff7 to your computer and use it in GitHub Desktop.
Android Library Publishing Maven Artifacts via gradle

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:

confused AF

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.

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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment