Skip to content

Bundle data files in pip package and fix hardcoded paths #78

Bundle data files in pip package and fix hardcoded paths

Bundle data files in pip package and fix hardcoded paths #78

Workflow file for this run

name: build-and-publish
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
push:
branches: [main]
workflow_dispatch:
inputs:
force_build:
description: "Force build jobs (manual/PR testing)"
required: false
type: boolean
publish_s2and:
description: "Publish s2and to PyPI (manual run)"
required: false
type: boolean
publish_rust:
description: "Publish s2and-rust to PyPI (manual run)"
required: false
type: boolean
jobs:
detect-versions:
name: detect version changes
runs-on: ubuntu-latest
outputs:
s2and_changed: ${{ steps.detect.outputs.s2and_changed }}
rust_changed: ${{ steps.detect.outputs.rust_changed }}
publish_any: ${{ steps.detect.outputs.publish_any }}
publish_s2and: ${{ steps.detect.outputs.publish_s2and }}
publish_rust: ${{ steps.detect.outputs.publish_rust }}
force_build: ${{ steps.detect.outputs.force_build }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Detect version changes
id: detect
env:
BEFORE_SHA: ${{ github.event.before }}
run: |
python - <<'PY'
import json
import os
import subprocess
import sys
import tomllib
event_name = os.environ.get("GITHUB_EVENT_NAME") or ""
event_path = os.environ.get("GITHUB_EVENT_PATH") or ""
event = None
if event_path and os.path.exists(event_path):
with open(event_path, "r", encoding="utf-8") as f:
event = json.load(f)
force_build = False
publish_s2and = False
publish_rust = False
if event_name == "workflow_dispatch":
inputs = (event or {}).get("inputs", {})
force_build = str(inputs.get("force_build", "")).lower() in {"1", "true", "yes", "on"}
publish_s2and = str(inputs.get("publish_s2and", "")).lower() in {"1", "true", "yes", "on"}
publish_rust = str(inputs.get("publish_rust", "")).lower() in {"1", "true", "yes", "on"}
elif event_name == "pull_request":
labels = (event or {}).get("pull_request", {}).get("labels", [])
force_build = any((label or {}).get("name", "").lower() == "force-build" for label in labels)
before = os.environ.get("BEFORE_SHA") or ""
if event_name == "pull_request" and event:
before = (event.get("pull_request", {}).get("base", {}).get("sha", "")) or ""
if before.startswith("0000000"):
before = ""
def read_toml_at(path, rev=None):
if rev:
try:
data = subprocess.check_output(["git", "show", f"{rev}:{path}"], text=True)
except subprocess.CalledProcessError:
return None
return tomllib.loads(data)
with open(path, "rb") as f:
return tomllib.load(f)
def read_version_file(rev=None):
version_path = "VERSION"
if rev:
try:
data = subprocess.check_output(["git", "show", f"{rev}:{version_path}"], text=True)
except subprocess.CalledProcessError:
return None
return data.strip()
if not os.path.exists(version_path):
return None
with open(version_path, "r", encoding="utf-8") as f:
return f.read().strip()
def get_project_version(toml_obj, path, rev=None):
if toml_obj is None:
return None
try:
return toml_obj["project"]["version"]
except KeyError:
# Allow dynamic version from VERSION for the root pyproject.toml.
if path == "pyproject.toml":
version = read_version_file(rev)
if version:
return version
raise SystemExit(f"Missing [project].version in {path}")
def get_cargo_version(toml_obj, path):
if toml_obj is None:
return None
try:
return toml_obj["package"]["version"]
except KeyError:
raise SystemExit(f"Missing [package].version in {path}")
cur_py = read_toml_at("pyproject.toml")
cur_rust_py = read_toml_at("s2and_rust/pyproject.toml")
cur_rust_cargo = read_toml_at("s2and_rust/Cargo.toml")
cur_py_ver = get_project_version(cur_py, "pyproject.toml")
cur_rust_py_ver = get_project_version(cur_rust_py, "s2and_rust/pyproject.toml")
cur_rust_cargo_ver = get_cargo_version(cur_rust_cargo, "s2and_rust/Cargo.toml")
if cur_rust_py_ver != cur_rust_cargo_ver:
raise SystemExit(
f"Rust version mismatch: pyproject={cur_rust_py_ver} cargo={cur_rust_cargo_ver}"
)
before_py_ver = (
get_project_version(read_toml_at("pyproject.toml", before), "pyproject.toml", before)
if before
else None
)
before_rust_py_ver = (
get_project_version(read_toml_at("s2and_rust/pyproject.toml", before), "s2and_rust/pyproject.toml")
if before
else None
)
s2and_changed = before_py_ver != cur_py_ver
rust_changed = before_rust_py_ver != cur_rust_py_ver
publish_any = s2and_changed or rust_changed or publish_s2and or publish_rust
out_path = os.environ["GITHUB_OUTPUT"]
with open(out_path, "a", encoding="utf-8") as f:
f.write(f"s2and_changed={'true' if s2and_changed else 'false'}\n")
f.write(f"rust_changed={'true' if rust_changed else 'false'}\n")
f.write(f"publish_any={'true' if publish_any else 'false'}\n")
f.write(f"publish_s2and={'true' if publish_s2and else 'false'}\n")
f.write(f"publish_rust={'true' if publish_rust else 'false'}\n")
f.write(f"force_build={'true' if force_build else 'false'}\n")
print(f"s2and: {before_py_ver} -> {cur_py_ver} changed={s2and_changed}")
print(f"rust: {before_rust_py_ver} -> {cur_rust_py_ver} changed={rust_changed}")
print(f"force_build: {force_build}")
print(f"publish_s2and: {publish_s2and}")
print(f"publish_rust: {publish_rust}")
PY
s2and-dist:
name: s2and sdist+wheel
runs-on: ubuntu-latest
needs: [detect-versions]
if: needs.detect-versions.outputs.s2and_changed == 'true' || needs.detect-versions.outputs.force_build == 'true' || needs.detect-versions.outputs.publish_s2and == 'true'
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Build s2and distributions
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m build --sdist --wheel --outdir dist
- uses: actions/upload-artifact@v6
with:
name: dist-s2and
path: dist/*
wheels-windows:
name: wheels (windows, py${{ matrix.py }})
runs-on: windows-latest
needs: [detect-versions]
if: needs.detect-versions.outputs.rust_changed == 'true' || needs.detect-versions.outputs.force_build == 'true' || needs.detect-versions.outputs.publish_rust == 'true'
strategy:
fail-fast: false
matrix:
py: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.py }}
- name: Build wheels
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380
with:
maturin-version: v1.11.5
command: build
args: --release --locked --compatibility pypi --out dist
working-directory: s2and_rust
- uses: actions/upload-artifact@v6
with:
name: dist-s2and-rust-windows-py${{ matrix.py }}
path: s2and_rust/dist/*
wheels-macos:
name: wheels (macos universal2, py${{ matrix.py }})
runs-on: macos-latest
needs: [detect-versions]
if: needs.detect-versions.outputs.rust_changed == 'true' || needs.detect-versions.outputs.force_build == 'true' || needs.detect-versions.outputs.publish_rust == 'true'
strategy:
fail-fast: false
matrix:
py: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.py }}
- name: Install Rust targets (universal2)
run: rustup target add x86_64-apple-darwin aarch64-apple-darwin
- name: Build wheels (universal2)
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380
env:
ARCHFLAGS: "-arch x86_64 -arch arm64"
with:
maturin-version: v1.11.5
command: build
args: --release --locked --compatibility pypi --out dist
working-directory: s2and_rust
- uses: actions/upload-artifact@v6
with:
name: dist-s2and-rust-macos-universal2-py${{ matrix.py }}
path: s2and_rust/dist/*
wheels-linux:
name: wheels (linux ${{ matrix.platform.name }})
runs-on: ubuntu-latest
needs: [detect-versions]
if: needs.detect-versions.outputs.rust_changed == 'true' || needs.detect-versions.outputs.force_build == 'true' || needs.detect-versions.outputs.publish_rust == 'true'
strategy:
fail-fast: false
matrix:
platform:
- name: manylinux2014-x86_64
target: x86_64-unknown-linux-gnu
manylinux: "2_17"
# Optional: enable when you want aarch64 wheels.
- name: manylinux2014-aarch64
target: aarch64-unknown-linux-gnu
manylinux: "2_17"
- name: musllinux-x86_64
target: x86_64-unknown-linux-musl
manylinux: "musllinux_1_2"
steps:
- uses: actions/checkout@v5
- name: Build wheels (manylinux/musllinux)
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380
with:
maturin-version: v1.11.5
command: build
target: ${{ matrix.platform.target }}
manylinux: ${{ matrix.platform.manylinux }}
args: >
--release --locked --compatibility pypi --out dist
-i python3.10 -i python3.11 -i python3.12
working-directory: s2and_rust
- uses: actions/upload-artifact@v6
with:
name: dist-s2and-rust-linux-${{ matrix.platform.name }}
path: s2and_rust/dist/*
sdist:
name: sdist
runs-on: ubuntu-latest
needs: [detect-versions]
if: needs.detect-versions.outputs.rust_changed == 'true' || needs.detect-versions.outputs.force_build == 'true' || needs.detect-versions.outputs.publish_rust == 'true'
steps:
- uses: actions/checkout@v5
- name: Build sdist
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380
with:
maturin-version: v1.11.5
command: sdist
args: --out dist
working-directory: s2and_rust
- uses: actions/upload-artifact@v6
with:
name: dist-s2and-rust-sdist
path: s2and_rust/dist/*
# Publish is split so skipped build jobs don't block unrelated publishes.
# Manual runs can publish by setting workflow_dispatch inputs.
publish-s2and:
name: publish s2and to PyPI
if: (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-versions.outputs.s2and_changed == 'true') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && needs.detect-versions.outputs.publish_s2and == 'true')
needs: [detect-versions, s2and-dist]
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
steps:
- name: Download s2and dists
uses: actions/download-artifact@v7
with:
name: dist-s2and
path: dist/s2and
- name: Publish s2and
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/s2and
publish-rust:
name: publish s2and-rust to PyPI
if: (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-versions.outputs.rust_changed == 'true') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && needs.detect-versions.outputs.publish_rust == 'true')
needs: [detect-versions, wheels-windows, wheels-macos, wheels-linux, sdist]
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
steps:
- name: Download s2and-rust dists
uses: actions/download-artifact@v7
with:
pattern: dist-s2and-rust-*
merge-multiple: true
path: dist/s2and-rust
- name: Publish s2and-rust
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/s2and-rust