From 005d4f504395e9aa701e6ad7302043aad9d855ef Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Fri, 1 May 2026 21:33:59 +0700 Subject: [PATCH 1/8] fix: prevent SIGSEGV crash on Homebrew --- .github/workflows/distribution-test.yml | 64 +++++-- .github/workflows/release-build.yml | 218 ++++++++++++++---------- 2 files changed, 180 insertions(+), 102 deletions(-) diff --git a/.github/workflows/distribution-test.yml b/.github/workflows/distribution-test.yml index 1cf2bdc..dbe8791 100644 --- a/.github/workflows/distribution-test.yml +++ b/.github/workflows/distribution-test.yml @@ -21,14 +21,11 @@ jobs: -e TN_ENDPOINT="https://gateway.mainnet.truf.network" \ sdk-py-dist-test - test-native-build: - name: Test Native Build on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - + # Linux dev-install path: builds via setup.py + setup-python, mirrors + # what a contributor would run locally with `uv pip install -e .`. + test-native-build-linux: + name: Test Native Build (Linux) + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 @@ -43,10 +40,8 @@ jobs: go install github.com/go-python/gopy@v0.4.10 go install golang.org/x/tools/cmd/goimports@latest echo "$HOME/go/bin" >> $GITHUB_PATH - shell: bash - - name: Install system dependencies (Linux) - if: runner.os == 'Linux' + - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y build-essential patchelf @@ -55,11 +50,11 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.12' - + - name: Install uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - - name: Build and Test Natively + - name: Build and test natively run: | export PATH="$HOME/.local/bin:$PATH" uv pip install setuptools wheel pybindgen build --system @@ -67,4 +62,45 @@ jobs: python -m build --wheel --outdir /tmp/wheelhouse uv pip install /tmp/wheelhouse/*.whl --system python tests/distribution/get_account_test.py - shell: bash \ No newline at end of file + + # macOS PR check uses the same cibuildwheel pipeline as the release + # workflow so the wheel under test is structurally identical to what + # ships, then loads it under a Homebrew Python to assert + # cross-interpreter portability — the property the v0.6.6 SIGSEGV + # report showed was missing. + test-native-build-macos: + name: Test Native Build (macOS arm64) + runs-on: macos-14 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.24.1' + + - name: Build wheels with cibuildwheel + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD: cp312-macosx_arm64 + CIBW_ARCHS_MACOS: arm64 + CIBW_ENVIRONMENT: > + CGO_ENABLED=1 + GOARCH=arm64 + PATH=$PATH:/usr/local/go/bin:$HOME/go/bin + MACOSX_DEPLOYMENT_TARGET=11.0 + CIBW_BEFORE_ALL_MACOS: | + go install github.com/go-python/gopy@v0.4.10 + go install golang.org/x/tools/cmd/goimports@latest + CIBW_BEFORE_BUILD_MACOS: | + pip install pybindgen setuptools wheel setuptools-scm + + - name: Verify wheel under Homebrew Python + run: | + brew install python@3.12 + BREW_PY=$(brew --prefix python@3.12)/bin/python3.12 + "$BREW_PY" --version + "$BREW_PY" -m venv /tmp/brew-venv + /tmp/brew-venv/bin/pip install wheelhouse/*.whl + /tmp/brew-venv/bin/python tests/distribution/get_account_test.py diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 9ba54cb..5e2762b 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -1,88 +1,130 @@ -name: Release · Build & Package Python SDK - -on: - # Run whenever you push a tag like v1.2.3 - push: - tags: - - 'v*.*.*' - # Also allow manual re-runs - workflow_dispatch: - -permissions: write-all - -jobs: - build: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - python-version: ['3.12'] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for setuptools_scm to detect version from git tags - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.24.1' - - - name: Install gopy & goimports - run: | - go install github.com/go-python/gopy@v0.4.10 - go install golang.org/x/tools/cmd/goimports@latest - echo "$HOME/go/bin" >> $GITHUB_PATH - shell: bash - - - name: Install system dependencies (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y build-essential - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install build tools - run: | - python3 -m pip install --upgrade pip setuptools wheel pybindgen build setuptools-scm - - - name: Build gopy bindings - run: make gopy_build - shell: bash - - - name: Build wheels - run: python -m build --wheel --outdir wheelhouse - - - name: Upload wheels as artifacts - uses: actions/upload-artifact@v4 - with: - name: wheels-${{ matrix.os }} - path: wheelhouse/*.whl - - publish: - name: Publish Wheels to Release - needs: build - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - - steps: - - name: Download all built wheels - uses: actions/download-artifact@v4 - with: - pattern: wheels-* - merge-multiple: true - path: dist/ - - - name: Publish GitHub Release with Wheels - uses: softprops/action-gh-release@v2 - with: - files: dist/*.whl - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +name: Release · Build & Package Python SDK + +on: + # Run whenever you push a tag like v1.2.3 + push: + tags: + - 'v*.*.*' + # Also allow manual re-runs + workflow_dispatch: + +permissions: write-all + +jobs: + build-linux: + name: Build Linux wheels + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.24.1' + + - name: Install gopy & goimports + run: | + go install github.com/go-python/gopy@v0.4.10 + go install golang.org/x/tools/cmd/goimports@latest + echo "$HOME/go/bin" >> $GITHUB_PATH + shell: bash + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential patchelf + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install build tools + run: python3 -m pip install --upgrade pip setuptools wheel pybindgen build setuptools-scm + + - name: Build gopy bindings + run: make gopy_build + + - name: Build wheels + run: python -m build --wheel --outdir wheelhouse + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux + path: wheelhouse/*.whl + + build-macos: + name: Build macOS wheels (${{ matrix.arch }}) + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + include: + - arch: arm64 + runs-on: macos-14 + goarch: arm64 + # Uncomment to also publish x86_64 wheels: + # - arch: x86_64 + # runs-on: macos-13 + # goarch: amd64 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.24.1' + + # cibuildwheel uses the python.org installer for the in-wheel Python, + # which bakes @rpath-relative libpython install names into the .so — + # the property that makes the wheel loadable by Homebrew/conda/uv + # interpreters. + - name: Build wheels with cibuildwheel + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD: cp312-macosx_${{ matrix.arch }} + CIBW_ARCHS_MACOS: ${{ matrix.arch }} + # CGO must be on for arm64; gopy + goimports must be reachable + # inside the build's hermetic env, so we install them in + # BEFORE_ALL and forward PATH via CIBW_ENVIRONMENT. + CIBW_ENVIRONMENT: > + CGO_ENABLED=1 + GOARCH=${{ matrix.goarch }} + PATH=$PATH:/usr/local/go/bin:$HOME/go/bin + MACOSX_DEPLOYMENT_TARGET=11.0 + CIBW_BEFORE_ALL_MACOS: | + go install github.com/go-python/gopy@v0.4.10 + go install golang.org/x/tools/cmd/goimports@latest + CIBW_BEFORE_BUILD_MACOS: | + pip install pybindgen setuptools wheel setuptools-scm + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.arch }} + path: wheelhouse/*.whl + + publish: + name: Publish wheels to release + needs: [build-linux, build-macos] + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - name: Download all built wheels + uses: actions/download-artifact@v4 + with: + pattern: wheels-* + merge-multiple: true + path: dist/ + + - name: Publish GitHub release with wheels + uses: softprops/action-gh-release@v2 + with: + files: dist/*.whl + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 86df73125c20bfcc388c172f96bd09dd7408b926 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Fri, 1 May 2026 22:40:16 +0700 Subject: [PATCH 2/8] more patches --- Makefile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3cdf739..e9fc9ef 100644 --- a/Makefile +++ b/Makefile @@ -6,5 +6,17 @@ gopy_build: if [ `uname` = "Linux" ]; then \ patchelf --set-rpath '$$ORIGIN' src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ elif [ `uname` = "Darwin" ]; then \ - install_name_tool -add_rpath @loader_path src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ + install_name_tool -id @loader_path/trufnetwork_sdk_c_bindings_go.so \ + src/trufnetwork_sdk_c_bindings/trufnetwork_sdk_c_bindings_go.so; \ + GO_SO_OLD=`otool -L src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so | awk '/trufnetwork_sdk_c_bindings_go\.so/ {print $$1; exit}'`; \ + if [ -z "$$GO_SO_OLD" ]; then \ + echo "FATAL: _trufnetwork_sdk_c_bindings.so does not reference trufnetwork_sdk_c_bindings_go.so"; exit 1; \ + fi; \ + install_name_tool -change "$$GO_SO_OLD" @loader_path/trufnetwork_sdk_c_bindings_go.so \ + src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ + install_name_tool -add_rpath @loader_path \ + src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ + echo "=== otool -L (post-fix) ==="; \ + otool -L src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ + otool -L src/trufnetwork_sdk_c_bindings/trufnetwork_sdk_c_bindings_go.so; \ fi \ No newline at end of file From 7235708903e48672337a301722b34cdd0955af87 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Sat, 2 May 2026 00:31:16 +0700 Subject: [PATCH 3/8] build(macos): pass -dynamic-link to gopy gen so the Go .so defers libpython symbols --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e9fc9ef..0206a4f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ gopy_build: rm -f src/trufnetwork_sdk_c_bindings/*.so - gopy gen -output=src/trufnetwork_sdk_c_bindings -vm=python3 -name=trufnetwork_sdk_c_bindings ./bindings + gopy gen -output=src/trufnetwork_sdk_c_bindings -vm=python3 -name=trufnetwork_sdk_c_bindings -dynamic-link=true ./bindings cd src/trufnetwork_sdk_c_bindings && \ make build if [ `uname` = "Linux" ]; then \ From f198805f49c2f5a82926bdbca3774f98b81261d8 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Sat, 2 May 2026 00:37:30 +0700 Subject: [PATCH 4/8] build(macos): scope -dynamic-link to Darwin and widen CGO_LDFLAGS_ALLOW --- Makefile | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0206a4f..500f8f4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,19 @@ +UNAME_S := $(shell uname) + +# On macOS, link the Go .so dynamically against libpython so Python symbols +# resolve at load time against whatever interpreter imports the module +# (Homebrew/conda/uv/python.org), instead of baking in an absolute path to +# the build-time libpython. The resulting #cgo LDFLAGS contain +# `-undefined dynamic_lookup`, which cgo blocks by default, so widen the +# allow regex for this build. +ifeq ($(UNAME_S),Darwin) +DYNAMIC_LINK_FLAG := -dynamic-link=true +export CGO_LDFLAGS_ALLOW := .* +endif + gopy_build: rm -f src/trufnetwork_sdk_c_bindings/*.so - gopy gen -output=src/trufnetwork_sdk_c_bindings -vm=python3 -name=trufnetwork_sdk_c_bindings -dynamic-link=true ./bindings + gopy gen -output=src/trufnetwork_sdk_c_bindings -vm=python3 -name=trufnetwork_sdk_c_bindings $(DYNAMIC_LINK_FLAG) ./bindings cd src/trufnetwork_sdk_c_bindings && \ make build if [ `uname` = "Linux" ]; then \ From 9c2eb67f6a140039666617ae993d90e9377b1fdc Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Sat, 2 May 2026 00:43:03 +0700 Subject: [PATCH 5/8] build(macos): override gopy-generated Makefile LDFLAGS to drop libpython link --- Makefile | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 500f8f4..b3f4994 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,31 @@ UNAME_S := $(shell uname) -# On macOS, link the Go .so dynamically against libpython so Python symbols -# resolve at load time against whatever interpreter imports the module -# (Homebrew/conda/uv/python.org), instead of baking in an absolute path to -# the build-time libpython. The resulting #cgo LDFLAGS contain -# `-undefined dynamic_lookup`, which cgo blocks by default, so widen the -# allow regex for this build. +# On macOS, decouple both gopy artifacts from a specific libpython: +# * `-dynamic-link=true` switches the #cgo LDFLAGS in the generated .go +# file from `-L... -lpython3.12` to LDSHARED-derived flags +# (`-undefined dynamic_lookup`), so the Go .so defers Python symbols +# to whoever loads it. +# * Overriding LDFLAGS for the gopy-emitted sub-make does the same for +# the gcc step that links the Python C extension wrapper +# (`_trufnetwork_sdk_c_bindings.so`); otherwise gopy hard-codes +# `pycfg.LdFlags` regardless of `-dynamic-link`, leaving the wrapper +# bound to the build-time libpython and triggering the Homebrew +# SIGSEGV the PR is meant to prevent. +# `-undefined dynamic_lookup` is on cgo's denylist, so widen the allow +# regex too. ifeq ($(UNAME_S),Darwin) DYNAMIC_LINK_FLAG := -dynamic-link=true export CGO_LDFLAGS_ALLOW := .* +SUBMAKE_BUILD := LDFLAGS="-undefined dynamic_lookup -Wl,-flat_namespace" make build +else +SUBMAKE_BUILD := make build endif gopy_build: rm -f src/trufnetwork_sdk_c_bindings/*.so gopy gen -output=src/trufnetwork_sdk_c_bindings -vm=python3 -name=trufnetwork_sdk_c_bindings $(DYNAMIC_LINK_FLAG) ./bindings cd src/trufnetwork_sdk_c_bindings && \ - make build + $(SUBMAKE_BUILD) if [ `uname` = "Linux" ]; then \ patchelf --set-rpath '$$ORIGIN' src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ elif [ `uname` = "Darwin" ]; then \ From 88ca9009477e187db2378c96d2b45046eae648c8 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Sat, 2 May 2026 00:47:47 +0700 Subject: [PATCH 6/8] build(macos): pass LDFLAGS as sub-make argument so override actually wins --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b3f4994..3dbb89e 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,12 @@ UNAME_S := $(shell uname) ifeq ($(UNAME_S),Darwin) DYNAMIC_LINK_FLAG := -dynamic-link=true export CGO_LDFLAGS_ALLOW := .* -SUBMAKE_BUILD := LDFLAGS="-undefined dynamic_lookup -Wl,-flat_namespace" make build +# IMPORTANT: pass LDFLAGS as a make command-line argument, not as an env +# var prefix. The gopy-emitted Makefile defines `LDFLAGS = ...` itself, +# and Make precedence gives Makefile assignments priority over env vars +# (command-line args win over both). `LDFLAGS=... make build` would be +# silently ignored. +SUBMAKE_BUILD := make build LDFLAGS="-undefined dynamic_lookup -Wl,-flat_namespace" else SUBMAKE_BUILD := make build endif From 3c8d200747c1fe10035c3ac7b6cb99af40729ea3 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Sat, 2 May 2026 02:12:57 +0700 Subject: [PATCH 7/8] ci: scope release perms to contents:write, bump setup actions to v5, assert no libpython link --- .github/workflows/distribution-test.yml | 6 +++--- .github/workflows/release-build.yml | 9 +++++---- Makefile | 7 +++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/distribution-test.yml b/.github/workflows/distribution-test.yml index dbe8791..35a7e8e 100644 --- a/.github/workflows/distribution-test.yml +++ b/.github/workflows/distribution-test.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.24.1' @@ -47,7 +47,7 @@ jobs: sudo apt-get install -y build-essential patchelf - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' @@ -76,7 +76,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.24.1' diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 5e2762b..8df695d 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -8,7 +8,8 @@ on: # Also allow manual re-runs workflow_dispatch: -permissions: write-all +permissions: + contents: write jobs: build-linux: @@ -20,7 +21,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.24.1' @@ -37,7 +38,7 @@ jobs: sudo apt-get install -y build-essential patchelf - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' @@ -76,7 +77,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.24.1' diff --git a/Makefile b/Makefile index 3dbb89e..a15b9fe 100644 --- a/Makefile +++ b/Makefile @@ -47,4 +47,11 @@ gopy_build: echo "=== otool -L (post-fix) ==="; \ otool -L src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ otool -L src/trufnetwork_sdk_c_bindings/trufnetwork_sdk_c_bindings_go.so; \ + for f in src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so \ + src/trufnetwork_sdk_c_bindings/trufnetwork_sdk_c_bindings_go.so; do \ + if otool -L "$$f" | grep -E 'Python\.framework|libpython' >/dev/null; then \ + echo "FATAL: $$f still references Python (Python.framework or libpython). This will SIGSEGV under non-build-time Python interpreters (Homebrew/conda/uv). Verify -dynamic-link=true and the LDFLAGS sub-make override are taking effect."; \ + exit 1; \ + fi; \ + done; \ fi \ No newline at end of file From 9deb11b86cdddedfac87464b17b03c7907ca1f94 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Sat, 2 May 2026 02:39:38 +0700 Subject: [PATCH 8/8] ci(release): smoke-test wheel before publish; make Darwin install_name block fail-fast --- .github/workflows/release-build.yml | 15 +++++++++++++++ Makefile | 1 + 2 files changed, 16 insertions(+) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 8df695d..dce927f 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -104,6 +104,21 @@ jobs: CIBW_BEFORE_BUILD_MACOS: | pip install pybindgen setuptools wheel setuptools-scm + # Smoke-test the wheel before publishing: install it into a fresh + # interpreter and load the C bindings. Catches packaging breakage + # and the SIGSEGV-class regression the PR fixes (the import alone + # triggers loading both gopy .so files). + - name: Set up Python for wheel smoke test + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Smoke-test built wheel + run: | + python -m venv /tmp/smoke-venv + /tmp/smoke-venv/bin/pip install wheelhouse/*.whl + /tmp/smoke-venv/bin/python -c "from trufnetwork_sdk_py.client import TNClient; print('wheel import ok')" + - name: Upload wheels uses: actions/upload-artifact@v4 with: diff --git a/Makefile b/Makefile index a15b9fe..81017d1 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ gopy_build: if [ `uname` = "Linux" ]; then \ patchelf --set-rpath '$$ORIGIN' src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so; \ elif [ `uname` = "Darwin" ]; then \ + set -e; \ install_name_tool -id @loader_path/trufnetwork_sdk_c_bindings_go.so \ src/trufnetwork_sdk_c_bindings/trufnetwork_sdk_c_bindings_go.so; \ GO_SO_OLD=`otool -L src/trufnetwork_sdk_c_bindings/_trufnetwork_sdk_c_bindings.so | awk '/trufnetwork_sdk_c_bindings_go\.so/ {print $$1; exit}'`; \