Publishing Android Library to GitHub Package Registry

GitHub introduced a software package hosting service that allows you to host your software packages privately or publicly. Much like NPM, you can use the packages as dependencies on your project. In my opinion, I think the idea of having the source code for a particular project with its packages in one platform is great because it increases the accessibility of your software to the developers who are interested in it; they can easily checkout the codebase without switching browsers. Also GitHub made it easy to publish packages with just a few configs.

Without further ado lets dive into how you can publish an Android library to GitHub packages. If you want to learn more about GPR checkout this link About GitHub Packages

Applying Maven Plugin

The first step is to apply the maven plugin to your build.gradle file. We shall be using the Maven Publish plugin.

apply plugin: 'maven-publish'

Add the Gradle Publishing Task

Next you customize the publication task by setting the groupId, artifactId and version for the library. We define the method getArtifactId to return the artifactId of our library, usually the package name the Android library.

Also remember to update the library version each time you are publishing to GPR. Maven publish plugin provides capabilities for customizing the generated pom file. In our case we are just setting the project name and description. Please check out MavenPom in the DSL Reference for the complete documentation of available properties and methods.

Inside the pom.withXml method, we are manually adding the library dependencies to our pom file because the publication doesn't know about our dependencies. We are also checking for transitive dependencies and excluding them. Refer to this Gist for more.

def getArtifactId = { ->  
    return "your-library"  
}

publishing {  
  publications {
        bar(MavenPublication) {
            groupId 'com.somecompany'
            artifactId getArtifactId()
            version '1.0.0'
            artifact("$buildDir/outputs/aar/${getArtifactId()}-release.aar")
            artifact androidJavadocsJar
            artifact androidSourcesJar

            pom {
                name = 'Name of your Project'
                description = 'Your project description goes here.'
            }

            pom.withXml {
                final dependenciesNode = asNode().appendNode('dependencies')

                ext.addDependency = { dep, String scope ->
                    if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified")
                        return // ignore invalid dependencies

                    final dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', dep.group)
                    dependencyNode.appendNode('artifactId', dep.name)
                    dependencyNode.appendNode('version', dep.version)
                    dependencyNode.appendNode('scope', scope)

                    if (!dep.transitive) {
                        // If this dependency is not transitive, we should force exclude all its dependencies from the POM
                        final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
                        exclusionNode.appendNode('groupId', '*')
                        exclusionNode.appendNode('artifactId', '*')
                    } else if (!dep.properties.excludeRules.empty) {
                        // Otherwise add specified exclude rules
                        final exclusionsNode = dependencyNode.appendNode('exclusions')
                        dep.properties.excludeRules.each { rule ->
                            final exclusionNode = exclusionsNode.appendNode('exclusion')
                            exclusionNode.appendNode('groupId', rule.group ?: '*')
                            exclusionNode.appendNode('artifactId', rule.module ?: '*')
                        }
                    }
                }

                // List all "implementation" dependencies (for old Gradle)
                configurations.implementation.getDependencies().each { dep -> addDependency(dep, "implementation") }
                // List all "api" dependencies (for new Gradle) as "implementation" dependencies
                configurations.api.getDependencies().each { dep -> addDependency(dep, "implementation") }
                // List all "implementation" dependencies (for new Gradle) as "runtime" dependencies
                configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
            }

      }

    }

}

Note You can name bar to anything. I just used it as a placeholder but it should be the name of your publication.

Publishing to GitHub Package Registry

Setting Credential and Adding Repository

GPR requires authentication before publishing. Therefore you will be required to provide a GitHub Personal Access Token together with your username. If you don't have a personal access token see this link on how to create one. Personal access token.

Once you have obtained the access token save them in the local.properties file of your project or on the System environment variables. In our case we have saved the GitHub username and access token to the properties named gpr.usr and gpr.key respectively. We then read the file content into a variable named properties.


def properties = new Properties()  
properties.load(new FileInputStream(rootProject.file("local.properties")))

//Declared inside the closure `publishing` at the same level as `publications`
publishing {
    publications { /** Refer to publications content above **/}
    repositories {  
      maven {  
          name = "GitHubPackages"  
          /** Configure path of the package repository on Github using your username and repository */  
          url = uri("https://maven.pkg.github.com/githubusername/your-repository")  
          credentials {  
              /** get credentials from local.properties in root project folder file with */  
              username = properties['gpr.usr'] ?: System.getenv("GPR_USER")  
              password = properties['gpr.key'] ?: System.getenv("GPR_API_KEY")  
          }  
       }
    }
}

repositories closure is part of the publishing task and is included within the same level as publications

Publishing our library

Finally lets publish our library. First you can list the available gradle tasks by running this command.

./gradlew tasks

Among the listed tasks, we have publish - used to publish all the publications declared in the project (remember we named ours bar). But also notice we have a publishBarPublicationToGitHubPackagesRepository task that is specific to our publication.

publishToMavenLocal task is used to publish all publications to the local Maven cache and publishBarPublicationToMavenLocal to publish our specific publication.

To wrap this up we handle the publication with the following command. The command does the following: cleans the build directory, bundles our Android library into an aar file that will be stored in the build/output/aar directory of our project. Then finally publishes the bundled aar file to GPR.

./gradlew :your-libary:clean && ./gradlew :your-library:assembleRelease && ./gradlew publish

That's it. I hope you find this article helpful and that you will consider using GitHub Package Register in your next project.