name: CD
on:
push:
branches: [main]
tags:
- "v*"
workflow_dispatch:
inputs:
release_type:
description: "Release type"
required: true
type: choice
options:
- beta
- stable
publish_existing_tag:
description: "Recovery only: publish an already-created tag (e.g. v0.1.2), skipping semantic-release. Use when the auto-computed version's tag name is permanently reserved by a GitHub immutable release."
required: false
type: string
default: ""
expected_version:
description: "Optional guard: abort before tagging unless the computed version is this one (e.g. 0.1.1, without the -beta.N suffix)."
required: false
type: string
default: ""
env:
RELEASE_TYPE: ${{ inputs.release_type }}
PITH_PLATFORMS: linux-x64 linux-arm64 darwin-arm64 darwin-x64 win32-x64
permissions:
contents: write
concurrency:
group: cd-${{ github.ref }}
cancel-in-progress: false
jobs:
readme-sync:
name: Verify README registry metadata
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 with:
python-version: "3.13"
- name: Verify README registry metadata
run: python scripts/repo-bootstrap/verify_readme_sync.py --repo-root=.
release:
name: Semantic Release
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
released: ${{ steps.release.outputs.released || steps.forced.outputs.released }}
tag: ${{ steps.release.outputs.tag || steps.forced.outputs.tag }}
version: ${{ steps.release.outputs.version || steps.forced.outputs.version }}
is_prerelease: ${{ steps.release.outputs.is_prerelease || steps.forced.outputs.is_prerelease }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with:
egress-policy: audit
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with:
app-id: ${{ vars.CI_APP_ID }}
private-key: ${{ secrets.CI_APP_KEY }}
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- id: plan
name: Plan the release (read-only)
uses: n24q02m/better-semantic-release@0e57c64481cb78492ad1ff8025b0cd368dbb695f if: inputs.publish_existing_tag == ''
with:
github_token: ${{ steps.app-token.outputs.token }}
config_file: semantic-release.toml
mode: plan
- name: Refuse a release that is not the expected version
if: inputs.publish_existing_tag == '' && inputs.expected_version != ''
shell: bash
env:
PLAN_JSON: ${{ steps.plan.outputs.plan_json }}
PLAN_BLOCKED: ${{ steps.plan.outputs.plan_blocked }}
EXPECTED: ${{ inputs.expected_version }}
run: |
python3 - <<'PYEOF'
import json, os, sys
plan = json.load(open(os.environ["PLAN_JSON"], encoding="utf-8"))
os.remove(os.environ["PLAN_JSON"]) # keep the worktree clean for the release commit
planned = (plan.get("version") or "").split("-")[0]
expected = os.environ["EXPECTED"].removeprefix("v")
print("planned:", plan.get("version"), "| expected:", expected, "| blocked:", os.environ["PLAN_BLOCKED"])
if os.environ["PLAN_BLOCKED"] == "true":
sys.exit("the release plan is blocked: " + json.dumps(plan.get("blockers")))
if planned != expected:
sys.exit("computed version " + str(plan.get("version")) + " is not the expected " + expected)
PYEOF
- name: Drop the plan file
if: always() && inputs.publish_existing_tag == ''
shell: bash
run: rm -f release-plan.json
- id: release
uses: n24q02m/better-semantic-release@0e57c64481cb78492ad1ff8025b0cd368dbb695f if: inputs.publish_existing_tag == ''
with:
github_token: ${{ steps.app-token.outputs.token }}
config_file: semantic-release.toml
prerelease: ${{ env.RELEASE_TYPE == 'beta' }}
prerelease_token: beta
- name: Confirm the tag carries the expected version
if: inputs.publish_existing_tag == '' && inputs.expected_version != '' && steps.release.outputs.released == 'true'
shell: bash
env:
VERSION: ${{ steps.release.outputs.version }}
EXPECTED: ${{ inputs.expected_version }}
run: |
base="${VERSION%%-*}"
[ "$base" = "${EXPECTED#v}" ] || { echo "released ${VERSION}, expected ${EXPECTED}"; exit 1; }
- if: inputs.publish_existing_tag == '' && steps.release.outputs.released == 'true'
uses: python-semantic-release/publish-action@5a5718ce47b892ef699f2972dae122297771d641 with:
github_token: ${{ steps.app-token.outputs.token }}
tag: ${{ steps.release.outputs.tag }}
- name: Use existing tag (immutable-release recovery)
id: forced
if: inputs.publish_existing_tag != ''
run: |
TAG="${{ inputs.publish_existing_tag }}"
VERSION="${TAG#v}"
{
echo "released=true"
echo "tag=$TAG"
echo "version=$VERSION"
# A hyphen in the version is a semver pre-release (0.1.1-beta.1).
case "$VERSION" in *-*) echo "is_prerelease=true" ;; *) echo "is_prerelease=false" ;; esac
} >> "$GITHUB_OUTPUT"
build:
name: Build SDK Artifacts (${{ matrix.platform }})
if: |
!cancelled() && needs.release.result != 'failure' && (
startsWith(github.ref, 'refs/tags/v') ||
(github.event_name == 'workflow_dispatch' && needs.release.outputs.released == 'true')
)
needs: [release]
runs-on: ${{ matrix.os }}
outputs:
has_python: ${{ steps.detect.outputs.has_python }}
has_node: ${{ steps.detect.outputs.has_node }}
has_java: ${{ steps.detect.outputs.has_java }}
stem: ${{ steps.stem.outputs.stem }}
env:
PITH_CDYLIB_DIR: target/release
strategy:
fail-fast: false
matrix:
include:
- { platform: linux-x64, os: ubuntu-latest }
- { platform: linux-arm64, os: ubuntu-24.04-arm }
- { platform: darwin-arm64, os: macos-latest }
- { platform: darwin-x64, os: macos-15-intel }
- { platform: win32-x64, os: windows-latest }
steps:
- name: Harden Runner
if: runner.os == 'Linux'
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
fetch-depth: 0
ref: ${{ needs.release.outputs.tag || github.ref }}
- name: Detect which SDKs this repo has
id: detect
shell: bash
run: |
{
for sdk in python:pyproject.toml node:package.json go:go.mod java:pom.xml; do
lang="${sdk%%:*}"
file="${sdk#*:}"
if [ -f "sdk/$lang/$file" ]; then
echo "has_$lang=true"
else
echo "has_$lang=false"
fi
done
} >> "$GITHUB_OUTPUT"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 with:
python-version: "3.13"
- name: Name the cdylib
id: stem
shell: bash
run: |
python - <<'PYEOF'
import json, os, subprocess, sys
meta = json.loads(subprocess.check_output(["cargo", "metadata", "--no-deps", "--format-version", "1", "--locked"]))
stems = sorted({t["name"].replace("-", "_") for p in meta["packages"] for t in p["targets"] if "cdylib" in t["crate_types"]})
if len(stems) != 1:
sys.exit("expected exactly one cdylib target in the workspace, found: %r" % stems)
print("cdylib stem:", stems[0])
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as out:
out.write("stem=" + stems[0] + chr(10))
PYEOF
- name: Refuse a tag whose manifests disagree
id: version-guard
shell: bash
env:
EXPECTED_VERSION: ${{ needs.release.outputs.version || github.ref_name }}
run: |
python -m pip install --quiet packaging
cargo metadata --no-deps --format-version 1 --locked > "$RUNNER_TEMP/crates.json"
CRATES_JSON="$RUNNER_TEMP/crates.json" python - <<'PYEOF'
import json, os, sys, tomllib
import xml.etree.ElementTree as ET
from pathlib import Path
from packaging.version import Version
expected = os.environ["EXPECTED_VERSION"].removeprefix("v")
seen = {}
crates = json.loads(Path(os.environ["CRATES_JSON"]).read_text(encoding="utf-8"))
for pkg in crates["packages"]:
seen["crate " + pkg["name"]] = pkg["version"]
node = Path("sdk/node/package.json")
if node.exists():
seen["sdk/node/package.json"] = json.loads(node.read_text(encoding="utf-8"))["version"]
pom = Path("sdk/java/pom.xml")
if pom.exists():
root = ET.parse(pom).getroot()
ns = root.tag[: root.tag.index("}") + 1] if root.tag.startswith("{") else ""
el = root.find(ns + "version")
seen["sdk/java/pom.xml"] = el.text.strip() if el is not None and el.text else "<missing>"
bad = [k for k, v in seen.items() if v != expected]
# PEP 440 spells a beta `0.1.1b1`, semver spells it `0.1.1-beta.1`: compare
# the pyproject version as a version, not as a string.
py = Path("sdk/python/pyproject.toml")
if py.exists():
found = tomllib.loads(py.read_text(encoding="utf-8")).get("project", {}).get("version")
if found is not None:
seen["sdk/python/pyproject.toml"] = found
if Version(found) != Version(expected):
bad.append("sdk/python/pyproject.toml")
for key, value in sorted(seen.items()):
print(("BAD " if key in bad else "ok ") + key + " = " + value)
if bad:
sys.exit("these manifests disagree with tag version " + expected + ": " + ", ".join(bad))
PYEOF
- name: Build the core cdylib
shell: bash
run: cargo build --workspace --release --locked
- name: Regenerate reference.json (hex-exact vectors)
shell: bash
run: |
cargo run --locked --bin gen-reference -- gen
# pith-math keeps its vectors in tests/ and its generator writes there; the
# other repos write ./reference.json directly. Either way the file that
# ships is the one in the repository root.
test -f reference.json || cp tests/reference.json reference.json
- name: Collect cdylibs for the SDKs
shell: bash
run: |
mkdir -p dist/sdk
find "$PITH_CDYLIB_DIR" -maxdepth 1 -type f \
\( -name '*.so' -o -name '*.dylib' -o -name '*.dll' \) \
-exec cp {} dist/sdk/ \;
ls -la dist/sdk
- name: Stage the cdylib under this platform's id
shell: bash
run: |
mkdir -p "dist/prebuilds/${{ matrix.platform }}" dist/assets
cp dist/sdk/* "dist/prebuilds/${{ matrix.platform }}/"
for f in dist/sdk/*; do cp "$f" "dist/assets/${{ matrix.platform }}-$(basename "$f")"; done
ls -la "dist/prebuilds/${{ matrix.platform }}"
- name: Build the Python wheel (ctypes)
if: steps.detect.outputs.has_python == 'true'
shell: bash
run: |
python -m pip install --upgrade build
python -m build --wheel sdk/python
- name: Repair the Linux wheel to manylinux
if: runner.os == 'Linux' && steps.detect.outputs.has_python == 'true'
shell: bash
run: |
python -m pip install --upgrade auditwheel
auditwheel repair sdk/python/dist/*.whl -w sdk/python/dist-manylinux
mv sdk/python/dist-manylinux/*.whl sdk/python/dist/
- name: Set up Go for the cgo binding
if: steps.detect.outputs.has_go == 'true'
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 with:
go-version-file: sdk/go/go.mod
- name: Go build and test
if: steps.detect.outputs.has_go == 'true'
shell: bash
env:
PITH_CDYLIB_DIR: ${{ github.workspace }}/target/release
run: |
cd sdk/go
CGO_ENABLED=1 go build ./...
CGO_ENABLED=1 go test ./...
- name: Upload SDK artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with:
name: sdk-${{ matrix.platform }}
path: |
dist/prebuilds/
dist/assets/
sdk/python/dist/
reference.json
- name: Attach build output to the release
continue-on-error: true
if: vars.PUBLISH_RELEASE_ASSET == 'true'
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 with:
tag_name: ${{ needs.release.outputs.tag || github.ref_name }}
files: |
dist/assets/*
reference.json
publish-crate:
name: Publish to crates.io
needs: [readme-sync, release, build]
if: |
!cancelled() && needs.build.result == 'success' &&
needs.readme-sync.result == 'success' &&
vars.PUBLISH_CRATE == 'true'
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
ref: ${{ needs.release.outputs.tag || github.ref }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c - name: Publish the crates crates.io does not have yet
shell: bash
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
cargo metadata --no-deps --format-version 1 --locked > "$RUNNER_TEMP/crates.json"
pending=()
while read -r name version; do
code="$(curl -sS -o /dev/null -w '%{http_code}' -A "pith-hash-cd (github.com/${GITHUB_REPOSITORY})" "https://crates.io/api/v1/crates/${name}/${version}")"
case "$code" in
200) echo "skip ${name} ${version}: already on crates.io" ;;
404) pending+=("-p" "$name") ;;
*) echo "crates.io answered HTTP ${code} for ${name} ${version}"; exit 1 ;;
esac
done < <(jq -r '.packages[] | select(.publish != []) | "\(.name) \(.version)"' "$RUNNER_TEMP/crates.json")
if [ "${#pending[@]}" -eq 0 ]; then
echo "every crate is already on crates.io"
exit 0
fi
cargo publish --locked "${pending[@]}"
publish-pypi:
name: Publish Python SDK to PyPI
needs: [readme-sync, release, build]
if: |
!cancelled() && needs.build.result == 'success' &&
needs.readme-sync.result == 'success' &&
needs.build.outputs.has_python == 'true' &&
vars.PUBLISH_PYPI == 'true'
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
ref: ${{ needs.release.outputs.tag || github.ref }}
- name: Download SDK artifacts from every platform
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with:
pattern: sdk-*
merge-multiple: true
path: wheelhouse
- name: Collect wheels
run: |
mkdir -p wheelhouse-flat
find wheelhouse -name '*.whl' ! -name '*-linux_*' -exec cp {} wheelhouse-flat/ \;
ls -la wheelhouse-flat/
- name: Check wheels
run: |
python -m pip install --upgrade twine
twine check wheelhouse-flat/*.whl
- name: Refuse a wheel set that misses a platform
env:
STEM: ${{ needs.build.outputs.stem }}
run: |
python scripts/check-package-contents.py wheels 'wheelhouse-flat/*.whl' --platforms "${PITH_PLATFORMS// /,}" --stem "$STEM"
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 with:
packages-dir: wheelhouse-flat
skip-existing: true
publish-npm:
name: Publish Node SDK to npm
needs: [readme-sync, release, build]
if: |
!cancelled() && needs.build.result == 'success' &&
needs.readme-sync.result == 'success' &&
needs.build.outputs.has_node == 'true' &&
vars.PUBLISH_NPM == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
ref: ${{ needs.release.outputs.tag || github.ref }}
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Check whether npm already has this version
id: exists
shell: bash
env:
VERSION: ${{ needs.release.outputs.version || github.ref_name }}
run: |
VERSION="${VERSION#v}"
NAME="$(node -p "require('./sdk/node/package.json').name")"
rc=0
out="$(npm view "${NAME}@${VERSION}" version 2>"$RUNNER_TEMP/npm-view.err")" || rc=$?
if [ "$rc" -eq 0 ] && [ "$out" = "$VERSION" ]; then
echo "${NAME}@${VERSION} is already on npm"
echo "skip=true" >> "$GITHUB_OUTPUT"
elif [ "$rc" -eq 0 ] && [ -z "$out" ]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
elif grep -q E404 "$RUNNER_TEMP/npm-view.err"; then
echo "skip=false" >> "$GITHUB_OUTPUT"
else
cat "$RUNNER_TEMP/npm-view.err"
exit 1
fi
- name: Download SDK artifacts from every platform
if: steps.exists.outputs.skip == 'false'
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with:
pattern: sdk-*
merge-multiple: true
path: sdk-dist
- name: Assemble the npm package with prebuilt cdylibs
if: steps.exists.outputs.skip == 'false'
id: pack
shell: bash
run: |
mkdir -p sdk/node/prebuilds
cp -R sdk-dist/dist/prebuilds/. sdk/node/prebuilds/
cp sdk-dist/reference.json sdk/node/
cd sdk/node
npm ci
TARBALL="$RUNNER_TEMP/$(npm pack --silent --pack-destination "$RUNNER_TEMP")"
echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT"
- name: Refuse a tarball that misses a platform
if: steps.exists.outputs.skip == 'false'
shell: bash
env:
STEM: ${{ needs.build.outputs.stem }}
TARBALL: ${{ steps.pack.outputs.tarball }}
run: |
python3 scripts/check-package-contents.py npm "$TARBALL" --platforms "${PITH_PLATFORMS// /,}" --stem "$STEM"
- name: Publish to npm
if: steps.exists.outputs.skip == 'false'
shell: bash
working-directory: sdk/node
env:
VERSION: ${{ needs.release.outputs.version || github.ref_name }}
TARBALL: ${{ steps.pack.outputs.tarball }}
run: |
case "${VERSION#v}" in
*-*) DIST_TAG=beta ;;
*) DIST_TAG=latest ;;
esac
npm publish "$TARBALL" --provenance --no-git-checks --tag "$DIST_TAG"
publish-maven:
name: Publish Java SDK to Maven Central
needs: [readme-sync, release, build]
if: |
!cancelled() && needs.build.result == 'success' &&
needs.readme-sync.result == 'success' &&
needs.build.outputs.has_java == 'true' &&
vars.PUBLISH_MAVEN == 'true'
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
ref: ${{ needs.release.outputs.tag || github.ref }}
- name: Check whether Maven Central already has this version
id: exists
shell: bash
env:
VERSION: ${{ needs.release.outputs.version || github.ref_name }}
run: |
VERSION="${VERSION#v}"
read -r GROUP ARTIFACT < <(python3 -c "import xml.etree.ElementTree as ET; r = ET.parse('sdk/java/pom.xml').getroot(); n = r.tag[: r.tag.index('}') + 1]; print(r.find(n + 'groupId').text, r.find(n + 'artifactId').text)")
URL="https://repo1.maven.org/maven2/${GROUP//.//}/${ARTIFACT}/${VERSION}/${ARTIFACT}-${VERSION}.pom"
code="$(curl -sS -o /dev/null -w '%{http_code}' "$URL")"
case "$code" in
200) echo "${GROUP}:${ARTIFACT}:${VERSION} is already on Maven Central"; echo "skip=true" >> "$GITHUB_OUTPUT" ;;
404) echo "skip=false" >> "$GITHUB_OUTPUT" ;;
*) echo "Maven Central answered HTTP ${code} for ${URL}"; exit 1 ;;
esac
- name: Download the cdylibs built by the matrix
if: steps.exists.outputs.skip == 'false'
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with:
pattern: sdk-*
merge-multiple: true
path: sdk-dist
- name: Bundle every platform's cdylib into the JAR
if: steps.exists.outputs.skip == 'false'
shell: bash
run: |
for platform in $PITH_PLATFORMS; do
dest="sdk/java/src/main/resources/hash/pith/native/$platform"
mkdir -p "$dest"
cp sdk-dist/dist/prebuilds/"$platform"/* "$dest"/
test -n "$(ls -A "$dest")" || { echo "no cdylib for $platform"; exit 1; }
done
find sdk/java/src/main/resources/hash/pith/native -type f
- name: Set up JDK 21 with Maven Central credentials and the GPG key
if: steps.exists.outputs.skip == 'false'
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 with:
distribution: temurin
java-version: "21"
server-id: central
server-username: CENTRAL_USERNAME
server-password: CENTRAL_PASSWORD
gpg-private-key: ${{ secrets.CENTRAL_GPG_PRIVATE_KEY }}
gpg-passphrase: GPG_PASSPHRASE
- name: Test, sign and bundle the Java SDK
if: steps.exists.outputs.skip == 'false'
shell: bash
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
GPG_PASSPHRASE: ${{ secrets.CENTRAL_GPG_PASSPHRASE }}
PITH_CDYLIB_DIR: ${{ github.workspace }}/sdk-dist/dist/prebuilds/linux-x64
run: |
mvn -B -Prelease verify -f sdk/java/pom.xml
mvn -B -Prelease -DskipTests -DcentralBaseUrl=http://127.0.0.1:9 deploy -f sdk/java/pom.xml || true
BUNDLE=sdk/java/target/central-publishing/central-bundle.zip
test -f "$BUNDLE" || { echo "bundle missing"; exit 1; }
python3 - <<'PYEOF'
import zipfile, os
src = "sdk/java/target/central-publishing/central-bundle.zip"
zin = zipfile.ZipFile(src)
zout = zipfile.ZipFile(src + ".clean", "w", zipfile.ZIP_DEFLATED)
kept = []
for n in zin.namelist():
name = n.replace("\\", "/")
base = name.split("/")[-1]
if base.startswith("maven-metadata") or base.startswith("_remote.repositories"):
continue
zout.writestr(name, zin.read(n))
kept.append(name)
zout.close(); zin.close(); os.replace(src + ".clean", src)
poms = [n for n in kept if n.endswith(".pom")]
ascs = [n for n in kept if n.endswith(".asc")]
assert len(poms) >= 1 and len(ascs) >= 4, "bundle incomplete after clean: %s" % kept
print("clean bundle:", len(kept), "entries; poms:", poms)
PYEOF
- name: Refuse a JAR that misses a platform
if: steps.exists.outputs.skip == 'false'
shell: bash
env:
STEM: ${{ needs.build.outputs.stem }}
run: |
python3 - <<'PYEOF'
import os, zipfile
bundle = zipfile.ZipFile("sdk/java/target/central-publishing/central-bundle.zip")
jars = [n for n in bundle.namelist() if n.endswith(".jar") and not n.endswith(("-sources.jar", "-javadoc.jar"))]
assert len(jars) == 1, "expected one main JAR in the bundle, found: %r" % jars
with open(os.path.join(os.environ["RUNNER_TEMP"], "main.jar"), "wb") as out:
out.write(bundle.read(jars[0]))
print("main jar:", jars[0])
PYEOF
python3 scripts/check-package-contents.py jar "$RUNNER_TEMP/main.jar" --platforms "${PITH_PLATFORMS// /,}" --stem "$STEM" --prefix hash/pith/native
- name: Upload the Java SDK to Maven Central
if: steps.exists.outputs.skip == 'false'
shell: bash
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
run: |
BUNDLE=sdk/java/target/central-publishing/central-bundle.zip
TOKEN="$(printf "%s:%s" "$CENTRAL_USERNAME" "$CENTRAL_PASSWORD" | base64 -w0)"
echo "::add-mask::$TOKEN"
ID="$(curl -sS --fail-with-body -X POST -H "Authorization: Bearer $TOKEN" -F "bundle=@$BUNDLE" "https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC")"
echo "deployment: $ID"
for _ in $(seq 1 60); do
sleep 10
ST="$(curl -sS --fail-with-body -X POST -H "Authorization: Bearer $TOKEN" "https://central.sonatype.com/api/v1/publisher/status?id=$ID")" || ST=""
echo "$ST"
if echo "$ST" | grep -q '"FAILED"'; then
exit 1
fi
if echo "$ST" | grep -qE '"(PUBLISHING|PUBLISHED)"'; then
exit 0
fi
done
echo "deployment $ID never left validation"
exit 1