This document describes the automated unified release workflow for building and publishing JNexus artifacts across all platforms (Desktop, Android, iOS, macOS) from a single git tag.
The Unified Release workflow (.github/workflows/release.yml) is triggered by pushing a version tag and automatically:
- Validates version consistency across all platforms
- Builds artifacts for all 4 platforms in parallel
- Creates a GitHub Release with all artifacts
- Deploys the Desktop JAR to PackageCloud.io
- Sends release notifications
Trigger: Push a version tag matching v*
git tag v2.1.0
git push origin v2.1.0
# Single command triggers builds for all platformsBefore pushing a release tag, ensure:
Update versions in these files to match the release version (e.g., 2.1.0):
Desktop (Maven):
<!-- pom.xml -->
<version>2.1.0</version>Core Library (Gradle):
// jnexus-core/build.gradle
version = '2.1.0'iOS/macOS (Xcode):
<!-- jnexus-ios/iOS/Info.plist -->
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>
<!-- jnexus-ios/macOS/Info.plist -->
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>Android (Gradle):
// jnexus-android/build.gradle
version = '2.1.0'Add a section for the new version at the top of CHANGELOG.md:
## [2.1.0] - 2026-06-15
### Added
- New feature 1
- New feature 2
### Fixed
- Bug fix 1
- Bug fix 2
### Changed
- Enhancement 1Test that all builds succeed:
# Desktop
./mvnw clean package
# Android
gradle :jnexus-core:build
gradle :jnexus-android:assembleDebug
# iOS (on macOS)
cd jnexus-ios
xcodebuild -scheme JNexus build
xcodebuild -scheme JNexus-macOS buildgit add pom.xml jnexus-core/build.gradle jnexus-ios/iOS/Info.plist \
jnexus-ios/macOS/Info.plist CHANGELOG.md
git commit -m "chore: bump version to 2.1.0"
git push origin main# Create annotated tag
git tag -a v2.1.0 -m "Release v2.1.0: Description of changes"
# Push tag to GitHub (triggers the unified release workflow)
git push origin v2.1.0Check progress on GitHub:
- Go to repository → Actions tab
- Click Unified Release workflow
- Watch jobs run in parallel:
- ✓ Prepare (validate versions)
- ✓ Build Desktop JAR
- ✓ Build Android APK
- ✓ Build iOS IPA
- ✓ Build macOS DMG
- ✓ Create GitHub Release
- ✓ Deploy to PackageCloud
Expected time: 10-15 minutes (iOS/macOS builds are slowest)
After workflow completes:
- Check Releases page: https://github.com/FlossWare/nexus-java/releases
- Verify all artifacts are attached:
- ✓
jnexus-*-jar-with-dependencies.jar - ✓
jnexus-android-2.1.0.apk - ✓
jnexus-ios-2.1.0.ipa(if successful) - ✓
jnexus-macos-2.1.0.dmg(if successful)
- ✓
- Verify release notes were extracted from CHANGELOG.md
prepare
├── get_version
├── extract_changelog
└── verify_version_consistency
│
├──> build-desktop (build Desktop JAR)
├──> build-android (build Android APK)
├──> build-ios (build iOS IPA)
└──> build-macos (build macOS DMG)
│
└──> create-release (GitHub Release with all artifacts)
│
├──> deploy (PackageCloud.io)
└──> notify (release summary)
- Runs on: Ubuntu (fastest)
- Time: ~30 seconds
- Tasks:
- Extract version from tag (e.g.,
v2.1.0→2.1.0) - Extract changelog section from CHANGELOG.md
- Validate version consistency across pom.xml, build.gradle, Info.plist files
- Output version and changelog for use by other jobs
- Extract version from tag (e.g.,
- Runs on: Ubuntu
- Time: ~5 minutes
- Output: Desktop JAR artifact
- Commands:
mvn clean package -DskipTests
- Runs on: Ubuntu
- Time: ~8 minutes
- Output: Android APK artifact
- Commands:
gradle :jnexus-core:build gradle :jnexus-android:assembleDebug
- Runs on: macOS (required for Xcode)
- Time: ~10 minutes
- Output: iOS IPA artifact (may fail without signing certificates)
- Commands:
xcodebuild -scheme JNexus \ -configuration Release \ -sdk iphoneos \ -archivePath build/JNexus.xcarchive \ archive xcodebuild -exportArchive ...
- Runs on: macOS (required for Xcode)
- Time: ~8 minutes
- Output: macOS DMG artifact
- Commands:
xcodebuild -scheme JNexus-macOS \ -configuration Release \ -sdk macosx \ -archivePath build/JNexus-macOS.xcarchive \ archive hdiutil create ... (create DMG)
- Runs on: Ubuntu
- Time: ~1 minute
- Tasks:
- Download all artifacts from previous jobs
- Create GitHub Release
- Attach all artifacts
- Post release notes from CHANGELOG.md
- Does NOT fail if iOS/macOS jobs fail (uses
if: always())
- Runs on: Ubuntu
- Time: ~1 minute
- Tasks:
- Download Desktop JAR
- Upload to PackageCloud.io
- Skips gracefully if PACKAGECLOUD_TOKEN not set
- Runs on: Ubuntu
- Time: ~30 seconds
- Tasks:
- Post release summary
- Show all artifacts built
- Link to release page
Jobs run in parallel where possible for speed:
Total time: ~10-15 minutes
├─ prepare: 30s
├─ build-desktop: 5m (parallel)
├─ build-android: 8m (parallel)
├─ build-ios: 10m (parallel with android/desktop)
├─ build-macos: 8m (parallel with others)
└─ create-release: 1m (waits for all builds)
Configure these in GitHub Settings → Secrets and variables → Actions:
| Secret | Description | Required | Example |
|---|---|---|---|
PACKAGECLOUD_TOKEN |
PackageCloud API token | ✅ (for Deploy job) | abc123... |
GITHUB_TOKEN |
GitHub API token (auto-provided) | ✅ | (automatic) |
Get PackageCloud token:
- Go to PackageCloud.io → Account Settings
- Copy API token
- Add to GitHub Secrets:
PACKAGECLOUD_TOKEN
If a release fails or needs to be removed:
# Delete local tag
git tag -d v2.1.0
# Delete remote tag
git push origin :refs/tags/v2.1.0
# Verify deletion
git tag -l | grep v2.1.0 # Should return nothing# Using GitHub CLI
gh release delete v2.1.0 --yes
# Or manually via GitHub web UI:
# 1. Go to Releases page
# 2. Click the release
# 3. Click "Delete"# Fix the issue
git add <fixed-files>
git commit -m "fix: resolve release issue"
# Increment version
# (e.g., 2.1.0 → 2.1.1)
# Update version files and CHANGELOG.md
# Then retry:
git tag v2.1.1
git push origin v2.1.1Problem: prepare job fails with "version mismatch"
Solution: Ensure all version files match the tag:
VERSION="2.1.0" # Without 'v' prefix
# Check versions
grep "<version>${VERSION}</version>" pom.xml
grep "version = '${VERSION}'" jnexus-core/build.gradle
grep "<string>${VERSION}</string>" jnexus-ios/iOS/Info.plist
grep "<string>${VERSION}</string>" jnexus-ios/macOS/Info.plist
# Update any that don't match, then re-tag:
git add pom.xml jnexus-core/build.gradle jnexus-ios/iOS/Info.plist jnexus-ios/macOS/Info.plist
git commit --amend --no-edit
git tag -d v${VERSION}
git tag -a v${VERSION} -m "Release v${VERSION}"
git push origin v${VERSION} --forceProblem: Desktop/Android/iOS build fails
Solution: Check the build logs:
- Go to Actions tab → Unified Release → Failed job
- Click the job name to see full logs
- Fix the issue locally, then retry:
# Re-push the same tag
git tag -d v2.1.0
git tag -a v2.1.0 -m "Release v2.1.0"
git push origin v2.1.0 --forceProblem: iOS or macOS builds show warnings/errors
Expected: iOS/macOS builds in CI require:
- Apple Developer account
- Signing certificates (Base64-encoded)
- Provisioning profiles
- Keychain setup
Current Status: Builds will fail without signing setup, but GitHub Release is still created with Desktop and Android artifacts.
To enable iOS/macOS signing:
See the secrets setup section below.
Problem: Release notes show "Release version 2.1.0" instead of actual changelog
Solution: Ensure CHANGELOG.md has correct format:
## [2.1.0] - 2026-06-15
### Added
- New feature
## [2.0.0] - 2026-06-01
### Added
- Previous featureThe workflow extracts text between ## [VERSION] and next ## [, so format is critical.
Problem: Deploy job shows "deployment failed"
Solution: Check PackageCloud token:
# Verify token is set
gh secret list | grep PACKAGECLOUD
# If not set:
gh secret set PACKAGECLOUD_TOKEN
# (paste token, press Ctrl+D)Deployment gracefully fails if token is not set (still creates GitHub Release).
To enable iOS IPA building and signing in CI:
On your Mac with Xcode:
# Open Keychain Access
open /Applications/Utilities/Keychain\ Access.app
# Right-click certificate → Export
# Save as: certificate.p12
# Enter password (remember it)
# Encode for GitHub
base64 -i certificate.p12 | pbcopy# File is here:
~/Library/MobileDevice/Provisioning\ Profiles/
# List profiles
ls ~/Library/MobileDevice/Provisioning\ Profiles/
# Choose the correct one for your app
# Encode for GitHub
base64 -i ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision | pbcopyIn GitHub Settings → Secrets:
IOS_BUILD_CERTIFICATE (base64-encoded .p12)
IOS_P12_PASSWORD (password for .p12)
IOS_KEYCHAIN_PASSWORD (temporary keychain password)
IOS_PROVISIONING_PROFILE (base64-encoded .mobileprovision)
To enable release APK signing:
keytool -genkey -v \
-keystore jnexus.keystore \
-alias jnexus \
-keyalg RSA \
-keysize 2048 \
-validity 10000
# Remember the passwords!base64 -i jnexus.keystore | pbcopyIn GitHub Settings → Secrets:
ANDROID_SIGNING_KEY (base64-encoded keystore)
ANDROID_KEY_ALIAS (jnexus)
ANDROID_KEYSTORE_PASSWORD (your password)
ANDROID_KEY_PASSWORD (your password)
Uncomment the signing step in .github/workflows/release.yml:
- name: Sign APK
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: jnexus-android/build/outputs/apk/release
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_KEY_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}To manually control the release process:
# 1. Build locally
./mvnw clean package # Desktop
gradle :jnexus-android:assembleDebug # Android
cd jnexus-ios && xcodebuild archive # iOS
# 2. Create GitHub Release
gh release create v2.1.0 \
--title "Release v2.1.0" \
--notes-file release-notes.md \
target/jnexus-*-jar-with-dependencies.jar \
jnexus-android/build/outputs/apk/debug/jnexus-android-debug.apk
# 3. Deploy JAR
curl -X POST \
-H "Authorization: Bearer $PACKAGECLOUD_TOKEN" \
-F "package[distro_version_id]=190" \
-F "package[package_file]=@target/jnexus-*-jar-with-dependencies.jar" \
https://packagecloud.io/api/v1/repos/flossware/jnexus/packages.json- Updated version in pom.xml
- Updated version in jnexus-core/build.gradle
- Updated version in jnexus-ios/iOS/Info.plist
- Updated version in jnexus-ios/macOS/Info.plist
- Updated CHANGELOG.md with release notes
- All builds pass locally (
./mvnw clean package,gradle build,xcodebuild archive) - Committed version changes to main branch
- Created version tag:
git tag v2.1.0 - Pushed tag:
git push origin v2.1.0 - Verified GitHub Actions workflow runs
- Checked GitHub Release page for all artifacts
- Verified PackageCloud deployment (if token configured)
- Posted release announcement (if applicable)
Contributors should be aware of this process:
Releases are now fully automated! Push a version tag and all platforms build, test, and release automatically.
See RELEASE_PROCESS.md for detailed steps.
Q: How do I release a hotfix?
# Fix the bug
git add .
git commit -m "fix: bug description"
# Increment patch version (1.0 → 1.0.1 if using X.Y.Z)
# OR increment minor version (1.0 → 1.1 if using X.Y)
# Update all version files + CHANGELOG.md
# Release as normal
git tag v1.1
git push origin v1.1Q: Can I skip the iOS build? A: The workflow continues even if iOS/macOS builds fail (GitHub Release is still created with Desktop and Android artifacts). iOS builds require Apple Developer account setup; see optional iOS signing section.
Q: How do I test the workflow locally?
A: Use act to simulate GitHub Actions:
act push --input tag=v2.1.0Q: What if I accidentally pushed a bad tag? A: See Rollback Process section above.
Q: How do I update release notes after creating a release? A: Edit the release on GitHub:
- Go to Releases page
- Click the release
- Click "Edit"
- Update notes and save
- CI-CD.md - General CI/CD configuration
- CONTRIBUTING.md - Contribution guidelines
- CHANGELOG.md - Release history