Release checklist (crates.io + PyPI)

Release checklist (crates.io + PyPI)

Use this when shipping both the Rust crate and the Python package (python-wrapper/). For Rust-only or Python-only hotfixes, follow the relevant sections only.

1) Version alignment (do this first)

Bump all of these to the same SemVer (e.g. 0.3.3):

File Field
Cargo.toml (repo root) [package] version
python-wrapper/pyproject.toml [project] version
python-wrapper/Cargo.toml [package] version
bindings/java/VERSION single line (no -SNAPSHOT at release)
bindings/java/rust-data-processing-jvm/pom.xml <version> (main artifact)
bindings/java/rust-data-processing-jvm/gradle.properties version=

The release script scripts/release.py (or ./scripts/release_tag.sh / ./scripts/release_tag.ps1) updates all of the above plus Cargo.lock (root + python-wrapper/), python-wrapper/uv.lock, and inserts a CHANGELOG.md stub. Edit the changelog body after the script runs (before confirming), or edit on main before the next release.

PyPI uses pyproject.toml as the distribution version; the extension crate version should match for maintainability.

README assets: Keep scope infographics under docs/images/ (repository root). README_CRATE.md (crates.io / docs.rs) and python-wrapper/README_PYPI.md (PyPI) use the Phase 3 hero phase-3-scope-overview.png; keep phase-2-scope-overview.png and phase-1-scope-overview.png for historical posts or docs that still reference earlier phases. Language doc indexes (docs/rust/README.md, docs/python/README.md, docs/java/README.md) use the same Phase 3 graphic via relative paths. Registry READMEs use raw.githubusercontent.com for the hero image so PyPI/crates.io always resolve the current graphic (PyPI avoids bundling a duplicate PNG in sdist; crates.io/docs.rs avoid stale or missing relative-path renders). The Planning/ tree is gitignored and is not a publish source for this asset.

2) Changelog + CI

  1. Finish the new section in CHANGELOG.md (replace the stub line if the script added one) (Keep a Changelog).
  2. Open a PR; ensure GitHub Actions are green:
  3. Merge to main.

3) Publish Rust crate (crates.io)

Preferred: after merging to main, push tag v* (use scripts/release.py / ./scripts/release_tag.sh / ./scripts/release_tag.ps1 - see §4) — rust_release.yml publishes via CRATES_IO_TOKEN.

Manual alternative (from repo root, after merge):

cargo fmt --check
cargo clippy -- -D warnings
cargo test
cargo publish --dry-run
cargo publish

cargo publish fails if that version already exists — bump and repeat. If you use CI for this version, do not also run cargo publish locally.

3b) JVM bindings (rdp-jvm-sys + Maven + Gradle) — Phase 3

Location Bump / notes
bindings/java/VERSION Single source — sync pom.xml, gradle.properties, bindings/java/rdp-jvm-sys/pom.xml
bindings/jvm-sys/Cargo.toml Native crate semver (coordinate with FFI ABI rdp_ffi_abi_version)
bindings/java/rust-data-processing-jvm/pom.xml <version> after VERSION edit

Consistency guard (run before tagging):

python scripts/check_java_version_consistency.py
python scripts/check_jvm_ffi_manifest.py

Smoke build (mirror CI — explicit native path on each runner):

# Java formatting (same gate as Maven Central release `validate` / `mvn deploy`)
mvn -f bindings/java/rust-data-processing-jvm spotless:check
# on failure: mvn -f bindings/java/rust-data-processing-jvm spotless:apply

cargo build --release --manifest-path bindings/jvm-sys/Cargo.toml --features full
export RDP_JVM_SYS="$(pwd)/bindings/jvm-sys/target/release/librdp_jvm_sys.so"
mvn -f bindings/java/rust-data-processing-jvm -q verify
( cd bindings/java/rust-data-processing-jvm && ./gradlew check publishToMavenLocal --no-daemon )

Native classifier smoke (Linux x86_64 — validates classpath loading without RDP_JVM_SYS):

./scripts/test_native_classifier_local.sh

Central publication (Java API JAR): docs/java/MAVEN_CENTRAL_PUBLISHING.md (namespace io.github.scorpio-datalake, tokens, GPG). CI: .github/workflows/jvm_maven_central_release.yml — runs when a GitHub Release is published and tag v{bindings/java/VERSION} matches a non-SNAPSHOT VERSION.

Central publication (native classifiers): CI: .github/workflows/jvm_native_maven_release.yml — same v{VERSION} gate; builds five platform classifiers and deploys via scripts/deploy_rdp_jvm_sys_native_jars.sh. Consumer docs: docs/java/NATIVE_ARTIFACT_PACKAGING.md.

Workflow: .github/workflows/jvm_bindings_ci.yml (Ubuntu / Windows / macOS × JDK 21).

4) Publish Python package (PyPI)

One-time setup (GitHub secrets)

Add two repository secrets under Settings → Secrets and variables → Actions (step-by-step: How_TO_deploy.md § GitHub: add secrets for crates.io and PyPI). Once CRATES_IO_TOKEN and PYPI_API_TOKEN are set, tagged releases can publish without further token setup.

Secret name Used by Created at
CRATES_IO_TOKEN .github/workflows/rust_release.yml crates.io → account → API tokens
PYPI_API_TOKEN .github/workflows/python_release.yml pypi.org → account → API tokens

Releases are not automatic on every merge: you choose when to cut a tag after main already contains the version bump and green CI.

Use one flow: complete steps 1–2 (changelog + merge to main), then run the release script (or the raw git commands below). The script can bump versions and commit in one step, or you can bump manually first. Do not also cargo publish locally for the same version, or the GitHub job will fail with “already uploaded”.

  1. Confirm python-wrapper/pyproject.toml version matches the Rust crate (step 1), or let the release script bump them.

  2. Merge your release PR into main and ensure CI is green. Push main to origin so HEAD matches origin/main (unless the script will push main and bump for you).

  3. From repo root, on main, run the release script (interactive: shows last v* tag, prompts for new SemVer, writes files, commits, pushes main, pushes v*):

    ./scripts/release_tag.sh

    Windows PowerShell:

    ./scripts/release_tag.ps1

    Or: python scripts/release.py / python scripts/release.py 0.2.0 --comment "Release notes" -y See --help for --dry-run, --skip-git (bump only), --no-commit, --allow-dirty.

    Equivalent manual commands:

    git fetch origin main
    git checkout main && git pull --ff-only origin main
    git tag -a v0.2.0 -m "Release v0.2.0"
    git push origin v0.2.0
  4. Actions runs:

Tags pointing at commits not on main are rejected (no publish).

Manual cargo publish (optional)

If you publish the Rust crate from your machine instead of rust_release.yml, skip pushing a tag until you are ready, or disable that workflow temporarily — avoid double-publishing the same version.

Local dry run (optional)

cd python-wrapper
uv run maturin build --release -o dist --find-interpreter --sdist
# Inspect python-wrapper/dist/*.whl and .tar.gz

Custom wheels (optional)

5) After publish

  1. GitHub Releases — create a release for tag vX.Y.Z; paste CHANGELOG notes.
  2. docs.rs — updates automatically for the published Rust version. If a version shows “failed to build”, open Builds on docs.rs: OOM is common for Polars-heavy crates; this repo sets [package.metadata.docs.rs] cargo-args = ["-j", "1"] in the root Cargo.toml. Publish a patch (e.g. 0.1.6) after that change so docs.rs rebuilds. For compiler errors, fix the reported issue and republish.
  3. PyPI — verify the new version appears and pip install rust-data-processing==X.Y.Z works.

Reference