Skip to content

Commit 0986ecf

Browse files
Qizotclaude
andcommitted
ci(android): implement Maven Central publishing and CI release
Add vanniktech maven-publish plugin to the MoQKit library with full POM metadata, signing support, and Maven Central (Central Portal) upload. Implement the release-android CI workflow (was a stub). Add mise-tasks/publish-android script and update mise.toml tasks to use mavenLocal for local dev and Maven Central for releases. Update the example app to consume moqkit from Maven Central instead of a local AAR. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent b225375 commit 0986ecf

10 files changed

Lines changed: 170 additions & 44 deletions

File tree

.github/workflows/release-android.yml

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,109 @@ on:
88
type: string
99
outputs:
1010
checksum:
11-
description: "SHA256 checksum of the Android AAR (empty until implemented)"
12-
value: ${{ jobs.release-android.outputs.checksum }}
11+
description: "SHA256 checksum of moqkit-{version}.aar"
12+
value: ${{ jobs.build.outputs.checksum }}
1313

1414
jobs:
15-
release-android:
15+
build:
1616
runs-on: ubuntu-latest
17+
env:
18+
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
1719
outputs:
1820
checksum: ${{ steps.checksum.outputs.checksum }}
1921
steps:
20-
- name: Not yet implemented
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
with:
25+
submodules: recursive
26+
fetch-depth: 0
27+
28+
- name: Derive version
29+
id: version
30+
run: |
31+
VERSION="${{ inputs.tag }}"
32+
VERSION="${VERSION#v}"
33+
echo "version=$VERSION" >> $GITHUB_OUTPUT
34+
35+
- name: Set up JDK 17
36+
uses: actions/setup-java@v4
37+
with:
38+
java-version: '17'
39+
distribution: 'temurin'
40+
41+
- name: Install Rust
42+
uses: dtolnay/rust-toolchain@stable
43+
with:
44+
targets: aarch64-linux-android
45+
46+
- name: Install cargo-ndk
47+
run: cargo install cargo-ndk
48+
49+
- name: Cache Rust build artifacts
50+
uses: actions/cache@v4
51+
with:
52+
path: vendor/moq/target
53+
key: ${{ runner.os }}-rust-android-${{ hashFiles('vendor/moq/Cargo.lock') }}
54+
restore-keys: |
55+
${{ runner.os }}-rust-android-
56+
57+
- name: Build host library for UniFFI metadata
58+
run: |
59+
cargo build --release --package moq-ffi \
60+
--manifest-path vendor/moq/Cargo.toml
61+
62+
- name: Generate Kotlin UniFFI bindings
63+
run: |
64+
mkdir -p android/moqkit/MoQKit/src/main/java
65+
cd vendor/moq && cargo run --release --package moq-ffi --bin uniffi-bindgen \
66+
--manifest-path Cargo.toml \
67+
generate \
68+
--library target/release/libmoq_ffi.so \
69+
--language kotlin \
70+
--out-dir ../../android/moqkit/MoQKit/src/main/java
71+
72+
- name: Build Android shared library (arm64-v8a)
73+
env:
74+
ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_LATEST_HOME }}
75+
run: |
76+
mkdir -p android/moqkit/MoQKit/src/main/jniLibs
77+
cargo ndk -t arm64-v8a \
78+
-o android/moqkit/MoQKit/src/main/jniLibs \
79+
build --release --package moq-ffi \
80+
--manifest-path vendor/moq/Cargo.toml
81+
82+
- name: Build AAR
83+
run: |
84+
cd android/moqkit
85+
chmod +x gradlew
86+
./gradlew :MoQKit:assembleRelease
87+
88+
- name: Rename AAR and compute checksum
2189
id: checksum
90+
env:
91+
VERSION: ${{ steps.version.outputs.version }}
92+
run: |
93+
AAR_SRC="android/moqkit/MoQKit/build/outputs/aar/MoQKit-release.aar"
94+
AAR_DST="moqkit-${VERSION}.aar"
95+
cp "$AAR_SRC" "$AAR_DST"
96+
CHECKSUM=$(sha256sum "$AAR_DST" | awk '{print $1}')
97+
echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT
98+
echo "Checksum: $CHECKSUM"
99+
100+
- name: Upload AAR to release
101+
uses: softprops/action-gh-release@v2
102+
with:
103+
tag_name: ${{ inputs.tag }}
104+
files: moqkit-${{ steps.version.outputs.version }}.aar
105+
106+
- name: Publish to Maven Central
107+
if: env.MAVEN_CENTRAL_USERNAME != ''
108+
env:
109+
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
110+
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
111+
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
112+
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}
22113
run: |
23-
echo "checksum=" >> $GITHUB_OUTPUT
24-
echo "Android release not yet implemented"
114+
cd android/moqkit
115+
./gradlew :MoQKit:publishToMavenCentral \
116+
-PpublishVersion=${{ steps.version.outputs.version }}

.github/workflows/release.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Release
33
on:
44
push:
55
tags:
6-
- 'v*'
6+
- "v*"
77

88
permissions:
99
contents: write
@@ -79,9 +79,6 @@ jobs:
7979
print("Updated Package.swift")
8080
EOF
8181
82-
# When Android is implemented, add a manifest-update step here:
83-
# if: needs.android.outputs.checksum != ''
84-
8582
- name: Commit, retag, and push
8683
env:
8784
TAG: ${{ github.ref_name }}

android/moqkit/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ plugins {
33
alias(libs.plugins.android.application) apply false
44
alias(libs.plugins.android.library) apply false
55
alias(libs.plugins.kotlin.android) apply false
6+
alias(libs.plugins.maven.publish) apply false
67
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
[versions]
22
agp = "9.0.1"
33
kotlin = "2.0.21"
4+
mavenPublish = "0.32.0"
45
coroutines = "1.9.0"
5-
media3 = "1.9.2"
66
coreKtx = "1.10.1"
77
jna = "5.18.1"
88
junit = "4.13.2"
99
junitVersion = "1.1.5"
1010
espressoCore = "3.5.1"
11-
appcompat = "1.6.1"
12-
material = "1.10.0"
1311

1412
[libraries]
1513
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
1614
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
1715
junit = { group = "junit", name = "junit", version.ref = "junit" }
1816
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
1917
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
20-
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
21-
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
2218
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
23-
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
2419

2520
[plugins]
2621
android-application = { id = "com.android.application", version.ref = "agp" }
2722
android-library = { id = "com.android.library", version.ref = "agp" }
2823
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
24+
maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
2925

android/moqkit/moqkit/build.gradle.kts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
alias(libs.plugins.android.library)
3+
alias(libs.plugins.maven.publish)
34
}
45

56
android {
@@ -32,30 +33,43 @@ android {
3233
}
3334
}
3435

35-
val aarOutputDir = file("/Users/jakub/repos/moq-kit/examples/android/subscriber/MoQSubscriber/libs")
36-
37-
tasks.register<Copy>("manualBuild") {
38-
group = "build"
39-
description = "Compiles the library and copies the AAR to the target directory"
40-
41-
dependsOn("assembleRelease")
42-
43-
from(layout.buildDirectory.dir("outputs/aar"))
44-
into(aarOutputDir)
45-
include("*-release.aar")
46-
47-
doLast {
48-
println("Build finished! AAR copied to $aarOutputDir")
36+
mavenPublishing {
37+
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
38+
if (project.hasProperty("signing.keyId") || project.hasProperty("signingInMemoryKey")) {
39+
signAllPublications()
40+
}
41+
val publishVersion = project.findProperty("publishVersion")?.toString() ?: "0.0.1-alpha"
42+
coordinates("com.swmansion.moqkit", "moqkit", publishVersion)
43+
pom {
44+
name = "MoQ Kit Android SDK"
45+
description = "Android SDK for Media over QUIC (MOQ) — live streaming over QUIC/WebTransport."
46+
url = "https://github.com/software-mansion-labs/moq-kit"
47+
licenses {
48+
license {
49+
name = "Apache License, Version 2.0"
50+
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
51+
}
52+
}
53+
scm {
54+
connection = "scm:git:git://github.com/software-mansion-labs/moq-kit.git"
55+
developerConnection = "scm:git:ssh://github.com/software-mansion-labs/moq-kit.git"
56+
url = "https://github.com/software-mansion-labs/moq-kit"
57+
}
58+
developers {
59+
developer {
60+
id = "swmansion"
61+
name = "Software Mansion"
62+
email = "contact@swmansion.com"
63+
}
64+
}
4965
}
5066
}
5167

68+
5269
dependencies {
5370
implementation("net.java.dev.jna:jna:5.18.1@aar")
54-
api(libs.media3.exoplayer)
5571
implementation(libs.kotlinx.coroutines.android)
5672
implementation(libs.androidx.core.ktx)
57-
implementation(libs.androidx.appcompat)
58-
implementation(libs.material)
5973
testImplementation(libs.junit)
6074
testImplementation("net.java.dev.jna:jna:5.18.1@aar")
6175
androidTestImplementation(libs.androidx.junit)

examples/android/subscriber/MoQSubscriber/app/build.gradle.kts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ android {
4242
}
4343

4444
dependencies {
45-
implementation(files("../libs/MoQKit-release.aar"))
46-
// TODO: Remove once MoQKit is published to a Maven repository (transitive deps will be resolved via POM)
47-
implementation("net.java.dev.jna:jna:5.18.1@aar")
48-
implementation(libs.media3.exoplayer)
49-
implementation(libs.media3.ui)
45+
implementation("com.swmansion.moqkit:moqkit:0.0.1-alpha")
5046
implementation(libs.androidx.core.ktx)
5147
implementation(libs.androidx.appcompat)
5248
implementation(libs.material)

examples/android/subscriber/MoQSubscriber/gradle/libs.versions.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
agp = "9.0.1"
33
kotlin = "2.0.21"
44
kotlinCoroutines = "1.8.1"
5-
media3 = "1.9.2"
65
activityCompose = "1.9.3"
76
lifecycleViewmodelCompose = "2.8.7"
87
coreKtx = "1.10.1"
@@ -22,8 +21,6 @@ material = { group = "com.google.android.material", name = "material", version.r
2221
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
2322
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
2423
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinCoroutines" }
25-
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
26-
media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }
2724

2825
[plugins]
2926
android-application = { id = "com.android.application", version.ref = "agp" }

examples/android/subscriber/MoQSubscriber/settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ plugins {
1717
dependencyResolutionManagement {
1818
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
1919
repositories {
20+
mavenLocal()
2021
google()
2122
mavenCentral()
2223
}

mise-tasks/publish-android

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
#MISE description="Build Rust + UniFFI, then publish Android AAR to Maven Central"
3+
set -euo pipefail
4+
5+
# usage: publish-android [--debug]
6+
# --debug Build Rust with debug symbols (passed through to build-android)
7+
#
8+
# Requires in ~/.gradle/gradle.properties:
9+
# mavenCentralUsername=<Central Portal token username>
10+
# mavenCentralPassword=<Central Portal token password>
11+
# signing.keyId=<last 8 chars of GPG key ID>
12+
# signing.password=<GPG key passphrase>
13+
# signing.secretKeyRingFile=<path to secring.gpg>
14+
# Or for CI (in-memory key):
15+
# signingInMemoryKey=<ascii-armored GPG private key>
16+
# signingInMemoryKeyPassword=<GPG key passphrase>
17+
18+
ROOT_DIR="$MISE_PROJECT_ROOT"
19+
20+
echo "==> Building Rust + UniFFI bindings for Android..."
21+
mise run build-android "$@"
22+
23+
echo "==> Publishing MoQKit AAR to Maven Central..."
24+
(cd "$ROOT_DIR/android/moqkit" &&
25+
./gradlew :MoQKit:publishToMavenCentral)
26+
27+
echo ""
28+
echo "Done. Check https://central.sonatype.com for publication status."

mise.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ run = "cd vendor/moq && just check"
66
description = "Auto-format Rust code in vendor/moq"
77
run = "cd vendor/moq && just fix"
88

9-
[tasks."android:aar"]
10-
description = "Assemble AAR and copy to examples"
11-
run = "cd android/moqkit && ./gradlew :MoQKit:manualBuild"
9+
[tasks."publish-android:local"]
10+
description = "Assemble AAR and publish to local Maven (~/.m2)"
11+
run = "cd android/moqkit && ./gradlew :MoQKit:publishToMavenLocal"
1212

1313
[tasks."android:install"]
1414
description = "Install Android subscriber demo APK"
@@ -22,6 +22,10 @@ run = "adb shell am start -n com.swmansion.moqsubscriber/.MainActivity"
2222
description = "Build Rust + UniFFI + .so + Kotlin bindings + AAR"
2323
depends = ["build-android", "android:aar"]
2424

25+
[tasks."android:publish"]
26+
description = "Build Rust + AAR and publish to Maven Central"
27+
run = "mise run publish-android"
28+
2529
[tasks."relay"]
2630
description = "Run a relay server"
2731
run = "cd vendor/moq && just relay"

0 commit comments

Comments
 (0)