Publishing an Android library to GitHub Packages

GitHub released the Package Registry beta in May of this year, and graduated it to public availability in Universe 2019, rebranded as GitHub Packages. It supports NodeJS, Docker, Maven, Gradle, NuGet, and RubyGems. That's a LOT of ground covered for a service that's about one year old.

Naturally, I was excited to try this out. The documentation is by no means lacking, but the official instructions for using Packages with Gradle do not work for Android libraries. To make it compatible with Android libraries, some small but non-obvious edits are needed which I've documented here for everybody's benefit.

IMPORTANT: GitHub Packages currently does NOT support unauthenticated access to packages, which means you will always require a personal access token with the read:packages scope to be able to download packages during build. I emailed GitHub support about this, and their reply is attached at the end of this post.

I've also created a sample repository with incremental commits corresponding to the steps given below, for people who prefer to see the code directly.

NB: Grab a Personal Access Token from GitHub with the write:packages scope. You will be using this as authentication to deploy packages. I'll not delve deep into this, a Google search will point you in the right direction if you're not familiar with how to obtain one.

Step 1

Copy the official integration step from GitHub's guide, into your Android library's build.gradle / build.gradle.kts. If you try to run ./gradlew publish now, you'll run into errors. We'll be fixing that shortly. [Commit link]

--- library/build.gradle
+++ library/build.gradle
@@ -1,5 +1,6 @@
 apply plugin: "com.android.library"
 apply plugin: "kotlin-android"
+apply plugin: "maven-publish"
 
 apply from: "../dependencies.gradle"
 // apply from: "../bintrayconfig.gradle"
@@ -28,6 +29,24 @@ android {
   }
 }
 
+publishing {
+  repositories {
+    maven {
+      name = "GitHubPackages"
+      url = uri("https://maven.pkg.github.com/msfjarvis/github-packages-deployment-sample")
+      credentials {
+        username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
+        password = project.findProperty("gpr.key") ?: System.getenv("PASSWORD")
+      }
+    }
+  }
+  publications {
+    gpr(MavenPublication) {
+      from(components.java)
+    }
+  }
+}
+
 dependencies {
   api deps.support.app_compat
   implementation deps.kotlin.stdlib8

Step 2

Switch out the maven-publish plugin with this one. It provides us an Android component that's compatible with publications and precisely what we need. [Commit link]

--- build.gradle
+++ build.gradle
@@ -14,6 +14,7 @@ buildscript {
     classpath deps.gradle_plugins.kotlin
     classpath deps.gradle_plugins.spotless
     classpath deps.gradle_plugins.versions
+    classpath deps.gradle_plugins.android_maven_publish
   }
 }
 
--- dependencies.gradle
+++ dependencies.gradle
@@ -12,7 +12,8 @@ ext.deps = [
         spotless: "com.diffplug.spotless:spotless-plugin-gradle:3.26.0",
         versions: "com.github.ben-manes:gradle-versions-plugin:0.27.0",
         bintray_release: "com.novoda:bintray-release:0.9.1",
-        kotlin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.60"
+        kotlin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.60",
+        android_maven_publish: "digital.wup:android-maven-publish:3.6.2"
     ],
 
     kotlin: [

--- library/build.gradle
+++ library/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: "com.android.library"
 apply plugin: "kotlin-android"
-apply plugin: "maven-publish"
+apply plugin: "digital.wup.android-maven-publish"
 
 apply from: "../dependencies.gradle"
 // apply from: "../bintrayconfig.gradle"

Step 3

Switch to using the android component provided by wup.digital.android-maven-publish. This is the one we require to be able to upload an AAR artifact. [Commit link]

--- library/build.gradle
+++ library/build.gradle
@@ -42,7 +42,7 @@ publishing {
   }
   publications {
     gpr(MavenPublication) {
-      from(components.java)
+      from(components.android)
     }
   }
 }

Step 4

Every Gradle/Maven dependency's address has three attributes, a group ID, an artifact ID, and a version.

implementation 'com.example:my-fancy-library:1.0.0'

Here:

We'll need to configure these too. I prefer using the gradle.properties file for this purpose since it's very easy to access variables from it, but if you have a favorite way of configuring build properties, use that instead! [Commit link]

--- gradle.properties
+++ gradle.properties
@@ -19,3 +19,7 @@ android.useAndroidX=true
 android.enableJetifier=true
 # Kotlin code style for this project: "official" or "obsolete":
 kotlin.code.style=official
+
+# Publishing config
+GROUP=msfjarvis
+VERSION=0.1.0-SNAPSHOT

--- library/build.gradle
+++ library/build.gradle
@@ -43,6 +43,10 @@ publishing {
   publications {
     gpr(MavenPublication) {
       from(components.android)
+      groupId "$GROUP"
+      artifactId "deployment-sample-library"
+      // Use your configured version outside CI, the SHA of the top commit inside.
+      version System.env['GITHUB_SHA'] == null ? "$VERSION" : System.env['GITHUB_SHA']
     }
   }
 }

Step 5

Now all that's left to do is configure GitHub Actions. Go to the Secrets menu in your repository's settings, then create a PACKAGES_TOKEN secret and provide the access token you generated earlier. Head over to the documentation for Secrets if you wanna know how this works under the hood.

Now, let's add the actual configuration that'll get Actions up and running.

--- /dev/null
+++ .github/workflows/publish_snapshot.yml
@@ -0,0 +1,13 @@
+name: "Release per-commit snapshots"
+on: push
+
+jobs:
+  setup-android:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/[email protected]
+    - name: Publish snapshot
+      run: ./gradlew publish
+      env:
+        USERNAME: msfjarvis
+        PASSWORD: ${{ secrets.PACKAGES_TOKEN }}

That's it! Once you push to GitHub, you'll see the action running in your repository's Actions tab and a corresponding package in the Packages tab once the workflow finishes executing.

Closing notes

The requirement to authenticate for packages is a significant problem with GitHub Packages’ adoption, giving an edge to solutions like JitPack which handle the entire process automagically. As mentioned earlier, I did contact GitHub support about it and got this back.

GitHub support reply about authentication requirement for packages

My interpretation of this is quite simply that it's gonna take a while. I hope not :)