diff --git a/.github/workflows/publish_version.yml b/.github/workflows/publish_version.yml new file mode 100644 index 0000000..1bf86aa --- /dev/null +++ b/.github/workflows/publish_version.yml @@ -0,0 +1,146 @@ +name: Publish to PyPI + +on: + workflow_dispatch: + inputs: + repository: + description: "Target index" + type: choice + options: [pypi, testpypi] + default: testpypi + ref: + description: "Git ref to build (branch/tag/SHA). Leave empty to use the UI-selected ref." + required: false + default: "" + +concurrency: + group: pypi-publish + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + container: + image: cubertgmbh/cuvis_pyil:3.5.0-ubuntu24.04 + + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref || github.ref }} + + # Optional hard stop: only allow repo admins to proceed + - name: Enforce admin-only trigger + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const username = context.actor; + + const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, repo, username + }); + + core.info(`Actor permission: ${data.permission}`); + if (data.permission !== "admin") { + core.setFailed(`Only repository admins may publish. (${username} has: ${data.permission})`); + } + + - name: Install build tooling + run: python3 -m pip install -U build twine + + - name: Read package name/version from pyproject.toml + id: meta + run: | + python3 - <<'PY' + import sys, json + try: + import tomllib # py3.11+ + except ModuleNotFoundError: + import tomli as tomllib # fallback if needed + from pathlib import Path + + data = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8")) + proj = data.get("project", {}) + name = proj.get("name") + version = proj.get("version") + if not name or not version: + print("Missing [project].name or [project].version in pyproject.toml", file=sys.stderr) + sys.exit(2) + + print(f"name={name}") + print(f"version={version}") + with open("pkg_meta.json", "w", encoding="utf-8") as f: + json.dump({"name": name, "version": version}, f) + PY + echo "name=$(python3 -c "import json; print(json.load(open('pkg_meta.json'))['name'])")" >> "$GITHUB_OUTPUT" + echo "version=$(python3 -c "import json; print(json.load(open('pkg_meta.json'))['version'])")" >> "$GITHUB_OUTPUT" + + - name: Abort if this version already exists on the target index + env: + NAME: ${{ steps.meta.outputs.name }} + VERSION: ${{ steps.meta.outputs.version }} + TARGET: ${{ inputs.repository }} + run: | + python3 - <<'PY' + import json, os, sys, urllib.request, urllib.error + + name = os.environ["NAME"] + version = os.environ["VERSION"] + target = os.environ["TARGET"] + + base = "https://pypi.org/pypi" if target == "pypi" else "https://test.pypi.org/pypi" + url = f"{base}/{name}/json" + + try: + with urllib.request.urlopen(url) as resp: + data = json.load(resp) + except urllib.error.HTTPError as e: + if e.code == 404: + print(f"{name} not found on {target}; OK to publish {version}.") + sys.exit(0) + raise + + releases = data.get("releases", {}) + if version in releases and releases[version]: + print(f"Version {name}=={version} already exists on {target}. Aborting.") + sys.exit(1) + + print(f"Version {name}=={version} not present on {target}; OK to publish.") + PY + + - name: Build sdist and wheel + run: | + python3 -m build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + + environment: ${{ inputs.repository }} + + permissions: + contents: read + id-token: write # required for PyPI Trusted Publishing (OIDC) + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@v1.13.0 + with: + repository-url: ${{ inputs.repository == 'pypi' && 'https://upload.pypi.org/legacy/' || 'https://test.pypi.org/legacy/' }} diff --git a/.gitignore b/.gitignore index 5123bc8..d3a4025 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /dist /build -/.venv +/.venv* /.eggs /cuvis.egg-info /MANIFEST.in @@ -8,6 +8,7 @@ /cuvis/_cuvis_pyil.pyd /venv /cuvis/__pycache__ -/.venv310 + /tests/__pycache__ /.claude + diff --git a/prebuild.py b/prebuild.py index 326463c..027df7d 100644 --- a/prebuild.py +++ b/prebuild.py @@ -4,7 +4,11 @@ def get_git_commit_hash(): try: - return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip() + return ( + subprocess.check_output(["git", "rev-parse", "HEAD"]) + .decode("ascii") + .strip() + ) except subprocess.CalledProcessError: return "unknown"