Merge pull request #238 from abalabin-bamfunds/merge-adaptors-2026-01-16 #42
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 (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 |