Skip to content

Merge pull request #19 from ecrum19/release/v3.1.0 #14

Merge pull request #19 from ecrum19/release/v3.1.0

Merge pull request #19 from ecrum19/release/v3.1.0 #14

Workflow file for this run

name: publish-python
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
release_tag:
description: "Existing Git tag to validate, for example v1.2.4. Manual runs never publish to PyPI."
required: true
type: string
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# Pin pip instead of taking whatever released most recently. Every job used to
# run `pip install --upgrade pip`, so each run resolved dependencies with a
# different, unannounced resolver: CI was non-reproducible by construction,
# and a pip release could break the build with no change in this repository.
#
# This does NOT prevent transient index failures, and should not be mistaken
# for a fix for one. A case is on record: the pull_request run for PR #14
# failed with `no matching distributions available for your environment:
# mdurl` while the identical commit had passed 15 minutes earlier on push,
# passed on two other platforms in that same run, and passed again on re-run
# with no code change. mdurl is a pure-Python py3-none-any wheel, so nothing
# about the environment made it uninstallable -- that was the index, not the
# resolver. The right response there is a re-run. The right response to an
# unpinned toolchain is this pin.
#
# Bump deliberately, as its own commit, so a resolver change lands where it
# can be attributed instead of appearing inside an unrelated PR.
PIP_VERSION: "26.2.1"
jobs:
build:
name: build-sdist-wheel
runs-on: ubuntu-latest
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag || github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.release_tag || github.ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Validate committed release metadata
run: python scripts/release.py --check-tag "${RELEASE_TAG}"
- name: Build package
run: |
python -m pip install "pip==${{ env.PIP_VERSION }}" build twine
rm -rf dist build *.egg-info
python -m build
python -m twine check dist/*
- name: Verify built artifact versions
run: |
python - <<'PY'
from pathlib import Path
import os
import tomllib
tag = os.environ["RELEASE_TAG"].strip()
if not tag.startswith("v"):
raise SystemExit(f"Release tag must start with 'v': {tag}")
version = tag[1:]
pyproject_version = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["version"]
if pyproject_version != version:
raise SystemExit(
f"pyproject.toml version {pyproject_version} does not match release tag version {version}"
)
dist = sorted(Path("dist").glob("*"))
if not dist:
raise SystemExit("No build artifacts found in dist/")
names = [path.name for path in dist]
expected = {
f"vcf_rdfizer-{version}.tar.gz",
f"vcf_rdfizer-{version}-py3-none-any.whl",
}
missing = sorted(expected.difference(names))
if missing:
raise SystemExit(
f"Built artifacts do not match project version {version}; missing: {', '.join(missing)}"
)
unexpected = sorted(
name for name in names if name.startswith("vcf_rdfizer-") and f"vcf_rdfizer-{version}" not in name
)
if unexpected:
raise SystemExit(
f"Found stale artifacts in dist/: {', '.join(unexpected)}"
)
print("Verified build artifacts:")
for name in names:
print(f" {name}")
PY
- name: Smoke-test installed wheel
run: |
python -m venv /tmp/vcf-rdfizer-wheel-test
/tmp/vcf-rdfizer-wheel-test/bin/python -m pip install --no-deps dist/*.whl
(
cd /tmp
/tmp/vcf-rdfizer-wheel-test/bin/vcf-rdfizer --help
/tmp/vcf-rdfizer-wheel-test/bin/python - <<'PY'
import importlib.resources
from pathlib import Path
import vcf_rdfizer
rules = importlib.resources.files("vcf_rdfizer_data").joinpath("rules/default_rules.ttl")
if not rules.is_file():
raise SystemExit("Packaged default rules are missing")
if not vcf_rdfizer.resolve_default_rules_path(Path(vcf_rdfizer.__file__).resolve().parent).is_file():
raise SystemExit("Installed wrapper cannot resolve packaged default rules")
PY
)
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: python-dist
path: dist/*
publish:
name: publish-to-pypi
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
environment:
name: pypi
permissions:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: python-dist
path: dist
# PyPI reserves a version permanently once it is uploaded, so re-pushing a
# release tag -- to correct a Dockerfile, a workflow, or anything else
# outside the Python artifact -- used to fail this job on the duplicate.
# Skipping what is already published makes a tag re-push idempotent: the
# Docker and release jobs re-run, and PyPI is left exactly as it was.
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true