Update odx compare #7
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release wheels + PyPI | |
| # Trigger on version tags (e.g. v0.1.0, v0.2.0-rc1) or manual dispatch. | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| # ─── Tag / version sanity check ──────────────────────────────────────────── | |
| # | |
| # Refuses to build if the pushed tag (`vX.Y.Z`) doesn't match the version | |
| # in [workspace.package] of Cargo.toml. Catches "I tagged but forgot to | |
| # bump" before we waste 4× wheel build minutes on a wrong-version release. | |
| # All build jobs `needs: verify-version`, so a mismatch fails the whole | |
| # workflow at the first step. | |
| verify-version: | |
| name: Verify tag matches Cargo.toml | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.check.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - id: check | |
| run: | | |
| set -euo pipefail | |
| # Extract the version from the [workspace.package] section. | |
| PKG_VERSION=$(awk ' | |
| /^\[workspace\.package\]/ { in_section = 1; next } | |
| /^\[/ { in_section = 0 } | |
| in_section && /^version[[:space:]]*=/ { | |
| gsub(/^version[[:space:]]*=[[:space:]]*"|"[[:space:]]*$/, "") | |
| exit | |
| } | |
| ' Cargo.toml) | |
| echo "Cargo.toml: $PKG_VERSION" | |
| echo "version=$PKG_VERSION" >> "$GITHUB_OUTPUT" | |
| # Only enforce tag<->version match when triggered by a tag push. | |
| # workflow_dispatch and re-runs without a tag fall through. | |
| if [[ "$GITHUB_REF" != refs/tags/v* ]]; then | |
| echo "Not a tag push; skipping tag<->version match." | |
| exit 0 | |
| fi | |
| TAG_VERSION="${GITHUB_REF#refs/tags/v}" | |
| echo "Tag: $TAG_VERSION" | |
| if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then | |
| echo "::error::Tag v$TAG_VERSION does not match [workspace.package].version = \"$PKG_VERSION\". Bump Cargo.toml or retag." | |
| exit 1 | |
| fi | |
| # ─── Linux wheels ─────────────────────────────────────────────────────────── | |
| linux: | |
| name: Linux ${{ matrix.target }} (${{ matrix.libc }}) | |
| runs-on: ubuntu-latest | |
| needs: [verify-version] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64 | |
| libc: manylinux | |
| - target: aarch64 | |
| libc: manylinux | |
| - target: x86_64 | |
| libc: musllinux | |
| - target: aarch64 | |
| libc: musllinux | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: PyO3/maturin-action@v1 | |
| # Set the CMAKE env var so the cmake-rs crate (used by | |
| # hdf5-metno-src's build script) uses our pip-installed cmake | |
| # directly, sidestepping any PATH-resolution surprises. | |
| # before-script-linux populates /opt/_pipcmake/bin/cmake. | |
| env: | |
| CMAKE: /opt/_pipcmake/bin/cmake | |
| with: | |
| target: ${{ matrix.target }} | |
| # abi3-py39 wheels work for all CPython >= 3.9. | |
| args: --release --out dist | |
| working-directory: python | |
| # `2_28` (AlmaLinux 9 / glibc 2.28) instead of the older `auto` | |
| # (= `2014` = manylinux_2_17, CentOS 7 / glibc 2.17). The 2014 | |
| # cross image ships Python 3.6, which maturin rejects (needs | |
| # >=3.7) and which is below our abi3-py39 baseline. 2_28 has | |
| # Python 3.9+ baked in. The compat trade-off is glibc 2.28+ | |
| # required at install time — i.e. RHEL/AlmaLinux 8+, Debian 10+, | |
| # Ubuntu 18.10+ (essentially anything from the last ~6 years). | |
| manylinux: ${{ matrix.libc == 'manylinux' && '2_28' || 'musllinux_1_2' }} | |
| before-script-linux: | | |
| # hdf5-metno-src builds HDF5 from source via cmake; it requires | |
| # CMake >= 3.26. Both manylinux_2_17 (AlmaLinux yum cmake 3.20) | |
| # and musllinux_1_2 (Alpine apk cmake 3.22) ship too-old | |
| # versions, and cross-compile images from messense (used for | |
| # aarch64) don't have /opt/python/ at all. Install a recent | |
| # cmake via pip from whatever Python we can find, and pin its | |
| # path via the CMAKE env var. | |
| # | |
| # `set -ex` prints every command so failures are diagnosable | |
| # from the workflow log without re-running. | |
| set -ex | |
| uname -a | |
| cat /etc/os-release || true | |
| ls -d /opt/python 2>&1 || echo "(no /opt/python — likely a cross-compile image)" | |
| # Install minimal build tools + a system Python if needed. | |
| # The cross-compile images have package managers but often no | |
| # Python preinstalled, so add it here. | |
| if command -v apk >/dev/null 2>&1; then | |
| apk update | |
| apk add --no-cache make gcc g++ musl-dev linux-headers perl bash python3 py3-pip | |
| elif command -v dnf >/dev/null 2>&1; then | |
| dnf install -y make gcc gcc-c++ perl python3 python3-pip | |
| elif command -v yum >/dev/null 2>&1; then | |
| yum install -y make gcc gcc-c++ perl python3 python3-pip | |
| elif command -v apt-get >/dev/null 2>&1; then | |
| apt-get update | |
| apt-get install -y make gcc g++ perl python3 python3-pip | |
| fi | |
| # Resolve a pip we can use. Prefer the PyPA-style /opt/python | |
| # bundled CPython on native images; on *cross-compile* images | |
| # (messense/*-cross), /opt/python contains TARGET-arch binaries | |
| # that can't execute on the build host (kernel returns ENOEXEC, | |
| # bash falls back to interpreting the pip script and chokes on | |
| # Python syntax). Probe each candidate by running --version | |
| # before committing to it. | |
| PIP_CMD="" | |
| PYBIN="" | |
| for cand in /opt/python/cp310-cp310/bin /opt/python/cp311-cp311/bin \ | |
| /opt/python/cp312-cp312/bin /opt/python/cp39-cp39/bin \ | |
| /opt/python/cp313-cp313/bin; do | |
| if [ -d "$cand" ] && "$cand/python" --version >/dev/null 2>&1; then | |
| PYBIN="$cand" | |
| PIP_CMD="$cand/pip" | |
| break | |
| fi | |
| done | |
| if [ -z "$PIP_CMD" ]; then | |
| if command -v pip3 >/dev/null 2>&1; then | |
| PIP_CMD="pip3" | |
| elif command -v python3 >/dev/null 2>&1; then | |
| python3 -m ensurepip --default-pip 2>/dev/null || true | |
| PIP_CMD="python3 -m pip" | |
| else | |
| echo "::error::No Python/pip found in build container" | |
| exit 1 | |
| fi | |
| fi | |
| echo "Using PIP_CMD='$PIP_CMD' (PYBIN='${PYBIN:-system}')" | |
| # If we found a /opt/python/cp* Python, make sure it shadows | |
| # any older system python3 on PATH. maturin invokes `python3` | |
| # from PATH after this script runs, and we've seen older | |
| # cross-compile images leave a too-old `/usr/bin/python3` in | |
| # place (e.g., 3.6 on the legacy manylinux2014 cross image). | |
| if [ -n "$PYBIN" ]; then | |
| ln -sf "$PYBIN/python" /usr/local/bin/python3 | |
| ln -sf "$PYBIN/python" /usr/local/bin/python | |
| fi | |
| # Install cmake>=3.26 from PyPI. `--break-system-packages` is | |
| # for PEP 668-marked Pythons (some distros); harmless on others. | |
| $PIP_CMD install --no-cache-dir --upgrade --break-system-packages "cmake>=3.26" 2>/dev/null \ | |
| || $PIP_CMD install --no-cache-dir --upgrade "cmake>=3.26" | |
| # Find where the pip-installed cmake landed. With /opt/python | |
| # it's in $PYBIN/cmake; with system pip it's wherever pip puts | |
| # console scripts — discoverable via `command -v cmake` after | |
| # install (PATH is updated by the install). | |
| INSTALLED_CMAKE="" | |
| if [ -n "$PYBIN" ] && [ -x "$PYBIN/cmake" ]; then | |
| INSTALLED_CMAKE="$PYBIN/cmake" | |
| elif command -v cmake >/dev/null 2>&1; then | |
| INSTALLED_CMAKE=$(command -v cmake) | |
| else | |
| # Last-ditch: ask pip where it put the script. | |
| INSTALLED_CMAKE=$($PIP_CMD show -f cmake 2>/dev/null | awk '/Location:/ {loc=$2} /bin\/cmake/ {print loc"/"$1}' | head -1) | |
| # That gives a path relative to site-packages; resolve to abs. | |
| if [ -n "$INSTALLED_CMAKE" ]; then | |
| INSTALLED_CMAKE=$(realpath "$INSTALLED_CMAKE" 2>/dev/null || echo "$INSTALLED_CMAKE") | |
| fi | |
| fi | |
| if [ -z "$INSTALLED_CMAKE" ] || [ ! -x "$INSTALLED_CMAKE" ]; then | |
| echo "::error::Could not locate pip-installed cmake binary" | |
| $PIP_CMD show -f cmake || true | |
| exit 1 | |
| fi | |
| echo "Pip-installed cmake at: $INSTALLED_CMAKE" | |
| # Pin the path to match the CMAKE env var set on the step. | |
| mkdir -p /opt/_pipcmake/bin | |
| ln -sf "$INSTALLED_CMAKE" /opt/_pipcmake/bin/cmake | |
| # Shadow common system locations as a PATH fallback. | |
| for target in /usr/local/bin/cmake /usr/bin/cmake; do | |
| parent=$(dirname "$target") | |
| [ -d "$parent" ] && ln -sf "$INSTALLED_CMAKE" "$target" || true | |
| done | |
| # Diagnostic: confirm the right cmake is found via every route. | |
| echo "--- cmake versions:" | |
| "$INSTALLED_CMAKE" --version | |
| /opt/_pipcmake/bin/cmake --version | |
| cmake --version | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-linux-${{ matrix.target }}-${{ matrix.libc }} | |
| path: python/dist | |
| # ─── macOS wheels (x86_64 + Apple Silicon) ───────────────────────────────── | |
| # | |
| # Runner mapping: | |
| # - aarch64 → macos-14 (Apple Silicon, free) | |
| # - x86_64 → macos-15-intel (Intel x86_64 on macOS 15; GitHub's | |
| # recommended migration path for x86_64 | |
| # builds, supported until August 2027). | |
| # | |
| # The older `macos-13` runner pool is queue-pressured; `macos-15-intel` | |
| # is the current Intel target with much better availability. There's no | |
| # `macos-15-intel-large` etc. — Intel hardware is being retired, so | |
| # macos-15-intel is the long tail. | |
| macos: | |
| name: macOS ${{ matrix.target }} | |
| runs-on: ${{ matrix.target == 'aarch64' && 'macos-14' || 'macos-15-intel' }} | |
| needs: [verify-version] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [x86_64, aarch64] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: PyO3/maturin-action@v1 | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist | |
| working-directory: python | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-macos-${{ matrix.target }} | |
| path: python/dist | |
| # ─── Windows wheels ──────────────────────────────────────────────────────── | |
| windows: | |
| name: Windows ${{ matrix.target }} | |
| runs-on: windows-latest | |
| needs: [verify-version] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: [x64] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: PyO3/maturin-action@v1 | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist | |
| working-directory: python | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-windows-${{ matrix.target }} | |
| path: python/dist | |
| # ─── Source distribution ─────────────────────────────────────────────────── | |
| sdist: | |
| name: sdist | |
| runs-on: ubuntu-latest | |
| needs: [verify-version] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: PyO3/maturin-action@v1 | |
| with: | |
| command: sdist | |
| args: --out dist | |
| working-directory: python | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: sdist | |
| path: python/dist | |
| publish: | |
| name: Publish to PyPI | |
| needs: [linux, macos, windows, sdist] | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/odx | |
| permissions: | |
| id-token: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: Inspect artifacts | |
| run: ls -la dist/ | |
| - uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist | |
| # Switch to test.pypi.org for dry-runs by uncommenting: | |
| # repository-url: https://test.pypi.org/legacy/ | |
| skip-existing: true |