Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Release workflow: build the universal libjcocoa and the jar on macOS, run
# the tests against that Release build, and publish to Maven Central through
# the Central Portal (central-publishing-maven-plugin, profile central-deploy
# in pom.xml).
#
# Required GitHub Secrets:
# GPG_PRIVATE_KEY - Base64-encoded GPG private key
# GPG_PASSPHRASE - GPG key passphrase
# MAVEN_CENTRAL_USERNAME - Maven Central portal API token username
# MAVEN_CENTRAL_PASSWORD - Maven Central portal API token password
#
# Release process:
# git tag v1.3
# git push origin v1.3
#
# The version published is the tag without its leading "v"; pom.xml's
# version is set from it for the build and is not committed back.
#
# To check the secrets and signing without publishing, run the workflow by
# hand (Actions -> Release to Maven Central -> Run workflow) with dry_run
# ticked: it builds, tests and signs everything and skips the upload.

name: Release to Maven Central

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
dry_run:
description: 'Build, test and sign, but do not publish'
type: boolean
default: true

permissions:
contents: read

concurrency:
group: release
cancel-in-progress: false

jobs:
deploy:
name: Deploy to Maven Central
# libjcocoa is built with xcodebuild, so the release runs on macOS.
runs-on: macos-15
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Determine release version
shell: bash
env:
DRY_RUN: ${{ inputs.dry_run }}
run: |
if [[ "$GITHUB_REF_TYPE" == tag && "$GITHUB_REF_NAME" == v* ]]; then
VERSION="${GITHUB_REF_NAME#v}"
elif [[ "$DRY_RUN" == true ]]; then
VERSION="0.0.0-dryrun-${GITHUB_SHA::7}"
else
echo "::error::Publishing needs a v* tag (got $GITHUB_REF_NAME); tick dry_run to test from a branch"
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "Release version: $VERSION"

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '11'
cache: maven

# Pinned rather than the runner image's Maven: swingwebview's Central
# upload was rejected ("Bundle has content that does NOT have a .pom
# file") after the image moved to a Maven whose install plugin leaves
# maven-metadata-central-staging.xml where central-publishing 0.7.0 no
# longer deletes it. 3.9.11 publishes cleanly. Verified against the
# published .sha512.
- name: Install pinned Maven
shell: bash
env:
MAVEN_VERSION: 3.9.11
run: |
set -euo pipefail
base="https://repo1.maven.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}"
tgz="apache-maven-${MAVEN_VERSION}-bin.tar.gz"
curl -fsSL --retry 5 -o "$RUNNER_TEMP/$tgz" "$base/$tgz"
echo "$(curl -fsSL --retry 5 "$base/$tgz.sha512") $RUNNER_TEMP/$tgz" | shasum -a 512 -c -
tar xzf "$RUNNER_TEMP/$tgz" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH"
"$RUNNER_TEMP/apache-maven-${MAVEN_VERSION}/bin/mvn" -v

# Credentials stay as ${env.*} lookups, resolved in the deploy step.
- name: Write Maven settings
shell: bash
run: |
set -euo pipefail
mkdir -p ~/.m2
cat > ~/.m2/settings.xml <<'EOF'
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<servers>
<server>
<id>central</id>
<username>${env.MAVEN_CENTRAL_USERNAME}</username>
<password>${env.MAVEN_CENTRAL_PASSWORD}</password>
</server>
</servers>
</settings>
EOF

- name: Import GPG key
shell: bash
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_TTY: /dev/null
run: |
set -euo pipefail
command -v gpg >/dev/null || brew install gnupg
# macOS's base64 has no --ignore-garbage; decode with Python so
# line breaks, spaces or a trailing CR in the secret are ignored.
printf '%s' "$GPG_PRIVATE_KEY" \
| python3 -c 'import base64, sys; sys.stdout.buffer.write(base64.b64decode("".join(sys.stdin.read().split())))' \
| gpg --batch --import
gpg --list-secret-keys --keyid-format long

- name: Set Maven project version
shell: bash
run: |
mvn versions:set \
-DnewVersion="${{ env.VERSION }}" \
-DgenerateBackupPoms=false \
--batch-mode -ntp

# One build: -Drelease=true builds the universal Release libjcocoa and
# attaches sources, javadoc and signatures; the tests run against that
# library before anything is uploaded.
- name: Build and test the release
shell: bash
run: mvn -B -ntp clean verify -Drelease=true -Dgpg.passphrase="$GPG_PASSPHRASE"
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}

- name: libjcocoa is universal
shell: bash
run: |
set -euo pipefail
archs=$(lipo -archs target/classes/libjcocoa.dylib)
echo "libjcocoa.dylib: $archs"
for a in arm64 x86_64; do
[[ " $archs " == *" $a "* ]] || { echo "::error::libjcocoa.dylib has no $a slice"; exit 1; }
done
unzip -l "target/java-objc-bridge-${VERSION}.jar" | grep -q ' libjcocoa.dylib$' \
|| { echo "::error::libjcocoa.dylib is not in the jar"; exit 1; }

# No clean: publishes the tree built and tested above.
- name: Publish to Maven Central
if: ${{ !inputs.dry_run }}
shell: bash
run: |
mvn -B -ntp deploy \
-DskipTests \
-Drelease=true \
-Pcentral-deploy \
-Dgpg.passphrase="$GPG_PASSPHRASE"
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}

- name: Clean up GPG keys
if: always()
shell: bash
run: |
gpg --batch --yes --delete-secret-and-public-keys \
$(gpg --list-secret-keys --keyid-format long | grep sec | awk '{print $2}' | cut -d'/' -f2) 2>/dev/null || true
rm -rf ~/.gnupg
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ Add the following dependency to your pom.xml:
</dependency>
```

### Releasing

Releases are published to Maven Central by `.github/workflows/release.yml` when a `v*` tag is pushed:

```
git tag v1.3
git push origin v1.3
```

The version is the tag without the `v`. The workflow builds the universal Release `libjcocoa`, runs the tests against it, signs the artifacts, and publishes through the Central Portal. To check the signing setup without publishing, run the workflow by hand from the Actions tab with **dry_run** ticked.

### Hardened runtime and the Mac App Store

By default the bridge extracts the `libjcocoa.dylib` inside its jar to a temporary file and loads it from there. An app signed with the hardened runtime (as notarized and Mac App Store apps are) can only load libraries signed by its own team, unless it has the `com.apple.security.cs.disable-library-validation` entitlement, so it cannot load that extracted copy.
Expand Down
39 changes: 31 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,37 @@
</plugins>
</build>
</profile>

<!--
Publishes to Maven Central through the Central Portal (OSSRH, which
the old nexus-staging distributionManagement pointed at, is retired).
central-publishing-maven-plugin replaces deploy:deploy while this
profile is active. Used with the release profile, which builds the
universal Release libjcocoa and attaches sources, javadoc and GPG
signatures:

mvn deploy -Drelease=true -Pcentral-deploy -Dgpg.passphrase=...

Credentials come from the "central" server in settings.xml. See
.github/workflows/release.yml, which runs this on a v* tag.
-->
<profile>
<id>central-deploy</id>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.7.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<properties>
Expand Down Expand Up @@ -90,14 +121,6 @@
<url>https://github.com/shannah/Java-Objective-C-Bridge</url>
</scm>

<distributionManagement>
<repository>
<id>nexus-staging</id>
<name>Nexus Release Repository</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<build>
<extensions>
<extension>
Expand Down
Loading