Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
44cf245
Establish `BackendFromEnv` machinery
b-long Sep 19, 2026
9cd2233
Use experimental cffi backend by GOPY_BACKEND=cffi
b-long Sep 19, 2026
3bbe911
Create a separate cffi job for Github Actions
b-long Sep 19, 2026
f7da474
Prevent cffi from unloading Go shared library
b-long Sep 19, 2026
2d8f4e2
Add to/from bytes & `pkg` mode, start Complex nums
b-long Sep 19, 2026
93188fb
Complete complex64/128 number support
b-long Sep 19, 2026
36c319c
Support Python callbacks in cffi backend
b-long Sep 20, 2026
d1206d0
Add interface{} args & results in cffi callbacks
b-long Sep 20, 2026
a65a870
Complete cffi support for full test suite
b-long Sep 20, 2026
363ee92
Isolate cffi errors per OS thread
b-long Sep 24, 2026
af1fe2d
Fix cffi DLL load failure on Windows
b-long Sep 24, 2026
6454e06
Generalize cffi impl, start pybind11 GOPY_BACKEND
b-long Sep 25, 2026
d385fff
Add pybind11 backend to CI matrix
b-long Sep 25, 2026
b7474fd
Fix pybind11 DLL load failure on Windows
b-long Sep 25, 2026
3460138
fix: static-link libstdc++/libgcc on Windows
b-long Sep 25, 2026
9325cee
Add []byte support, shrink pybind11 CI skips
b-long Sep 26, 2026
faed63e
Raise pybind11 CI test timeout to 30m
b-long Sep 26, 2026
9fb3276
Support complex numbers in pybind11 backend
b-long Sep 26, 2026
4af5643
Add pybind11 callbacks; fix GIL deadlock and crash
b-long Sep 26, 2026
325a6a5
Add benchmark job for cffi, pybind11, & pybindgen
b-long Sep 26, 2026
cfffb88
Add nanobind backend (GOPY_BACKEND=nanobind)
b-long Sep 27, 2026
d703fa8
Unskip TestHi for pybind11 and nanobind backends
b-long Oct 4, 2026
6c41564
Fix generated Makefile for pybind11 and nanobind
b-long Oct 4, 2026
c047d50
Fix nanobind Makefile on Windows
b-long Oct 4, 2026
c5ba697
Add capi backend (GOPY_BACKEND=capi)
b-long Oct 4, 2026
3d241d8
Fix generated Makefile for pybindgen and capi
b-long Oct 4, 2026
2078752
Drop cgo backend and unused extraGccArgs
b-long Oct 4, 2026
bfecfd7
Deduplicate backend CI jobs and build code
b-long Oct 4, 2026
d399371
Test callbacks & makeShellArg, update README.md
b-long Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/requirements-capi.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Python packages for the GOPY_BACKEND=capi job in workflows/ci.yml.
# pybindgen is deliberately absent: the capi backend must not need it.
# used by the memory-leak checks on Windows, where the resource module is missing
psutil
5 changes: 5 additions & 0 deletions .github/requirements-cffi.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Python packages for the GOPY_BACKEND=cffi job in workflows/ci.yml.
# pybindgen is deliberately absent: the cffi backend must not need it.
cffi
# used by the memory-leak checks on Windows, where the resource module is missing
psutil
5 changes: 5 additions & 0 deletions .github/requirements-nanobind.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Python packages for the GOPY_BACKEND=nanobind job in workflows/ci.yml.
# pybindgen is deliberately absent: the nanobind backend must not need it.
nanobind
# used by the memory-leak checks on Windows, where the resource module is missing
psutil
5 changes: 5 additions & 0 deletions .github/requirements-pybind11.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Python packages for the GOPY_BACKEND=pybind11 job in workflows/ci.yml.
# pybindgen is deliberately absent: the pybind11 backend must not need it.
pybind11
# used by the memory-leak checks on Windows, where the resource module is missing
psutil
104 changes: 104 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,107 @@ jobs:
- name: Upload-Coverage
if: matrix.platform == 'ubuntu-latest'
uses: codecov/codecov-action@v4

# Builds and tests each opt-in backend (GOPY_BACKEND), beside the main
# matrix and on one Go version. None installs pybindgen: no backend here
# may need it.
# cffi: loads the cgo shim at runtime, with no C compiler step.
# pybind11: compiles a C++ module against the same shim (see noAPIShim in
# bind/noapi.go), so needs no extra system packages: a C++
# compiler is already required for CGO_ENABLED=1 everywhere.
# nanobind: as pybind11 (see buildCXXModule in cmd_build.go), and also
# compiles nanobind's own runtime (nb_combined.cpp) into every
# module.
# capi: builds exactly as the default backend does, except that
# build.py writes the C module itself (see bind/capi_build.py).
backends:
name: ${{ matrix.backend }} backend (${{ matrix.platform }}, Python ${{ matrix.python-version }})
strategy:
# a failure in one backend doesn't cancel the others
fail-fast: false
matrix:
backend: [cffi, pybind11, nanobind, capi]
platform: [ubuntu-latest, windows-latest, macos-15]
python-version: ['3.11', '3.12']
include:
# CXX defaults to "c++" in buildCXXModule (cmd_build.go), which
# isn't a recognized command on windows-latest's MinGW toolchain (the
# same one CGO_ENABLED=1 already needs there, so no extra install is
# needed). timeout raises go test's own default (10m): each test
# here compiles twice (cgo, then a separate C++ step), and once
# measured at ~18.5s/test average on windows-latest, 32 tests alone
# used 590s of the default budget.
- backend: pybind11
cxx: g++
timeout: 30m
- backend: nanobind
cxx: g++
timeout: 30m
runs-on: ${{ matrix.platform }}
env:
GOPY_BACKEND: ${{ matrix.backend }}
CXX: ${{ matrix.cxx }}
# print the python stack if the process crashes, e.g. at exit
PYTHONFAULTHANDLER: 1
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: .github/requirements-${{ matrix.backend }}.txt

- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.25.x
cache: true

- name: Install packages
run: |
python -m pip install -r .github/requirements-${{ matrix.backend }}.txt
go install golang.org/x/tools/cmd/goimports@v0.29.0

- name: Build
run: go build -v ./...

- name: Test
run: go test -v -timeout=${{ matrix.timeout || '10m' }} ./...

# Compares per-call overhead across backends (see _examples/bench/run.sh):
# not a pass/fail check, just a table uploaded as a build artifact. Runs
# on ubuntu-latest only -- the comparison is between backends, not OSes.
benchmark:
name: benchmark backends
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.25.x
cache: true

- name: Install packages
run: |
python -m pip install pybindgen cffi pybind11 nanobind
go install golang.org/x/tools/cmd/goimports@v0.29.0

- name: Run benchmark
run: bash _examples/bench/run.sh | tee benchmark.txt

- name: Upload benchmark table
uses: actions/upload-artifact@v4
with:
name: benchmark
path: benchmark.txt
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ New features:

Gopy now assumes that you are working with modules-based builds, and requires a valid `go.mod` file, and works only with Go versions 1.15 and above.

Currently using [pybindgen](https://pybindgen.readthedocs.io/en/latest/tutorial/) to generate the low-level c-to-python bindings, but support for [cffi](https://cffi.readthedocs.io/en/latest/) should be relatively straightforward for those using PyPy instead of CPython (pybindgen should be significantly faster for CPython apparently). You also need `goimports` to ensure the correct imports are included.
By default, gopy uses [pybindgen](https://pybindgen.readthedocs.io/en/latest/tutorial/) to generate the low-level c-to-python bindings. You also need `goimports` to ensure the correct imports are included.

```sh
$ python3 -m pip install pybindgen
Expand All @@ -29,6 +29,22 @@ $ go install github.com/go-python/gopy@latest

(This all assumes you have already installed [Go itself](https://golang.org/doc/install), and added `~/go/bin` to your `PATH`).

### Choosing a backend (experimental)

The `GOPY_BACKEND` environment variable picks a different tool for those bindings, for `gopy gen`, `gopy build` and `gopy pkg`:

| `GOPY_BACKEND` | Needs | |
|---|---|---|
| `pybindgen` (default) | `pip install pybindgen` | |
| `capi` | nothing beyond python itself | writes the C-API bindings directly, without pybindgen |
| `cffi` | `pip install cffi` | |
| `pybind11` | `pip install pybind11`, a C++ compiler | |
| `nanobind` | `pip install nanobind`, a C++ compiler | |

```sh
$ GOPY_BACKEND=capi gopy build github.com/go-python/gopy/_examples/hi
```

To [install python modules](https://packaging.python.org/tutorials/packaging-projects/), you will need the python install packages:

```sh
Expand Down
1 change: 1 addition & 0 deletions SUPPORT_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ don't modify manually.
Feature |py3
--- | ---
_examples/arrays | yes
_examples/callbacks | yes
_examples/cgo | yes
_examples/consts | yes
_examples/cstrings | yes
Expand Down
23 changes: 23 additions & 0 deletions _examples/bench/bench.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2026 The go-python Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package bench has a few trivial functions used to compare the per-call
// overhead of gopy's backends (GOPY_BACKEND); see bench.py and the
// "benchmark" job in .github/workflows/ci.yml. Not part of the test suite
// itself: no _examples/bench entry in main_test.go's features map.
package bench

// Add returns the sum of its arguments: the cheapest possible call, to
// isolate per-call FFI overhead from any argument-marshaling cost.
func Add(i, j int) int {
return i + j
}

// Concat concatenates two strings: a second data point, since string
// arguments/returns cross the boundary very differently from an int
// (a managed buffer + length or a null-terminated copy, depending on the
// backend) and might not share the int case's relative cost.
func Concat(a, b string) string {
return a + b
}
40 changes: 40 additions & 0 deletions _examples/bench/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/bash
# Copyright 2026 The go-python Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

# Builds _examples/bench under each of gopy's backends and prints a table
# comparing run_bench.py's timing/memory numbers across them. Run from the repo
# root; needs pybindgen, cffi, pybind11 and nanobind all installed for the python
# interpreter named by $PYTHON (defaults to python3), and a C++ compiler.
set -eu

PYTHON="${PYTHON:-python3}"
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

echo "building gopy..."
GOPY="$WORK/gopy"
(cd "$REPO" && go build -o "$GOPY" .)

printf '%-10s %8s %14s %14s %10s\n' backend calls "add (s)" "concat (s)" "peak (KB)"

for backend in pybindgen capi cffi pybind11 nanobind; do
out="$WORK/$backend"
mkdir -p "$out"
# gopy build cds into -output and runs go build there; give it a module
# that resolves back to this checkout, same as go.mod already does for
# anyone building _examples/* in place.
printf 'module dummy\n\nrequire github.com/go-python/gopy v0.0.0\nreplace github.com/go-python/gopy => %s\n' "$REPO" >"$out/go.mod"
GOPY_BACKEND="$backend" "$GOPY" build -vm="$PYTHON" -output="$out" -no-make -package-prefix= \
"$REPO/_examples/bench" >"$out/build.log" 2>&1 || {
echo "$backend: build failed, see $out/build.log" >&2
tail -n 20 "$out/build.log" >&2
continue
}
cp "$REPO/_examples/bench/run_bench.py" "$out/"
row="$(cd "$out" && "$PYTHON" run_bench.py)"
IFS=, read -r calls add_s concat_s peak_kb <<<"$row"
printf '%-10s %8s %14s %14s %10s\n' "$backend" "$calls" "$add_s" "$concat_s" "$peak_kb"
done
47 changes: 47 additions & 0 deletions _examples/bench/run_bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2026 The go-python Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

# Times N calls each of bench.Add and bench.Concat, and the peak memory
# after them, printing one CSV line: iterations,add_seconds,concat_seconds,peak_kb
# (see run.sh, which builds this example under each backend and prints the
# resulting rows as a table -- this script itself doesn't know which
# backend it was built with).

from __future__ import print_function

import sys
import time

import bench

if sys.platform == "win32":
import psutil

def peak_kb():
return psutil.Process().memory_info().rss // 1024
else:
import resource

def peak_kb():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss


N = 1000
WARMUP = 100

for i in range(WARMUP):
bench.Add(i, i)
bench.Concat("a", "b")

start = time.perf_counter()
for i in range(N):
bench.Add(i, i)
add_seconds = time.perf_counter() - start

start = time.perf_counter()
for i in range(N):
bench.Concat("a", "b")
concat_seconds = time.perf_counter() - start

print("%d,%f,%f,%d" % (N, add_seconds, concat_seconds, peak_kb()))
Loading
Loading