Creating a Release - Paimon Mosaic

# Creating a Release

Guide for Release Managers of Apache Paimon Mosaic, following ASF Release Policy.

## Overview

The release process consists of:

1. Decide to release
2. Prepare for the release
3. Build a release candidate
4. Vote on the release candidate
5. If necessary, fix any issues and go back to step 3
6. Finalize the release
7. Promote the release

### Automated Publishing

When a version tag is pushed, the `Release` workflow orchestrates language-specific release jobs:

Component | Tag Pattern | Published To | Pre-release (`-rc`) Behavior | Ordering
--- | --- | --- | --- | ---
Rust crate | `v0.1.0` | crates.io | Dry-run only | Runs in parallel
Java binding | `v0.1.0` | Apache Nexus staging | Deploys to staging | Runs in parallel
Python binding | `v0.1.0` | PyPI | Publishes to TestPyPI | Publishes only after Rust and Java jobs succeed

The Release Manager's primary responsibility is managing the source release (tarball + signature) and coordinating the community vote. Language artifact publishing is handled by CI once the tag is pushed.

## Decide to Release

Anybody can propose a release on the dev mailing list, giving a short rationale and nominating a committer as Release Manager (including themselves).

Checklist:

- Community agrees to release
- A Release Manager is selected

## Prepare for the Release

### One-time Release Manager Setup

#### GPG Key

1. Install GnuPG:

       # macOS
       brew install gnupg

       # Ubuntu / Debian
       sudo apt install gnupg2

2. Generate a key pair (RSA 4096, does not expire, use your `@apache.org` email):

       gpg --full-gen-key

3. Find your key ID:

       gpg --list-keys --keyid-format short

4. Upload to key server:

       gpg --keyserver hkps://keyserver.ubuntu.com --send-keys <YOUR_KEY_ID>

5. Append to the project KEYS file:

       svn co https://dist.apache.org/repos/dist/release/paimon/ paimon-dist-release --depth=files
       cd paimon-dist-release
       (gpg --list-sigs <YOUR_KEY_ID> && gpg --armor --export <YOUR_KEY_ID>) >> KEYS
       svn ci -m "Add <YOUR_NAME>'s public key"

6. Configure Git to sign tags:

       git config --global user.signingkey <YOUR_KEY_ID>

#### GitHub Actions Secrets

Ensure the following repository secrets are configured:

Secret | Purpose
--- | ---
`CARGO_REGISTRY_TOKEN` | crates.io publishing
`NEXUS_STAGE_DEPLOYER_USER` | Apache Nexus staging
`NEXUS_STAGE_DEPLOYER_PW` | Apache Nexus staging
`GPG_SECRET_KEY` | Java artifact signing
`GPG_PASSPHRASE` | Java artifact signing
`PYPI_API_TOKEN` | PyPI publishing
`TEST_PYPI_API_TOKEN` | TestPyPI publishing

### Clone into a Fresh Workspace

    git clone https://github.com/apache/paimon-mosaic.git
    cd paimon-mosaic

### Set Up Environment Variables

    RELEASE_VERSION="0.1.0"
    SHORT_RELEASE_VERSION="0.1"
    NEXT_VERSION="0.2.0"
    RC_NUM="1"
    RELEASE_BRANCH="release-${SHORT_RELEASE_VERSION}"
    RC_TAG="v${RELEASE_VERSION}-rc${RC_NUM}"
    RELEASE_TAG="v${RELEASE_VERSION}"

### Generate Dependency License Manifests

ASF releases must include a declaration of third-party dependency licenses. Generate and commit dependency manifests on `main` before creating the release branch, so the release branch and source archive contain the same reviewed manifests.

    # Install cargo-deny (one-time)
    cargo install cargo-deny

    git checkout main
    git pull --ff-only origin main

    # Check all dependency licenses are approved
    python3 tools/dependencies.py check

    # Generate DEPENDENCIES.rust.tsv
    python3 tools/dependencies.py generate

    git add DEPENDENCIES.rust.tsv core/DEPENDENCIES.rust.tsv ffi/DEPENDENCIES.rust.tsv jni/DEPENDENCIES.rust.tsv
    git commit -m "chore: update dependency list for release ${RELEASE_VERSION}"
    git push origin main

Fix any license violations before proceeding. The generated files must be committed before tagging because `create_source_release.sh` builds the archive from the committed Git tree.

### Create a Release Branch

Create one stable release branch for the release line. Do not include the RC number in the branch name; RC attempts are represented by tags such as `v0.1.0-rc1` and `v0.1.0-rc2`.

    git checkout main
    git pull --ff-only origin main
    git checkout -b ${RELEASE_BRANCH}
    git push origin ${RELEASE_BRANCH}

### Bump Version on Main

After cutting the release branch, bump `main` to the next development version:

    git checkout main
    git pull --ff-only origin main
    cd tools
    OLD_VERSION=${RELEASE_VERSION} NEW_VERSION=${NEXT_VERSION}-SNAPSHOT ./update_branch_version.sh
    cd ..
    git push origin main

The script updates version strings in all `pom.xml` (matching both `${OLD_VERSION}` and `${OLD_VERSION}-SNAPSHOT`), `Cargo.toml` (excluding `target/`), and `python/pyproject.toml` files, then creates a commit.

## Build a Release Candidate

### Update Versions on the Release Branch

Before tagging, update the release branch to non-SNAPSHOT versions (removing `-SNAPSHOT` from `pom.xml`):

    git checkout ${RELEASE_BRANCH}
    git pull --ff-only origin ${RELEASE_BRANCH}
    cd tools
    OLD_VERSION=${RELEASE_VERSION}-SNAPSHOT NEW_VERSION=${RELEASE_VERSION} ./update_branch_version.sh
    cd ..

This ensures the Java artifacts deployed from the RC tag carry the final release version (e.g. `0.1.0`) rather than a SNAPSHOT suffix.

### Create the RC Tag

Push a signed RC tag to trigger CI workflows:

    git tag -s ${RC_TAG} -m "${RC_TAG}"
    git tag -v ${RC_TAG}
    git push origin ${RELEASE_BRANCH}
    git push origin ${RC_TAG}

After pushing, verify in GitHub Actions that the `Release` workflow succeeds:

- Rust release — dry-run check (does not publish for RC tags)
- Java release — builds native JNI libraries for 4 platforms, deploys JAR to Apache Nexus staging
- Python wheels — builds wheels for 4 platforms
- Python publish — publishes to TestPyPI only after the Rust, Java, and Python wheel jobs succeed

If only the Python publish job fails due to a transient PyPI or TestPyPI issue, use Re-run failed jobs on the `Release` workflow. The successful Rust, Java, and Python wheel jobs do not need to be repeated.

### Create Source Release Artifacts

Create source release artifacts from the same commit as the RC tag:

    git checkout ${RC_TAG}
    cd tools
    RELEASE_VERSION=${RELEASE_VERSION} ./create_source_release.sh

This creates the following under `tools/release/`:

- `apache-paimon-mosaic-${RELEASE_VERSION}-src.tgz` — source archive
- `apache-paimon-mosaic-${RELEASE_VERSION}-src.tgz.asc` — GPG signature
- `apache-paimon-mosaic-${RELEASE_VERSION}-src.tgz.sha512` — SHA-512 checksum

### Stage Artifacts to SVN

    svn checkout https://dist.apache.org/repos/dist/dev/paimon/ paimon-dist-dev --depth=immediates
    cd paimon-dist-dev
    mkdir paimon-mosaic-${RELEASE_VERSION}-rc${RC_NUM}
    cp ../paimon-mosaic/tools/release/apache-paimon-mosaic-${RELEASE_VERSION}-src.* \
       paimon-mosaic-${RELEASE_VERSION}-rc${RC_NUM}/
    svn add paimon-mosaic-${RELEASE_VERSION}-rc${RC_NUM}
    svn commit -m "Add paimon-mosaic ${RELEASE_VERSION} RC${RC_NUM}"

Checklist:

- RC tag pushed and the CI release workflow succeeded
- Source tarball, signature, and checksum staged to dist.apache.org dev
- Java artifacts deployed to Nexus staging repository
- Python wheels published to TestPyPI

## Vote on the Release Candidate

Start a vote on the dev mailing list.

Subject: `[VOTE] Release Apache Paimon Mosaic ${RELEASE_VERSION} (RC${RC_NUM})`

    Hi everyone,

    Please review and vote on release candidate #${RC_NUM} for Apache Paimon Mosaic ${RELEASE_VERSION}.

    [ ] +1 Approve the release
    [ ] +0 No opinion
    [ ] -1 Do not approve (please provide specific comments)

    The release candidate is available at:
    https://dist.apache.org/repos/dist/dev/paimon/paimon-mosaic-${RELEASE_VERSION}-rc${RC_NUM}/

    Git tag:
    https://github.com/apache/paimon-mosaic/releases/tag/${RC_TAG}

    Git commit:
    <RC_COMMIT_SHA>

    KEYS for signature verification:
    https://downloads.apache.org/paimon/KEYS

    Java artifacts (Nexus staging):
    https://repository.apache.org/content/repositories/orgapachepaimon-XXXX/

    Python wheels (TestPyPI):
    https://test.pypi.org/project/paimon-mosaic/${RELEASE_VERSION}rc${RC_NUM}/

    Verification guide:
    https://github.com/apache/paimon-mosaic/blob/main/docs/verifying-a-release-candidate.html

    The vote will be open for at least 72 hours.

    Thanks,
    Release Manager

After the vote passes, send a result email with subject: `[RESULT][VOTE] Release Apache Paimon Mosaic ${RELEASE_VERSION} (RC${RC_NUM})`

## Fix Any Issues

If the vote reveals issues:

1. Fix them on the release branch via normal PRs.
2. Remove the old RC from dist dev (optional):

       cd paimon-dist-dev
       svn remove paimon-mosaic-${RELEASE_VERSION}-rc${RC_NUM}
       svn commit -m "Remove paimon-mosaic ${RELEASE_VERSION} RC${RC_NUM} (superseded)"

3. Drop the Nexus staging repository via the Nexus UI.
4. Increment `RC_NUM`, then go back to Build a release candidate.

## Finalize the Release

### Push the Release Tag

Once the vote passes, create and push the final release tag. This triggers CI to publish to crates.io and PyPI automatically; PyPI publishing runs only after the Rust and Java release jobs succeed.

    git checkout ${RC_TAG}
    git tag -s ${RELEASE_TAG} -m "Release Apache Paimon Mosaic ${RELEASE_VERSION}"
    git tag -v ${RELEASE_TAG}
    git push origin ${RELEASE_TAG}

### Move Source Artifacts to the Release Repository

    svn mv -m "Release paimon-mosaic ${RELEASE_VERSION}" \
      https://dist.apache.org/repos/dist/dev/paimon/paimon-mosaic-${RELEASE_VERSION}-rc${RC_NUM} \
      https://dist.apache.org/repos/dist/release/paimon/paimon-mosaic-${RELEASE_VERSION}

### Release the Java Artifacts

1. Go to Apache Nexus Staging Repositories.
2. Find the staging repository for `orgapachepaimon-XXXX`.
3. Close the repository (runs validation rules).
4. Once closed, Release the repository to Maven Central.

### Verify Published Artifacts

- Rust: crates.io/crates/paimon-mosaic-core shows version `${RELEASE_VERSION}`
- Java: Maven Central shows version `${RELEASE_VERSION}`
- Python: PyPI — paimon-mosaic shows version `${RELEASE_VERSION}`

### Create GitHub Release

1. Go to Releases — New release.
2. Choose tag `v${RELEASE_VERSION}`.
3. Click Generate release notes and review.
4. Click Publish release.

Checklist:

- Release tag pushed; CI published to crates.io and PyPI
- Source artifacts moved to dist release
- Java artifacts released to Maven Central via Nexus
- GitHub Release created

## Promote the Release

### Update the Releases Page

Update the Releases page: move the released version from "Upcoming" to "Past Releases" with a summary of key features and a link to the GitHub release notes.

### Announce the Release

Wait at least 24 hours after finalizing. Send the announcement to `dev@paimon.apache.org` and `announce@apache.org` using your `@apache.org` email in plain text.

Subject: `[ANNOUNCE] Release Apache Paimon Mosaic ${RELEASE_VERSION}`

    The Apache Paimon community is pleased to announce the release of
    Apache Paimon Mosaic ${RELEASE_VERSION}.

    Apache Paimon Mosaic is a columnar-bucket hybrid format optimized for
    wide tables of Apache Paimon.

    Rust:   cargo add paimon-mosaic-core
    Java:   <dependency>
              <groupId>org.apache.paimon</groupId>
              <artifactId>mosaic</artifactId>
              <version>${RELEASE_VERSION}</version>
            </dependency>
    Python: pip install paimon-mosaic

    Release notes:
    https://github.com/apache/paimon-mosaic/releases/tag/v${RELEASE_VERSION}

    Thanks to all contributors!

### Clean Up Old Releases

ASF policy requires only the latest release in the dist release area. Remove older versions:

    svn rm -m "Remove old release" \
      https://dist.apache.org/repos/dist/release/paimon/paimon-mosaic-${OLD_VERSION}