Skip to content

Merge pull request #238 from abalabin-bamfunds/merge-adaptors-2026-01-16 #42

Merge pull request #238 from abalabin-bamfunds/merge-adaptors-2026-01-16

Merge pull request #238 from abalabin-bamfunds/merge-adaptors-2026-01-16 #42

Workflow file for this run

name: Release Wheels (cibuildwheel)
on:
push:
tags:
- 'v*'
- 'v_*.*.*'
jobs:
build-wheels:
name: Build wheels (${{ matrix.os }})
runs-on: ${{ matrix.os }}
env:
HGRAPH_SMOKE_PYTHONS: "3.12 3.13 3.14"
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
steps:
- uses: actions/checkout@v6
- name: Show trigger inputs
if: github.event_name == 'workflow_dispatch'
run: |
echo "publish_testpypi=${{ inputs.publish_testpypi }}"
- name: Setup uv (Python 3.12 ABI3 baseline)
uses: astral-sh/setup-uv@v7.5.0
with:
python-version: '3.12'
- name: Cache uv/pip
uses: actions/cache@v5.0.3
with:
path: |
~/.cache/uv
~/.cache/pip
key: ${{ runner.os }}-cibw-uvpip-${{ hashFiles('uv.lock', 'pyproject.toml') }}
restore-keys: |
${{ runner.os }}-cibw-uvpip-
- name: Set version from tag into pyproject
shell: bash
run: |
set -euo pipefail
REF="${GITHUB_REF_NAME}"
if [[ "${REF}" == v_* ]]; then
VERSION="${REF#v_}"
elif [[ "${REF}" == v* ]]; then
VERSION="${REF#v}"
else
echo "Tag ${REF} does not start with v or v_; skipping version update" >&2
exit 1
fi
echo "Setting project version to: ${VERSION}"
export VERSION="${VERSION}"
python - <<'PY'
import os, re, pathlib, sys
ver = os.environ.get('VERSION')
if not ver:
print('VERSION env not set', file=sys.stderr)
sys.exit(1)
p = pathlib.Path('pyproject.toml')
s = p.read_text(encoding='utf-8')
new_s, n = re.subn(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', rf"\g<1>{ver}\2", s)
if n == 0:
# Fallback: replace [project] version explicitly
new_s, n = re.subn(r'(?m)^version\s*=\s*"[^"]+"', f'version = "{ver}"', s)
if n == 0:
print('ERROR: Could not find version field in pyproject.toml', file=sys.stderr)
sys.exit(1)
p.write_text(new_s, encoding='utf-8')
print(f'pyproject.toml version updated to {ver}')
PY
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install tooling
run: |
uv tool install --upgrade cibuildwheel==3.4.0
uv tool install --upgrade conan==2.26.2
- name: Build wheels with cibuildwheel
env:
# Build once on CPython 3.12 and publish ABI3 wheels for 3.12+.
CIBW_BUILD: cp312-*
# macOS: build Apple Silicon wheels only
CIBW_ARCHS_MACOS: arm64
# Ensure the wheel tag matches the binary minimum target (avoid delocate mismatch)
CIBW_MACOSX_DEPLOYMENT_TARGET: "15.0"
# Instruct delocate (repair step) to update the wheel tag to 15.0 as well
CIBW_REPAIR_WHEEL_COMMAND_MACOS: >-
MACOSX_DEPLOYMENT_TARGET=15.0 delocate-wheel --require-archs arm64 -w {dest_dir} -v {wheel}
# Windows: build 64-bit wheels only (avoid win32 toolchain issues)
CIBW_ARCHS_WINDOWS: AMD64
# Enable nanobind stable ABI at CMake level
CMAKE_ARGS: -DNB_USE_STABLE_ABI=ON
# Global env visible during builds inside cibuildwheel
CIBW_ENVIRONMENT: >-
SKBUILD_VERBOSE=1
# Linux: disable backward-cpp to avoid execinfo.h / non-portable deps in manylinux
# Re-state CMAKE_ARGS to include both flags on Linux and prefer Ninja
CIBW_ENVIRONMENT_LINUX: >-
CMAKE_ARGS="-DNB_USE_STABLE_ABI=ON -DHGRAPH_WITH_BACKWARD=OFF" CMAKE_GENERATOR=Ninja SKBUILD_VERBOSE=1
# Windows: prefer Visual Studio generator to ensure cl.exe is used and on PATH
CIBW_ENVIRONMENT_WINDOWS: >-
CMAKE_GENERATOR="Visual Studio 17 2022" CMAKE_GENERATOR_PLATFORM=x64 SKBUILD_VERBOSE=1
# Apply env for all macOS builds so the wheel tag matches binary min target
CIBW_ENVIRONMENT_MACOS: >-
MACOSX_DEPLOYMENT_TARGET=15.0 SKBUILD_VERBOSE=1
# Explicit arch + generator for macOS builds
CIBW_ENVIRONMENT_MACOS_ARM64: >-
CMAKE_OSX_ARCHITECTURES=arm64 MACOSX_DEPLOYMENT_TARGET=15.0 CMAKE_OSX_DEPLOYMENT_TARGET=15.0 CMAKE_GENERATOR=Ninja SKBUILD_VERBOSE=1
run: |
uvx --from cibuildwheel==3.4.0 cibuildwheel --output-dir wheelhouse
- name: Build sdist (source distribution)
run: |
uv build -v --sdist
- name: Validate wheels metadata (twine check) [Unix]
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
uvx --from twine==6.2.0 twine check wheelhouse/*
if compgen -G "dist/*.tar.gz" > /dev/null; then uvx --from twine==6.2.0 twine check dist/*.tar.gz; fi
- name: Validate wheels metadata (twine check) [Windows]
if: runner.os == 'Windows'
shell: pwsh
run: |
if (Test-Path "wheelhouse") {
$wheels = Get-ChildItem "wheelhouse/*.whl" -ErrorAction SilentlyContinue
if ($wheels) { uvx --from twine==6.2.0 twine check wheelhouse/*.whl }
} else { Write-Host "wheelhouse not found" }
- name: Test built wheels with ABI3 smoke tests [Linux/macOS]
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
ls -la wheelhouse || true
if ! compgen -G "wheelhouse/*.whl" > /dev/null; then echo "No wheels built"; exit 1; fi
mv hgraph hgraph_src
trap 'mv hgraph_src hgraph' EXIT
for wheel in wheelhouse/*.whl; do
if [ "${RUNNER_OS}" = "Linux" ] && [[ "${wheel}" == *musllinux* ]]; then
echo "Skipping musllinux wheel on glibc runner: ${wheel}"
continue
fi
for pyver in ${HGRAPH_SMOKE_PYTHONS}; do
uv venv --clear ".venv-test-${pyver}" --python "${pyver}"
uv pip install --python ".venv-test-${pyver}/bin/python" --upgrade pip
uv pip install --python ".venv-test-${pyver}/bin/python" --reinstall pytest tornado requests pandas "perspective-python<4.0.0" kafka-python matplotlib "${wheel}"
for use_cpp in false true; do
echo "Smoke test: wheel=${wheel} python=${pyver} HGRAPH_USE_CPP=${use_cpp}"
HGRAPH_USE_CPP="${use_cpp}" ".venv-test-${pyver}/bin/pytest" hgraph_unit_tests -q -m smoke
done
done
done
- name: Test built wheels with ABI3 smoke tests [Windows]
if: runner.os == 'Windows'
shell: pwsh
run: |
if (Test-Path wheelhouse) { Get-ChildItem wheelhouse | Format-Table Name,Length,LastWriteTime -AutoSize } else { Write-Host "wheelhouse not found" }
$wheels = Get-ChildItem wheelhouse/*.whl -ErrorAction SilentlyContinue
if (-not $wheels) { Write-Error "No wheels built"; exit 1 }
Rename-Item hgraph hgraph_src
try {
foreach ($wheel in $wheels) {
foreach ($pyver in $env:HGRAPH_SMOKE_PYTHONS.Split(' ')) {
$venvPython = Join-Path $PWD ".venv-test-$pyver\Scripts\python.exe"
uv venv --clear ".venv-test-$pyver" --python $pyver
uv pip install --python $venvPython --upgrade pip
uv pip install --python $venvPython --reinstall pytest tornado requests pandas "perspective-python<4.0.0" kafka-python matplotlib $wheel.FullName
foreach ($useCpp in @('false', 'true')) {
Write-Host "Smoke test: wheel=$($wheel.Name) python=$pyver HGRAPH_USE_CPP=$useCpp"
$env:HGRAPH_USE_CPP = $useCpp
& (Join-Path $PWD ".venv-test-$pyver\Scripts\pytest.exe") hgraph_unit_tests -q -m smoke
}
}
}
} finally {
Remove-Item Env:HGRAPH_USE_CPP -ErrorAction SilentlyContinue
Rename-Item hgraph_src hgraph
}
- name: Upload wheels
uses: actions/upload-artifact@v7.0.0
with:
name: release-wheels-${{ runner.os }}
path: wheelhouse/**
if-no-files-found: error
retention-days: 14
- name: Upload sdist (Linux only)
if: runner.os == 'Linux'
uses: actions/upload-artifact@v7.0.0
with:
name: release-sdist
path: dist/*.tar.gz
if-no-files-found: ignore
retention-days: 14
publish:
name: Publish to PyPI
needs: [build-wheels]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
environment:
name: release
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: Download release artifacts (wheels + sdist)
uses: actions/download-artifact@v8.0.1
with:
path: dist_all
pattern: release-*
merge-multiple: true
- name: Collect distributions into a single folder
shell: bash
run: |
set -euo pipefail
mkdir -p to_upload
shopt -s globstar nullglob
# Gather .whl and .tar.gz from all artifacts (recursively)
for f in dist_all/**/*.whl dist_all/**/*.tar.gz; do
[ -e "$f" ] || continue
cp "$f" to_upload/
done
echo "Collected distributions:" && ls -la to_upload || true
- name: Publish to PyPI (trusted publishing)
uses: pypa/gh-action-pypi-publish@v1.13.0
with:
packages-dir: to_upload
skip-existing: true
- name: Checkout repository for version commit
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Commit version bump to default branch
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REF: ${{ github.ref_name }}
run: |
set -euo pipefail
# Determine version from tag inside Python to avoid env mismatches
DEFAULT_BRANCH="${{ github.event.repository.default_branch }}"
git checkout "$DEFAULT_BRANCH"
python - <<PY
import os, re, pathlib, sys
ref = os.environ.get('REF','')
if ref.startswith('v_'):
ver = ref[2:]
elif ref.startswith('v'):
ver = ref[1:]
else:
print(f'Unexpected tag format: {ref}', file=sys.stderr)
sys.exit(1)
print(f'Committing version {ver} to pyproject.toml on default branch')
p = pathlib.Path('pyproject.toml')
s = p.read_text(encoding='utf-8')
new_s, n = re.subn(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', rf"\g<1>{ver}\2", s)
if n == 0:
new_s, n = re.subn(r'(?m)^version\s*=\s*"[^"]+"', f'version = "{ver}"', s)
if n == 0:
print('ERROR: Could not find version field in pyproject.toml', file=sys.stderr)
sys.exit(1)
if new_s != s:
p.write_text(new_s, encoding='utf-8')
PY
# Commit only if changed; push to default branch
git config --local user.name "github-actions"
git config --local user.email "github-actions@github.com"
if ! git diff --quiet -- pyproject.toml; then
git add pyproject.toml
# Compute version string for the commit subject from REF (supports v_#.#.# and v#.#.#)
VER_COMMIT="$REF"
if [[ "$VER_COMMIT" == v_* ]]; then VER_COMMIT="${VER_COMMIT#v_}"; elif [[ "$VER_COMMIT" == v* ]]; then VER_COMMIT="${VER_COMMIT#v}"; fi
git commit -m "Update version to ${VER_COMMIT} based on tag"
git push origin HEAD:"$DEFAULT_BRANCH"
else
echo "No changes detected in pyproject.toml. Skipping commit and push."
fi