Copperlace Release Process
This runbook describes how to publish a Copperlace release across GitHub Release assets, PyPI, Maven Central, and npm.
For artifact contents and wrapper API details, see Packaging and language wrappers.
Release Prerequisites
Before tagging a release, confirm that:
-
the release commit is on
main; -
the working tree is clean, except for unrelated local files that will not be committed;
-
the package version is final and is not already published to PyPI, Maven Central, or npm;
-
the
Package Checkworkflow passes for the release commit; -
crates.io ownership is available for
copperlace; -
GitHub release publishing is available for the repository;
-
a PyPI API token is available for manual publication;
-
Maven Central credentials and GPG signing configuration are available for manual publication;
-
the npm package owner has permission to publish
copperlace.
The release workflow builds and smoke-tests release artifacts, then attaches them to the GitHub Release. Registry publication to crates.io, PyPI, Maven Central, and npm is manual after the generated packages have been checked.
Prepare the Version
Copperlace uses one lockstep package version for every released surface. Do not publish a release where the Rust, Python, Java, JS/TS, CLI, and GitHub Release artifact versions differ.
The committed package metadata is:
-
rust-core/Cargo.toml -
python/pyproject.toml -
java/pom.xml -
Java module parent versions under
java/api/,java/all-platform/, andjava/native/
js/pkg/package.json is generated by wasm-pack and must not be committed.
The generated version should come from rust-core/Cargo.toml.
Run the version check before tagging. It verifies all committed Copperlace package metadata uses the same version:
make release-check
The release tag must be the package version prefixed with v, such as
v0.1.1.
Validate Before Tagging
Run the full local check when the local toolchain is available:
make check
For package-specific validation, build the current-platform packages:
make package
The multi-platform Package Check GitHub Actions workflow is the release gate
for first-class platform artifacts. It builds and smoke-tests:
-
Python wheels for Linux x86_64/aarch64, macOS x86_64/aarch64, and Windows x86_64;
-
one Python source distribution;
-
Java API and native JARs for the same first-class native platforms;
-
CLI archives for the same first-class native platforms;
-
the JS/TS WebAssembly package tarball.
Do not push the release tag until the package-check workflow is green on the commit being released.
Tag and Publish Automated Artifacts
Create and push the release tag from the release commit:
VERSION=$(python scripts/check_versions.py)
git tag "v${VERSION}"
git push origin "v${VERSION}"
Pushing a v* tag starts .github/workflows/release.yml. The workflow:
-
verifies the tag matches the Cargo, Python, and Java package version;
-
builds and smoke-tests Python wheels and the source distribution;
-
builds and smoke-tests Java API, all-platform, and platform native JARs;
-
builds and smoke-tests platform CLI archives;
-
builds, packs, and smoke-tests the JS/TS WebAssembly tarball;
-
generates
SHA256SUMS; -
creates or updates the GitHub Release for the tag.
Wait for the full release workflow to finish before publishing registry packages from its artifacts or from the release tag.
Publish Rust to crates.io
Publish the Rust crate from the release tag after the release commit has passed checks and before or after pushing the GitHub release tag. The crate version is immutable once accepted by crates.io.
VERSION=$(python scripts/check_versions.py)
git switch --detach "v${VERSION}"
cargo fmt --check --manifest-path rust-core/Cargo.toml
cargo test --manifest-path rust-core/Cargo.toml
cargo clippy --manifest-path rust-core/Cargo.toml --all-targets -- -D warnings
RUSTDOCFLAGS=-Dwarnings cargo doc --manifest-path rust-core/Cargo.toml --no-deps
cargo package --list --manifest-path rust-core/Cargo.toml
cargo publish --dry-run --manifest-path rust-core/Cargo.toml
cargo publish --manifest-path rust-core/Cargo.toml
cargo package --list includes Cargo-generated files such as
.cargo_vcs_info.json and Cargo.toml.orig; those are expected. Do not publish
if the dry run warns about missing package metadata or fails verification.
Return to the normal branch after publishing:
git switch main
Publish Python to PyPI
PyPI publication is manual. For Linux x86_64 publication, build the wheel
inside a PyPA manylinux container, repair it with auditwheel, smoke-test the
repaired wheel, and upload only the repaired manylinux wheel.
From the repository root:
rm -rf python/build python/dist python/*.egg-info
podman run --rm -v "$PWD:/io" quay.io/pypa/manylinux2014_x86_64 /bin/bash -lc '
set -eux
curl https://sh.rustup.rs -sSf | sh -s -- -y
. "$HOME/.cargo/env"
cd /io/python
rm -rf build dist *.egg-info
/opt/python/cp310-cp310/bin/python -m pip install build auditwheel
/opt/python/cp310-cp310/bin/python -m build --wheel --outdir dist/raw
auditwheel repair dist/raw/*.whl --wheel-dir dist
'
python3 -m twine check python/dist/*manylinux*.whl
python3 scripts/smoke_python_wheel.py python/dist/*manylinux*.whl
python3 -m twine upload --verbose python/dist/*manylinux*.whl
Do not upload raw Linux wheels such as
copperlace-<version>-py3-none-linux_x86_64.whl; PyPI rejects the
linux_x86_64 platform tag. If auditwheel reports too-recent versioned
symbols, the wheel was built on a host that is too new. Rebuild inside the
manylinux container instead of repairing the host-built wheel.
On SELinux hosts, Podman may need a relabeled volume mount:
podman run --rm -v "$PWD:/io:Z" quay.io/pypa/manylinux2014_x86_64 /bin/bash -lc '...'
Publish Java to Maven Central
Maven Central publication is manual. Use the Java artifacts from the GitHub
release workflow, collect the native libraries into java/native-artifacts/,
then deploy with the all-platform and Central publishing profiles.
VERSION=$(python scripts/check_versions.py)
RELEASE_RUN_ID=<release-workflow-run-id>
gh run download "${RELEASE_RUN_ID}" --name 'java-jars-linux-x86_64' --dir java-release-artifacts
gh run download "${RELEASE_RUN_ID}" --name 'java-jars-linux-aarch64' --dir java-release-artifacts
gh run download "${RELEASE_RUN_ID}" --name 'java-jars-macos-x86_64' --dir java-release-artifacts
gh run download "${RELEASE_RUN_ID}" --name 'java-jars-macos-aarch64' --dir java-release-artifacts
gh run download "${RELEASE_RUN_ID}" --name 'java-jars-windows-x86_64' --dir java-release-artifacts
python scripts/collect_java_native_artifacts.py \
--output-dir java/native-artifacts \
java-release-artifacts
cd java
mvn -q -Pall-platform,central-publish \
-Dcopperlace.skip.current.native=true \
-Dgpg.passphrase="${GPG_PASSPHRASE}" \
deploy
The Maven command expects Central credentials in Maven settings under server id
central and local GPG signing material available to maven-gpg-plugin.
Publish JS/TS to npm
After the release workflow succeeds, publish the same generated JS/TS package version to npm.
Build the package locally from the release tag:
git switch --detach "v${VERSION}"
make js-package
cp LICENSE js/pkg/LICENSE
npm pack ./js/pkg --pack-destination /tmp
python scripts/smoke_js_package.py /tmp/copperlace-${VERSION}.tgz
Confirm the local npm client is authenticated as a package owner before
publishing. If npm whoami fails, log in and verify again:
npm whoami
npm login
npm whoami
Inspect what npm will publish:
npm publish ./js/pkg --dry-run
Publish from the generated package directory:
npm publish ./js/pkg
npm versions are immutable. If npm publication fails after any other package registry has accepted the release version, fix the npm issue without changing the existing release version unless a registry has permanently rejected that version.
Return to the normal branch after publishing:
git switch main
Post-Release Verification
Verify the GitHub Release contains:
-
one CLI archive for each first-class native platform;
-
all Python wheel files plus the source distribution;
-
Java API, all-platform, and platform native JARs;
-
the JS/TS WebAssembly package tarball;
-
SHA256SUMS.
Install-smoke each published package surface when practical:
python scripts/smoke_python_artifact.py --no-index <downloaded-wheel-or-sdist>
python scripts/smoke_java_artifacts.py --api-jar <api-jar> --native-jar <native-jar>
python scripts/smoke_cli_archive.py <downloaded-cli-archive>
python scripts/smoke_js_package.py <downloaded-js-tarball>
Also confirm the package pages are visible on:
-
PyPI for
copperlace; -
Maven Central for
dev.mahe.copperlace; -
npm for
copperlace; -
the GitHub Release page for the tag.
Failure Handling
If the release workflow fails before publishing to a package registry, fix the
problem on main, delete the failed local and remote tag if needed, and tag the
fixed commit with the same version.
If PyPI, Maven Central, or npm has accepted the version, treat the version as published. Do not move the tag to different source content. Fix any remaining release issue by rerunning the failed workflow job when safe, uploading missing GitHub Release assets, or preparing a new patch version.
If the GitHub Release upload fails after package registries have published,
rerun the release workflow or upload the missing assets from the workflow
artifacts. Regenerate SHA256SUMS for the final asset set.
If npm publication fails, first retry with the same generated package from the release tag. Only prepare a new version if npm permanently rejects the original version and the package was not published.