This document describes the current release workflow for the XVM project.
# 1. Prepare (creates PR with staged artifacts)
# Version is automatically computed from version.properties on master
gh workflow run prepare-release.yml
# 2. Review staged artifacts (Maven Central, GitHub draft release)
# 3. Merge PR (moves master to next snapshot)
gh pr merge <PR-NUMBER>
# 4. Promote staged release manually
gh workflow run promote-release.yml --field release-version=0.4.4# Re-promote after failure (selective targets)
gh workflow run promote-release.yml \
--field release-version=0.4.4 \
--field promote-maven-central=false \
--field publish-github-release=true \
--field publish-gradle-plugin-portal=false- Maven Central Staging: https://oss.sonatype.org/#stagingRepositories
- GitHub Releases: https://github.com/xtclang/xvm/releases
- Plugin Portal: https://plugins.gradle.org/plugin/org.xtclang.xtc-plugin
The release process is two-phase:
- Prepare Phase: Stage artifacts for review
- Promote Phase: Publish staged artifacts to production (manual workflow_dispatch)
Not all publishing targets support staging equally:
| Target | Staging Support | Prepare Phase | Promote Phase |
|---|---|---|---|
| GitHub Packages (Maven) | ❌ None | Published immediately | Already live |
| Maven Central | ✅ Full staging | Artifacts in staging repo | Close & release to production |
| GitHub Release (zip) | ✅ Draft releases | Uploaded as DRAFT | Publish draft → public |
| Gradle Plugin Portal | ❌ None | Credentials validated only | Published immediately if enabled during promotion |
Important: GitHub Packages artifacts are immediately public when prepare-release runs. This is acceptable because:
- Few users consume artifacts from GitHub Packages (most use Maven Central)
- The PR approval gate still controls Maven Central publication
- GitHub Packages versions can be manually deleted if needed (unlike Maven Central)
| Feature | Benefit |
|---|---|
| Automatic version computation | No manual version entry - reads from version.properties |
| Release branches | Isolates release work from ongoing development |
| Automatic version bumps | Next snapshot version computed automatically |
| Staging before production | Review artifacts before they go live |
| PR-based release prep | Clear approval gate with full audit trail |
| Manual promotion | Explicit production release step |
┌─────────────────────────────────────────────────────────────────┐
│ 1. Prepare Release (Manual Trigger) │
├─────────────────────────────────────────────────────────────────┤
│ • Read version from master (e.g., 0.4.4-SNAPSHOT) │
│ • Compute release version (0.4.4) │
│ • Compute next snapshot (0.4.5-SNAPSHOT) │
│ • Create release/0.4.4 branch │
│ • Commit version 0.4.4 │
│ • Tag v0.4.4 │
│ • Commit version 0.4.5-SNAPSHOT │
│ • Build and stage artifacts: │
│ - Maven Central → Staging repository │
│ - GitHub Release → Draft │
│ - Gradle Plugin Portal → Validate credentials only │
│ • Create PR to master │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Review Staged Artifacts (Manual) │
├─────────────────────────────────────────────────────────────────┤
│ • Check Maven Central staging repository │
│ • Review GitHub draft release │
│ • Test staged artifacts │
│ • Update release notes │
│ • Complete PR checklist │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. Merge PR to Master │
├─────────────────────────────────────────────────────────────────┤
│ • Master updated to 0.4.5-SNAPSHOT │
│ • v0.4.4 tag points to release commit │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. Promote Release (Manual workflow_dispatch) │
├─────────────────────────────────────────────────────────────────┤
│ • Maven Central staging → Released │
│ • GitHub draft → Published │
│ • Gradle Plugin Portal → Published if enabled │
└─────────────────────────────────────────────────────────────────┘
Configure these secrets in GitHub repository settings (Settings → Secrets and variables → Actions):
GitHub Packages (required for all publishing):
GITHUB_TOKEN- ✅ Automatic (provided by GitHub Actions)GITHUB_ACTOR- ✅ Automatic (provided by GitHub Actions)
Maven Central (required):
ORG_XTCLANG_MAVEN_CENTRAL_USERNAME- Sonatype OSSRH usernameORG_XTCLANG_MAVEN_CENTRAL_PASSWORD- Sonatype OSSRH password/token
Signing (required for Maven Central):
ORG_XTCLANG_SIGNING_KEY_ID- GPG key ID (8-char short or full fingerprint)ORG_XTCLANG_SIGNING_PASSWORD- GPG key passwordORG_XTCLANG_SIGNING_KEY- GPG private key (ASCII-armored with escaped\n)
Gradle Plugin Portal (optional during manual promotion):
ORG_XTCLANG_GRADLE_PLUGIN_PORTAL_PUBLISH_KEY- API keyORG_XTCLANG_GRADLE_PLUGIN_PORTAL_PUBLISH_SECRET- API secret
Note: Workflows automatically convert these secrets to Gradle properties using ORG_GRADLE_PROJECT_* prefix.
For local testing, add to ~/.gradle/gradle.properties:
# GitHub Packages
githubUsername=your-github-username
githubPassword=ghp_YourPersonalAccessToken
# Maven Central
mavenCentralUsername=your-sonatype-username
mavenCentralPassword=your-sonatype-password
# Signing
signing.keyId=YOUR_KEY_ID
signing.password=your-key-password
signing.secretKeyRingFile=/path/to/secring.gpg
# Or for in-memory signing:
# signing.key=-----BEGIN PGP PRIVATE KEY BLOCK-----\n...\n-----END PGP PRIVATE KEY BLOCK-----
# Gradle Plugin Portal
gradle.publish.key=your-api-key
gradle.publish.secret=your-api-secretTrigger the prepare-release workflow:
# Simple - version computed from master
gh workflow run prepare-release.yml
# Test from a feature branch
gh workflow run prepare-release.yml --field branch=lagergren/release-stageWhat happens:
- ✅ Reads current version from
version.properties(e.g.,0.4.4-SNAPSHOT) - ✅ Computes release version (
0.4.4) and next snapshot (0.4.5-SNAPSHOT) - ✅ Creates
release/0.4.4branch - ✅ Updates
version.propertiesto0.4.4and commits - ✅ Creates tag
v0.4.4 - ✅ Updates
version.propertiesto0.4.5-SNAPSHOTand commits - ✅ Pushes branch and tag
- ✅ Builds from tag
v0.4.4 - ✅ Publishes Maven artifacts (runs
./gradlew publish):- GitHub Packages:
⚠️ Published immediately tomaven.pkg.github.com(no staging) - Maven Central: Staged in
orgxtclang-XXXXrepository (not live yet)
- GitHub Packages:
- ✅ Creates GitHub Release draft with XDK zip (not live yet)
- ✅ Validates Gradle Plugin Portal credentials (doesn't publish)
- ✅ Creates PR to
master
Time: ~10-15 minutes
The PR will contain a checklist. Complete each item:
- Go to https://oss.sonatype.org/
- Log in with Sonatype credentials
- Navigate to "Staging Repositories"
- Find
orgxtclang-XXXX - Verify:
- All artifacts present (xdk, javatools, plugin)
- POM files are correct
- Signatures (
.asc) are valid - Checksums (
.md5,.sha1) are correct
- Go to https://github.com/xtclang/xvm/releases
- Find draft release
vX.Y.Z - Verify:
- XDK distribution ZIP is attached
- Update release notes with actual changes
- Tag points to correct commit
Finding Your Staging Repository ID:
- Log in to https://oss.sonatype.org/
- Click "Staging Repositories" (left sidebar)
- Find repository starting with
orgxtclang-(e.g.,orgxtclang-1234) - The number at the end is your staging repo ID
Download URLs (replace 1234 with your actual staging repo ID):
# XDK JAR
curl -O https://oss.sonatype.org/service/local/repositories/orgxtclang-1234/content/org/xtclang/xdk/0.4.4/xdk-0.4.4.jar
# XDK POM
curl -O https://oss.sonatype.org/service/local/repositories/orgxtclang-1234/content/org/xtclang/xdk/0.4.4/xdk-0.4.4.pom
# Plugin JAR
curl -O https://oss.sonatype.org/service/local/repositories/orgxtclang-1234/content/org/xtclang/xtc-plugin/0.4.4/xtc-plugin-0.4.4.jar
# Verify signatures
curl -O https://oss.sonatype.org/service/local/repositories/orgxtclang-1234/content/org/xtclang/xdk/0.4.4/xdk-0.4.4.jar.asc
gpg --verify xdk-0.4.4.jar.asc xdk-0.4.4.jarWhen all checks pass and artifacts are verified:
gh pr merge <PR_NUMBER>Or click "Merge pull request" in the GitHub UI.
What happens on merge:
- ✅ PR merged to
master - ✅ Master is now at
0.4.5-SNAPSHOT - ❌ release promotion does not happen automatically
- ✅ GitHub Packages: No action needed (already published in prepare phase)
gh workflow run promote-release.yml \
--field release-version=0.4.4 \
--field promote-maven-central=true \
--field publish-github-release=true \
--field publish-gradle-plugin-portal=falseIf you explicitly want Gradle Plugin Portal publication too:
gh workflow run promote-release.yml \
--field release-version=0.4.4 \
--field promote-maven-central=true \
--field publish-github-release=true \
--field publish-gradle-plugin-portal=trueAfter merge completes:
-
Wait for Maven Central sync (10-30 minutes)
-
Verify published artifacts:
# Maven Central curl -I https://repo1.maven.org/maven2/org/xtclang/xdk/0.4.4/xdk-0.4.4.jar # GitHub Release curl -I https://github.com/xtclang/xvm/releases/download/v0.4.4/xdk-0.4.4.zip # Gradle Plugin Portal curl -I https://plugins.gradle.org/m2/org/xtclang/xtc-plugin/0.4.4/xtc-plugin-0.4.4.jar
-
Announce the release
- Update documentation
- Post to social media
- Notify users
If you need to abort a prepared release before merging the PR:
# Close the PR without merging
gh pr close <PR_NUMBER>
# Drop Maven Central staging repository manually
# Log into https://oss.sonatype.org/, find your staging repo, and click "Drop"
# Delete draft release
gh release delete v0.4.4 --yes
# Delete tag
git push origin :v0.4.4
# Delete branch
git push origin :release/0.4.4Note: GitHub Packages artifacts cannot be automatically deleted but can be removed manually if needed.
Once promoted to production:
- ❌ Maven Central: Cannot delete (can only mark as deprecated)
- ❌ Gradle Plugin Portal: Cannot delete or unpublish
- ✅ GitHub Release: Can be deleted or marked as pre-release
Best practice: Only merge when you're 100% certain.
Problem: Manual version entry is error-prone and requires remembering the next version.
Solution:
- Read current version from
version.propertieson master - Compute release version by stripping
-SNAPSHOT - Compute next version by incrementing patch and adding
-SNAPSHOT
This ensures:
- ✅ Single source of truth (
version.properties) - ✅ No manual version entry errors
- ✅ Consistent version incrementing
Current workflow behavior is:
- credentials are validated during prepare
- publication is controlled explicitly during manual promotion
Commit 1: Release version (0.4.4)
- This is what gets built and released
- Tag
v0.4.4points here
Commit 2: Next snapshot (0.4.5-SNAPSHOT)
- This is what master becomes after merge
- Ensures master is always ready for development
Benefits:
- ✅ Release commit is clean (only release version)
- ✅ Master is automatically bumped after release
- ✅ No manual version editing needed
Alternative approaches:
- ❌ Manual button after PR merge → Extra step, easy to forget
- ❌ Separate workflow trigger → Requires remembering run-id
- ✅ Auto-promote on merge → One action = complete release
Safety: The PR review process is the approval gate. If you merge, you're committing to release.
The workflow supports releasing from any branch (not just master):
# Prepare release from feature branch
gh workflow run prepare-release.yml --field branch=feature/my-hotfixUse cases:
- Testing the release workflow on a feature branch
- Creating hotfix releases from maintenance branches
- Preparing releases for different product versions
For selective re-promotion after partial failures:
# Re-promote specific targets
gh workflow run promote-release.yml \
--field release-version=0.4.4 \
--field promote-maven-central=false \
--field publish-github-release=true \
--field publish-gradle-plugin-portal=falseScenarios:
Maven Central Succeeded, GitHub Release Failed:
gh workflow run promote-release.yml \
--field release-version=0.4.4 \
--field promote-maven-central=false \
--field publish-github-release=true \
--field publish-gradle-plugin-portal=falseOnly Publish Plugin Portal:
gh workflow run promote-release.yml \
--field release-version=0.4.4 \
--field promote-maven-central=false \
--field publish-github-release=false \
--field publish-gradle-plugin-portal=trueCause: Maven Central staging was dropped or already released.
Solution:
- Check https://oss.sonatype.org/ manually
- If dropped: Re-run prepare-release workflow
- If already released: Skip Maven Central promotion
Cause: Draft was manually deleted or already published.
Solution:
- Check https://github.com/xtclang/xvm/releases
- If published: Skip GitHub release promotion
- If deleted: Manually create release from tag
Cause: Credentials expired or incorrect.
Solution:
- Get new credentials from https://plugins.gradle.org/
- Update secrets in GitHub repository settings
- Re-run prepare-release workflow
Cause: Code issues on source branch.
Solution:
- Fix tests on the branch first
- Don't create release from broken code
Q: Can I release from a branch other than master?
A: Yes, use --field branch=your-branch-name. The PR will still target master.
Q: How do I know what version will be released?
A: The workflow reads version.properties from your branch and strips -SNAPSHOT. For example, 0.4.4-SNAPSHOT → 0.4.4.
Q: What if I want to release 0.5.0 instead of 0.4.5?
A: Update version.properties to 0.5.0-SNAPSHOT on master first, then run prepare-release. The auto-bump will create 0.5.1-SNAPSHOT.
Q: Can I skip Gradle Plugin Portal for a release?
A: Yes. The current promote-release.yml inputs allow that.
Q: How do I do a hotfix release?
A: Either use a maintenance branch with the appropriate snapshot version, or create from a release tag and update version.properties to the hotfix version (e.g., 0.4.4-hotfix.1-SNAPSHOT).
Q: Can I have multiple releases in progress? A: No, one release at a time per branch. Each release creates a PR that must be merged or closed before the next.
Modern release workflow with:
- ✅ Full automation (version computation, bumps, staging, promotion)
- ✅ Single source of truth (version.properties)
- ✅ Safety (review before production via PR approval)
- ✅ Simplicity (one merge = complete release)
- ✅ Audit trail (PR + workflow logs)
- ✅ Industry best practices (staging, signed artifacts, semantic versioning)
The manual steps are:
- Trigger prepare-release workflow (version computed automatically)
- Review staged artifacts
- Merge PR
- Trigger promote-release workflow