Skip to content

Commit 281d64f

Browse files
authored
feat(ci): add Maven Central publishing workflow and Android publishing scripts (#72)
1 parent f9a4eca commit 281d64f

6 files changed

Lines changed: 285 additions & 1 deletion

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Publish Capacitor Plugin to Maven Central
2+
3+
on:
4+
workflow_call:
5+
secrets:
6+
ANDROID_CENTRAL_USERNAME:
7+
required: true
8+
ANDROID_CENTRAL_PASSWORD:
9+
required: true
10+
ANDROID_SIGNING_KEY_ID:
11+
required: true
12+
ANDROID_SIGNING_PASSWORD:
13+
required: true
14+
ANDROID_SIGNING_KEY:
15+
required: true
16+
ANDROID_SONATYPE_STAGING_PROFILE_ID:
17+
required: true
18+
CAP_GH_RELEASE_TOKEN:
19+
required: true
20+
workflow_dispatch:
21+
22+
jobs:
23+
publish-android:
24+
if: github.ref == 'refs/heads/main'
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 60
27+
permissions:
28+
contents: read
29+
steps:
30+
- uses: actions/checkout@v5
31+
with:
32+
fetch-depth: 0
33+
ref: ${{ github.ref_name }} # explicitly set ref to include commit from `semantic-release`
34+
token: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
35+
- name: set up JDK 21
36+
uses: actions/setup-java@v5
37+
with:
38+
java-version: '21'
39+
distribution: 'zulu'
40+
- name: Grant execute permission for publishing script
41+
run: chmod +x ./scripts/publish-android.sh
42+
- name: Run publish script
43+
env:
44+
ANDROID_CENTRAL_USERNAME: ${{ secrets.ANDROID_CENTRAL_USERNAME }}
45+
ANDROID_CENTRAL_PASSWORD: ${{ secrets.ANDROID_CENTRAL_PASSWORD }}
46+
ANDROID_SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }}
47+
ANDROID_SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }}
48+
ANDROID_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
49+
ANDROID_SONATYPE_STAGING_PROFILE_ID: ${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }}
50+
run: ./scripts/publish-android.sh

.github/workflows/release_plugin.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,14 @@ jobs:
5656
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
5757
CAP_GH_RELEASE_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
5858

59+
publish-android:
60+
needs: release
61+
uses: ./.github/workflows/publish_maven_central.yml
62+
secrets:
63+
ANDROID_CENTRAL_USERNAME: ${{ secrets.ANDROID_CENTRAL_USERNAME }}
64+
ANDROID_CENTRAL_PASSWORD: ${{ secrets.ANDROID_CENTRAL_PASSWORD }}
65+
ANDROID_SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }}
66+
ANDROID_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
67+
ANDROID_SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }}
68+
ANDROID_SONATYPE_STAGING_PROFILE_ID: ${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }}
69+
CAP_GH_RELEASE_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}

packages/capacitor-plugin/android/build.gradle

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ext {
2+
capacitorVersion = System.getenv('CAPACITOR_VERSION')
23
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
34
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
45
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
@@ -12,15 +13,26 @@ buildscript {
1213
repositories {
1314
google()
1415
mavenCentral()
16+
maven {
17+
url = "https://plugins.gradle.org/m2/"
18+
}
1519
}
1620
dependencies {
1721
classpath 'com.android.tools.build:gradle:8.13.0'
1822
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
23+
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
24+
classpath 'io.github.gradle-nexus:publish-plugin:1.3.0'
25+
}
1926
}
2027
}
2128

2229
apply plugin: 'com.android.library'
2330
apply plugin: 'kotlin-android'
31+
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
32+
apply plugin: 'io.github.gradle-nexus.publish-plugin'
33+
apply from: file('../../../scripts/android/publish-root.gradle')
34+
apply from: file('../../../scripts/android/publish-module.gradle')
35+
}
2436

2537
android {
2638
namespace = "com.capacitorjs.plugins.geolocation"
@@ -45,6 +57,9 @@ android {
4557
sourceCompatibility JavaVersion.VERSION_21
4658
targetCompatibility JavaVersion.VERSION_21
4759
}
60+
publishing {
61+
singleVariant("release")
62+
}
4863
}
4964

5065
kotlin {
@@ -59,8 +74,14 @@ repositories {
5974

6075
dependencies {
6176
implementation fileTree(dir: 'libs', include: ['*.jar'])
77+
78+
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
79+
implementation "com.capacitorjs:core:$capacitorVersion"
80+
} else {
81+
implementation project(':capacitor-android')
82+
}
83+
6284
implementation("io.ionic.libs:iongeolocation-android:2.1.0")
63-
implementation project(':capacitor-android')
6485
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
6586

6687
implementation 'com.google.code.gson:gson:2.13.2'
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
apply plugin: 'maven-publish'
2+
apply plugin: 'signing'
3+
4+
def LIB_VERSION = System.getenv('PLUGIN_VERSION')
5+
def PLUGIN_NAME = System.getenv('PLUGIN_NAME')
6+
def PLUGIN_REPO = System.getenv('PLUGIN_REPO')
7+
def PLUGIN_SCM = System.getenv('PLUGIN_SCM')
8+
9+
task androidSourcesJar(type: Jar) {
10+
archiveClassifier.set('sources')
11+
if (project.plugins.findPlugin("com.android.library")) {
12+
from android.sourceSets.main.java.srcDirs
13+
from android.sourceSets.main.kotlin.srcDirs
14+
} else {
15+
from sourceSets.main.java.srcDirs
16+
from sourceSets.main.kotlin.srcDirs
17+
}
18+
}
19+
20+
artifacts {
21+
archives androidSourcesJar
22+
}
23+
24+
group = 'com.capacitorjs'
25+
version = LIB_VERSION
26+
27+
afterEvaluate {
28+
publishing {
29+
publications {
30+
release(MavenPublication) {
31+
// Coordinates
32+
groupId 'com.capacitorjs'
33+
artifactId PLUGIN_NAME
34+
version LIB_VERSION
35+
36+
// Two artifacts, the `aar` (or `jar`) and the sources
37+
if (project.plugins.findPlugin("com.android.library")) {
38+
from components.release
39+
} else {
40+
artifact("$buildDir/libs/${project.getName()}-${version}.jar")
41+
}
42+
43+
artifact androidSourcesJar
44+
45+
// POM Data
46+
pom {
47+
name = PLUGIN_NAME
48+
description = 'Capacitor Android ' + PLUGIN_NAME + ' plugin native library'
49+
url = PLUGIN_REPO
50+
licenses {
51+
license {
52+
name = 'MIT'
53+
url = PLUGIN_REPO + '/blob/main' + '/LICENSE'
54+
}
55+
}
56+
developers {
57+
developer {
58+
name = 'Ionic'
59+
email = 'hi@ionic.io'
60+
}
61+
}
62+
63+
// Version Control Info
64+
scm {
65+
connection = 'scm:git:' + PLUGIN_SCM + '.git'
66+
developerConnection = 'scm:git:ssh://' + PLUGIN_SCM + '.git'
67+
url = PLUGIN_REPO + '/tree/main'
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
signing {
76+
useInMemoryPgpKeys(
77+
rootProject.ext["signing.keyId"],
78+
rootProject.ext["signing.key"],
79+
rootProject.ext["signing.password"],
80+
)
81+
sign publishing.publications
82+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Create variables with empty default values
2+
ext["signing.keyId"] = ''
3+
ext["signing.key"] = ''
4+
ext["signing.password"] = ''
5+
ext["centralTokenUsername"] = ''
6+
ext["centralTokenPassword"] = ''
7+
ext["sonatypeStagingProfileId"] = ''
8+
9+
File globalSecretPropsFile = file('../../../scripts/android/local.properties')
10+
File secretPropsFile = project.rootProject.file('local.properties')
11+
if (globalSecretPropsFile.exists()) {
12+
// Read global local.properties file first if it exists (scripts/android/local.properties)
13+
Properties p = new Properties()
14+
new FileInputStream(globalSecretPropsFile).withCloseable { is -> p.load(is) }
15+
p.each { name, value -> ext[name] = value }
16+
} else if (secretPropsFile.exists()) {
17+
// Read plugin project specific local.properties file next if it exists
18+
Properties p = new Properties()
19+
new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
20+
p.each { name, value -> ext[name] = value }
21+
} else {
22+
// Use system environment variables
23+
ext["centralTokenUsername"] = System.getenv('ANDROID_CENTRAL_USERNAME')
24+
ext["centralTokenPassword"] = System.getenv('ANDROID_CENTRAL_PASSWORD')
25+
ext["sonatypeStagingProfileId"] = System.getenv('ANDROID_SONATYPE_STAGING_PROFILE_ID')
26+
ext["signing.keyId"] = System.getenv('ANDROID_SIGNING_KEY_ID')
27+
ext["signing.key"] = System.getenv('ANDROID_SIGNING_KEY')
28+
ext["signing.password"] = System.getenv('ANDROID_SIGNING_PASSWORD')
29+
}
30+
31+
// Set up Sonatype repository
32+
nexusPublishing {
33+
repositories {
34+
sonatype {
35+
stagingProfileId = sonatypeStagingProfileId
36+
username = centralTokenUsername
37+
password = centralTokenPassword
38+
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
39+
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
40+
}
41+
}
42+
repositoryDescription = 'Capacitor Android ' + System.getenv('PLUGIN_NAME') + ' plugin v' + System.getenv('PLUGIN_VERSION')
43+
}

scripts/publish-android.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env bash
2+
3+
publish_plugin_android () {
4+
PLUGIN_PATH=$1
5+
if [ -d "$PLUGIN_PATH" ]; then
6+
# Android dir path
7+
ANDROID_PATH=$PLUGIN_PATH/android
8+
GRADLE_FILE=$ANDROID_PATH/build.gradle
9+
10+
# Only try to publish if the directory contains a package.json and android package
11+
if test -f "$PLUGIN_PATH/package.json" && test -d "$ANDROID_PATH" && test -f "$GRADLE_FILE"; then
12+
PLUGIN_VERSION=$(grep '"version": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
13+
PLUGIN_NAME=$(grep '"name": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
14+
PLUGIN_NAME=${PLUGIN_NAME#@capacitor/}
15+
LOG_OUTPUT=./tmp/$PLUGIN_NAME.txt
16+
17+
# Get latest plugin info from MavenCentral
18+
PLUGIN_PUBLISHED_URL="https://repo1.maven.org/maven2/com/capacitorjs/$PLUGIN_NAME/maven-metadata.xml"
19+
PLUGIN_PUBLISHED_DATA=$(curl -s $PLUGIN_PUBLISHED_URL)
20+
PLUGIN_PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*<latest>(.*)<\/latest>.*/\1/;' <<< $PLUGIN_PUBLISHED_DATA)"
21+
22+
if [[ $PLUGIN_VERSION == $PLUGIN_PUBLISHED_VERSION ]]; then
23+
printf %"s\n\n" "Duplicate: a published plugin $PLUGIN_NAME exists for version $PLUGIN_VERSION, skipping..."
24+
else
25+
# Make log dir if doesnt exist
26+
mkdir -p ./tmp
27+
28+
printf %"s\n" "Attempting to build and publish plugin $PLUGIN_NAME for version $PLUGIN_VERSION to production..."
29+
30+
# Export ENV variables used by Gradle for the plugin
31+
export PLUGIN_NAME
32+
export PLUGIN_VERSION
33+
export CAPACITOR_VERSION
34+
export CAP_PLUGIN_PUBLISH=true
35+
export PLUGIN_REPO="https://github.com/ionic-team/capacitor-geolocation"
36+
export PLUGIN_SCM="github.com:ionic-team/capacitor-geolocation"
37+
38+
# Build and publish
39+
"$ANDROID_PATH"/gradlew clean build publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository --no-daemon --max-workers 1 -b "$ANDROID_PATH"/build.gradle -Pandroid.useAndroidX=true > $LOG_OUTPUT 2>&1
40+
41+
if grep --quiet "BUILD SUCCESSFUL" $LOG_OUTPUT; then
42+
printf %"s\n\n" "Success: $PLUGIN_NAME published to MavenCentral."
43+
else
44+
printf %"s\n\n" "Error publishing $PLUGIN_NAME, check $LOG_OUTPUT for more info! Manually review and release from the Central Portal may be necessary https://central.sonatype.com/publishing/deployments/"
45+
cat $LOG_OUTPUT
46+
exit 1
47+
fi
48+
fi
49+
else
50+
printf %"s\n\n" "$PLUGIN_PATH does not appear to be a plugin (has no package.json file or Android package), skipping..."
51+
fi
52+
fi
53+
}
54+
55+
# Get latest com.capacitorjs:core XML version info
56+
CAPACITOR_PUBLISHED_URL="https://repo1.maven.org/maven2/com/capacitorjs/core/maven-metadata.xml"
57+
CAPACITOR_PUBLISHED_DATA=$(curl -s $CAPACITOR_PUBLISHED_URL)
58+
CAPACITOR_PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*<latest>(.*)<\/latest>.*/\1/;' <<< $CAPACITOR_PUBLISHED_DATA)"
59+
60+
printf %"s\n" "The latest published Android library version of Capacitor Core is $CAPACITOR_PUBLISHED_VERSION in MavenCentral."
61+
62+
# Determine Capacitor Version to use as gradle dependency.
63+
STABLE_PART=$(echo "$CAPACITOR_PUBLISHED_VERSION" | cut -d'-' -f1)
64+
IFS='.' read -r MAJOR MINOR PATCH <<< "$STABLE_PART"
65+
if [[ "$CAPACITOR_PUBLISHED_VERSION" == *"-"* ]]; then
66+
# prerelease - go one major lower (latest stable major), but also allow next upcoming major
67+
PREV_MAJOR=$((MAJOR - 1))
68+
NEXT_MAJOR=$((MAJOR + 1))
69+
CAPACITOR_VERSION="[$PREV_MAJOR.0,$NEXT_MAJOR.0)"
70+
else
71+
# stable - current major range
72+
NEXT_MAJOR=$((MAJOR + 1))
73+
CAPACITOR_VERSION="[$MAJOR.0,$NEXT_MAJOR.0)"
74+
fi
75+
printf %"s\n" "Publishing plugin with dependency on Capacitor version $CAPACITOR_VERSION"
76+
77+
publish_plugin_android './packages/capacitor-plugin'

0 commit comments

Comments
 (0)