Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 24 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,43 @@ env:
GOTRACEBACK: crash
GO111MODULE: auto

# A new push to a pull request cancels its previous, now outdated, run.
# Pushes to master always run to completion: each one adds to the coverage
# history.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:

build:
name: Build (${{ matrix.platform }}, Go ${{ matrix.go-version }}, Python ${{ matrix.python-version }})
strategy:
fail-fast: false
matrix:
# TODO: Consider official support matrix (OS and Go versions) and adjust this matrix accordingly
go-version: [1.25.x, 1.24.x, 1.23.x, 1.22.x]
platform: [ubuntu-latest, windows-latest, macos-15]
python-version: ['3.11', '3.12']
runs-on: ${{ matrix.platform }}
# A hung step fails here instead of at GitHub's 6h default.
timeout-minutes: 20
env:
# Coverage is measured on exactly one matrix leg, so the numbers stay
# comparable from commit to commit.
COVERAGE_LEG: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.25.x' && matrix.python-version == '3.12' }}
steps:
# Real-time scanning inspects every file the compilers, linkers and pip
# write, which slows Windows jobs down. Turned off first, so checkout and
# the Go/Python installs benefit too. The status is printed before and
# after so the effect can be checked in the log.
- name: Disable Windows Defender real-time scanning
if: runner.os == 'Windows'
shell: pwsh
run: |
Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled
Set-MpPreference -DisableRealtimeMonitoring $true
Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled

- name: Checkout code
uses: actions/checkout@v4

Expand All @@ -48,26 +68,24 @@ jobs:
- name: Install Linux packages
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install curl libffi-dev python3-cffi python3-pip
# install pybindgen
python3 -m pip install --user -U pybindgen
# install goimports
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/tools/cmd/goimports@v0.29.0

- name: Install macOS packages
if: startsWith(matrix.platform, 'macos-')
run: |
python3 -m pip install -U pybindgen
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/tools/cmd/goimports@v0.29.0

- name: Install Windows packages
if: matrix.platform == 'windows-latest'
run: |
# install pybindgen and psutil (for memory tracking in tests)
python -m pip install -U pybindgen psutil
# install goimports
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/tools/cmd/goimports@v0.29.0


- name: Build-Linux
Expand Down
23 changes: 14 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# IDE files
.idea/
*.iml

# goreleaser build output
/dist/

# 'make cover' output
/coverage.out
# IDE files
.idea/
*.iml

# goreleaser build output
/dist/

# python bytecode caches
__pycache__/
*.py[co]

# 'make cover' output
/coverage.out
/covdata/
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ test:

# Same as 'test', but records statement coverage of every gopy package
# (-coverpkg), since most of bind/ is only exercised via main_test.go.
# main_test.go runs gopy as a separate process (see TestMain), which
# -coverprofile can't see, so the test binaries and gopy all write raw
# coverage data to COVERDIR, merged into COVERPROFILE afterwards.
COVERPROFILE=coverage.out
COVERDIR=$(CURDIR)/covdata

cover:
@echo "GO111MODULE = $(value GO111MODULE)"
$(GOTEST) -v -coverpkg=./... -coverprofile=$(COVERPROFILE) $(DIRS)
rm -rf $(COVERDIR) && mkdir -p $(COVERDIR)
$(GOTEST) -v -cover -coverpkg=./... $(DIRS) -args -test.gocoverdir=$(COVERDIR)
$(GOCMD) tool covdata textfmt -i=$(COVERDIR) -o=$(COVERPROFILE)
$(GOCMD) tool cover -func=$(COVERPROFILE) | tail -n 1

clean:
Expand Down
Binary file removed _examples/funcs/__pycache__/test.cpython-36.pyc
Binary file not shown.
13 changes: 7 additions & 6 deletions bind/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ type BuildMode string

const (
ModeGen BuildMode = "gen"
ModeBuild = "build"
ModeExe = "exe"
ModePkg = "pkg"
ModeBuild BuildMode = "build"
ModeExe BuildMode = "exe"
ModePkg BuildMode = "pkg"
)

// set this to true if OS is windows
Expand Down Expand Up @@ -496,13 +496,14 @@ build:
printf "#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 202311L)\ntypedef uint8_t bool;\n#endif\n" > %[1]s_go.h
# this will fail but is needed to generate the .c file that then allows go build to work
- $(PYTHON) build.py >/dev/null 2>&1
# generate %[1]s_go.h from %[1]s.go -- unfortunately no way to build .h only
$(GOBUILD) -buildmode=c-shared -o %[1]s_go$(LIBEXT)
# generate %[1]s_go.h from %[1]s.go -- unfortunately no way to build .h only;
# a c-archive doesn't link, so the stale %[1]s.c can't fail it
$(GOBUILD) -buildmode=c-archive -o %[1]s_go.a
# use pybindgen to build the %[1]s.c file which are the CPython wrappers to cgo wrappers..
# note: pip install pybindgen to get pybindgen if this fails
$(PYTHON) build.py
# build the executable
- rm %[1]s_go$(LIBEXT)
- rm %[1]s_go.a
$(GOBUILD) -o py%[1]s

`
Expand Down
1 change: 1 addition & 0 deletions bind/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
)

func TestPrinter(t *testing.T) {
t.Parallel()
decl := &printer{
buf: new(bytes.Buffer),
indentEach: []byte("\t"),
Expand Down
3 changes: 3 additions & 0 deletions bind/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
)

func TestGetGoVersion(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
info string
major int64
Expand Down Expand Up @@ -40,6 +41,7 @@ func TestGetGoVersion(t *testing.T) {
}

func TestExtractPythonName(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
name string
goDoc string
Expand Down Expand Up @@ -71,6 +73,7 @@ func TestExtractPythonName(t *testing.T) {
}

func TestPythonConfig(t *testing.T) {
t.Parallel()
t.Skip()

for _, tc := range []struct {
Expand Down
31 changes: 24 additions & 7 deletions cmd_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func gopyRunCmdBuild(cmdr *commander.Command, args []string) error {
cfg.Name = pkg.Name()
}
}
return runBuild("build", cfg)
return runBuild(bind.ModeBuild, cfg)
}

// runBuild calls genPkg and then executes commands to build the resulting files
Expand Down Expand Up @@ -134,11 +134,17 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error {
cmd = exec.Command(cfg.VM, "build.py")
cmd.Run() // will fail, we don't care about errors

args := []string{"build", "-mod=mod", "-buildmode=c-shared"}
// This build only writes buildname.h, the header the second
// build.py needs. A c-archive writes it without linking: the .c
// file the first build.py left behind was compiled against the
// placeholder header above, so it may call functions (such as the
// static _gopy_clear_go_tls) it can't link to yet, which macOS and
// Windows refuse in a shared library.
args := []string{"build", "-mod=mod", "-buildmode=c-archive"}
if cfg.BuildTags != "" {
args = append(args, "-tags", cfg.BuildTags)
}
args = append(args, "-o", buildname+libExt, ".")
args = append(args, "-o", buildname+".a", ".")

fmt.Printf("go %v\n", strings.Join(args, " "))
cmd = exec.Command("go", args...)
Expand All @@ -156,14 +162,15 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error {
return err
}

err = os.Remove(cfg.Name + "_go" + libExt)
err = os.Remove(buildname + ".a")

fmt.Printf("go build -o py%s\n", cfg.Name)
cmd = exec.Command("go", "build", "-mod=mod")
args = []string{"build", "-mod=mod"}
if cfg.BuildTags != "" {
args = append(args, "-tags", cfg.BuildTags)
}
args = append(args, "-o", "py"+cfg.Name)
args = append(args, "-o", exeName(cfg.Name))
fmt.Printf("go %v\n", strings.Join(args, " "))
cmd = exec.Command("go", args...)
cmdout, err = cmd.CombinedOutput()
if err != nil {
fmt.Printf("cmd had error: %v output:\n%v\n", err, string(cmdout))
Expand Down Expand Up @@ -333,3 +340,13 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error {

return err
}

// exeName returns the file name of the executable gopy exe builds for the
// package named name: py<name>, as the Makefile it writes also builds, with
// Windows' .exe suffix there (go build -o adds none).
func exeName(name string) string {
if runtime.GOOS == "windows" {
return "py" + name + ".exe"
}
return "py" + name
}
8 changes: 8 additions & 0 deletions cmd_exe.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ func gopyRunCmdExe(cmdr *commander.Command, args []string) error {
return err
}

// GenPyPkgSetup chdirs into the output dir; restore the caller's cwd
// when we're done, so a run doesn't strand the process there.
cwd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(cwd)

setupfn := filepath.Join(cfg.OutputDir, "setup.py")

if _, err = os.Stat(setupfn); os.IsNotExist(err) {
Expand Down
8 changes: 8 additions & 0 deletions cmd_pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ func gopyRunCmdPkg(cmdr *commander.Command, args []string) error {
return err
}

// GenPyPkgSetup chdirs into the output dir; restore the caller's cwd
// when we're done, so a run doesn't strand the process there.
cwd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(cwd)

setupfn := filepath.Join(cfg.OutputDir, "setup.py")

if _, err = os.Stat(setupfn); os.IsNotExist(err) {
Expand Down
8 changes: 8 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ coverage:
patch:
default:
informational: true

# Go profiles mark a line "partial" when it holds more than one block and
# only some ran, typically `if err != nil {` whose condition ran but whose
# body didn't. The body's own lines already count as misses, and go tool
# cover counts the condition as covered, so count the line as a hit too.
parsers:
go:
partials_as_hits: true
Loading
Loading