This document describes the process for releasing qumat and qumat-qdp. The process is divided into three main phases:
If you don't have a GPG key yet, generate one:
# Generate a 4096-bit RSA key (use your Apache email) gpg --full-generate-key # Select: RSA and RSA, 4096 bits, your_name <your_id@apache.org> # Find your key ID gpg --list-secret-keys --keyid-format SHORT # Example output: rsa4096/8EEFC01F # Upload your public key to a key server gpg --keyserver keys.openpgp.org --send-keys <YOUR_KEY_ID> # Append your public key to the project KEYS file (gpg --list-sigs <YOUR_KEY_ID> && gpg --armor --export <YOUR_KEY_ID>) >> KEYS
Important: Store your .pypirc in your home directory (~/.pypirc), never in the project directory — it can accidentally be included in source distributions.
# Create ~/.pypirc (not in the project directory!) cat > ~/.pypirc << 'EOF' [testpypi] username = __token__ password = pypi-xxxxx [pypi] username = __token__ password = pypi-xxxxx EOF chmod 600 ~/.pypirc
We follow the Airflow-style release branching model:
main ────●────●────●────●────●──→ (development continues)
│
├── mahout-qumat-0.5.0-RC1 (tag)
│
└── v0.5-stable (branch) ──●──→ RC2 ──→ v0.5.0 ──→ v0.5.1
When ready to cut a release, create a stable branch from main:
git checkout main git pull upstream main git checkout -b v0.5-stable git push -u upstream v0.5-stable
Tag RCs on the stable branch:
git checkout v0.5-stable git tag -a mahout-qumat-0.5.0-RC1 -m "Release Candidate 1 for qumat 0.5.0" git push upstream mahout-qumat-0.5.0-RC1
If bugs are found during RC testing:
Fix on main first (keeps main up-to-date):
git checkout main # ... fix and commit ... git push upstream main
Cherry-pick to stable branch:
# Using cherry-picker tool (auto-creates PR) uvx cherry-picker <commit-hash> v0.5-stable
Tag new RC:
git tag -a mahout-qumat-0.5.0-RC2 -m "Release Candidate 2 for qumat 0.5.0" git push upstream mahout-qumat-0.5.0-RC2
The goal of this phase is to ensure the release candidate is stable and ready for a formal vote.
dev@mahout.apache.org mailing list or Slack.Update version numbers and build the artifacts locally.
Update Versions to RC: Ensure the version includes the rc suffix (e.g., 0.5.0rc1):
pyproject.toml — set version = "0.5.0rc1"qdp/Cargo.toml — set version = "0.1.0-rc1"Build:
# Build Qumat (pure Python — one wheel for all Python versions) uv build # Build Qumat-QDP (native Rust — one wheel per Python version) cd qdp/qdp-python uv tool run maturin build --release --interpreter python3.10 uv tool run maturin build --release --interpreter python3.11 uv tool run maturin build --release --interpreter python3.12
Output locations:
dist/qumat-0.5.0rc1-py3-none-any.whldist/qumat-0.5.0rc1.tar.gzqdp/target/wheels/qumat_qdp-0.1.0rc1-cp3XX-*.whlSign each artifact with your GPG key and generate SHA-512 checksums:
# Sign and hash Qumat artifacts cd dist for f in qumat-0.5.0rc1*; do gpg --armor --detach-sign "$f" sha512sum "$f" > "$f.sha512" done # Sign and hash Qumat-QDP wheels cd ../qdp/target/wheels for f in qumat_qdp-0.1.0rc1-*.whl; do gpg --armor --detach-sign "$f" sha512sum "$f" > "$f.sha512" done
This produces .asc (GPG signature) and .sha512 (checksum) files for each artifact. These are required by ATR for release validation.
Verify signatures locally:
gpg --verify qumat-0.5.0rc1.tar.gz.asc qumat-0.5.0rc1.tar.gz
Ensure ~/.pypirc is configured (see PyPI Token Setup).
Upload to TestPyPI first (recommended):
# Upload Qumat uv tool run twine upload --repository testpypi --config-file ~/.pypirc dist/* # Upload Qumat-QDP uv tool run twine upload --repository testpypi --config-file ~/.pypirc qdp/target/wheels/qumat_qdp-<version>-cp31{0,1,2}-*.whl
Test install from TestPyPI:
uv venv && source .venv/bin/activate uv pip install \ --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ \ --index-strategy unsafe-best-match \ qumat==0.5.0rc1 qumat-qdp==0.1.0rc1 pytest testing/
Upload to PyPI:
# Upload Qumat uv tool run twine upload --repository pypi --config-file ~/.pypirc dist/* # Upload Qumat-QDP (exclude Python versions outside requires-python) uv tool run twine upload --repository pypi --config-file ~/.pypirc qdp/target/wheels/qumat_qdp-<version>-cp31{0,1,2}-*.whl
Note: This makes the RC available on PyPI for testing with pip install --pre qumat==0.5.0rc1.
Use the script to generate the RC testing issue:
# Generate issue content (dry run) ./dev/generate-rc-issue.sh 0.5.0rc1 0.1.0rc1 "Qumat 0.5.0" # Create GitHub issue directly ./dev/generate-rc-issue.sh 0.5.0rc1 0.1.0rc1 "Qumat 0.5.0" | \ gh issue create --repo apache/mahout \ --title "Status of testing Apache Mahout Qumat 0.5.0rc1" \ --body-file -
The script generates an issue with:
This phase is executed by a PMC member or Release Manager using the ATR platform. The PMC votes on source artifacts, which are the canonical release artifacts for Apache projects.
qumat/dist/qumat-1.0.0.tar.gzqdp/qdp-python/target/wheels/qumat_qdp-1.0.0.tar.gzCheck the “Release Candidates” section in ATR to ensure:
Use the ATR platform to generate the Vote email template.
dev@mahout.apache.org.Once the vote passes:
https://dist.apache.org/repos/dist/release/mahout/.After the PMC vote passes, publish to PyPI by tagging the release:
# Tag the voted release on the stable branch git checkout v0.6-stable git tag -a v0.6.0 -m "Release 0.6.0" git push upstream v0.6.0
This triggers the release.yml GitHub Actions workflow which:
qumat (pure Python wheel + sdist)qumat-qdp (Rust/maturin wheels for Python 3.10/3.11/3.12 + sdist)To approve the deploy:
pypi → “Approve and deploy”Verify the release:
pip install qumat==0.6.0 pip install qumat-qdp==0.2.0 python -c "import qumat; print(qumat.__version__)"
Note: The release.yml workflow uses PyPI Trusted Publishing — no API tokens or .pypirc files needed. Authentication is handled via OIDC between GitHub Actions and PyPI. The trusted publishers are configured at:
git tag -a v1.0.0 -m "Release 1.0.0" git push origin v1.0.0
main to the next development version (e.g., 1.1.0.dev0).cd website npm run version 1.0.0This creates a snapshot of the current docs under
versioned_docs/version-1.0.0/ and adds the version to versions.json. Update docusaurus.config.ts to configure the new version label and path if needed.dev@mahout.apache.org.