diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5be3246..29987c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,6 @@ env: # GOPY_TRAVIS_CI is set GOPY_TRAVIS_CI: 1 GOTRACEBACK: crash - PYPYVERSION: "v7.1.1" GO111MODULE: auto jobs: @@ -22,7 +21,7 @@ jobs: name: Build strategy: matrix: - go-version: [1.20.x, 1.19.x] + go-version: [1.22.x, 1.21.x] platform: [ubuntu-latest] #platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} @@ -53,18 +52,8 @@ jobs: run: | sudo apt-get update sudo apt-get install curl libffi-dev python3-cffi python3-pip - # pypy3 isn't packaged in ubuntu yet. - TEMPDIR=$(mktemp -d) - curl -L https://downloads.python.org/pypy/pypy3.6-${PYPYVERSION}-linux64.tar.bz2 --output $TEMPDIR/pypy3.tar.bz2 - tar xf $TEMPDIR/pypy3.tar.bz2 -C $TEMPDIR - sudo ln -s $TEMPDIR/pypy3.6-$PYPYVERSION-linux64/bin/pypy3 /usr/local/bin/pypy3 - # curl -L https://bootstrap.pypa.io/get-pip.py --output ${TEMPDIR}/get-pip.py - # pypy3 ${TEMPDIR}/get-pip.py - # install pybindgen python3 -m pip install --user -U pybindgen - # pypy3 -m pip install --user -U pybindgen - # install goimports go install golang.org/x/tools/cmd/goimports@latest diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index a647624..8b1d620 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -6,8 +6,9 @@ The `go-python` project (and `gopy`) eagerly accepts contributions from the comm The `go-python` project provides libraries and tools in Go for the Go community to better integrate with Python projects and libraries, and we would like you to join us in improving `go-python`'s quality and scope. -This document is for contributors or those interested in contributing. -Questions about `go-python` and the use of its libraries can be directed to the [go-python](mailto:go-python@googlegroups.com) mailing list. +This document is for contributors or those interested in contributing. + +Questions about `gopy` can be directed to the [the `gopy` discussions area in Github: https://github.com/go-python/gopy/discussions]. ## Contributing @@ -35,7 +36,7 @@ As a rule, we keep all tests OK and try to increase code coverage. ### Suggesting Enhancements If the scope of the enhancement is small, open an issue. -If it is large, such as suggesting a new repository, sub-repository, or interface refactoring, then please start a discussion on [the go-python list](https://groups.google.com/forum/#!forum/go-python). +If it is large, such as suggesting a new repository, sub-repository, or interface refactoring, then please start a discussion using [the `gopy` discussions area in Github: https://github.com/go-python/gopy/discussions]. ### Your First Code Contribution @@ -140,3 +141,5 @@ We use [Go style](https://github.com/golang/go/wiki/CodeReviewComments). This _"Contributing"_ guide has been extracted from the [Gonum](https://gonum.org) project. Its guide is [here](https://github.com/gonum/license/blob/master/CONTRIBUTING.md). + +[the `gopy` discussions area in Github: https://github.com/go-python/gopy/discussions]: https://github.com/go-python/gopy/discussions \ No newline at end of file diff --git a/Makefile b/Makefile index 02e10c7..604892a 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ prereq: # NOTE: MUST update version number here prior to running 'make release' and edit this file! -VERS=v0.4.8 +VERS=v0.4.10 PACKAGE=main GIT_COMMIT=`git rev-parse --short HEAD` VERS_DATE=`date -u +%Y-%m-%d\ %H:%M` diff --git a/SUPPORT_MATRIX.md b/SUPPORT_MATRIX.md index 9153644..8e73c1a 100644 --- a/SUPPORT_MATRIX.md +++ b/SUPPORT_MATRIX.md @@ -11,6 +11,7 @@ _examples/consts | yes _examples/cstrings | yes _examples/empty | yes _examples/funcs | yes +_examples/gobytes | yes _examples/gopygc | yes _examples/gostrings | yes _examples/hi | yes diff --git a/_examples/gobytes/gobytes.go b/_examples/gobytes/gobytes.go new file mode 100644 index 0000000..f7721e8 --- /dev/null +++ b/_examples/gobytes/gobytes.go @@ -0,0 +1,33 @@ +// Copyright 2017 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 gobytes + +func HashBytes(b []byte) [4]byte { + result := [4]byte{0, 0, 0, 0} + full_blocks := len(b) / 4 + for i := 0; i < full_blocks; i++ { + for j := 0; j < 4; j++ { + result[j] ^= b[4*i+j] + } + } + if full_blocks*4 < len(b) { + for j := 0; j < 4; j++ { + if full_blocks*4+j < len(b) { + result[j] ^= b[full_blocks*4+j] + } else { + result[j] ^= 0x55 + } + } + } + return result +} + +func CreateBytes(len byte) []byte { + res := make([]byte, len) + for i := (byte)(0); i < len; i++ { + res[i] = i + } + return res +} diff --git a/_examples/gobytes/test.py b/_examples/gobytes/test.py new file mode 100644 index 0000000..09887bc --- /dev/null +++ b/_examples/gobytes/test.py @@ -0,0 +1,18 @@ +# Copyright 2017 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. + +from __future__ import print_function +import gobytes, go + +a = bytes([0, 1, 2, 3]) +b = gobytes.CreateBytes(10) +print ("Python bytes:", a) +print ("Go slice: ", b) + +print ("gobytes.HashBytes from Go bytes:", gobytes.HashBytes(b)) + +print("Python bytes to Go: ", go.Slice_byte.from_bytes(a)) +print("Go bytes to Python: ", bytes(go.Slice_byte([3, 4, 5]))) + +print("OK") diff --git a/_examples/slices/slices.go b/_examples/slices/slices.go index 6c6fb1c..baa5d7e 100644 --- a/_examples/slices/slices.go +++ b/_examples/slices/slices.go @@ -60,3 +60,16 @@ func CmplxSqrt(arr SliceComplex) SliceComplex { } return res } + +func GetEmptyMatrix(xSize int, ySize int) [][]bool { + result := [][]bool{} + + for i := 0; i < xSize; i++ { + result = append(result, []bool{}) + for j := 0; j < ySize; j++ { + result[i] = append(result[i], false) + } + } + + return result +} diff --git a/_examples/slices/test.py b/_examples/slices/test.py index 6dc3f22..143f453 100644 --- a/_examples/slices/test.py +++ b/_examples/slices/test.py @@ -44,4 +44,11 @@ assert math.isclose(root_squared.real, orig.real) assert math.isclose(root_squared.imag, orig.imag) + +matrix = slices.GetEmptyMatrix(4,4) +for i in range(4): + for j in range(4): + assert not matrix[i][j] +print("[][]bool working as expected") + print("OK") diff --git a/appveyor.yml b/appveyor.yml index 03aeeb6..98b9cd9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,15 +14,13 @@ branches: environment: GOPATH: C:\gopath - GOROOT: C:\go119 + GOROOT: C:\go121 GOPY_APPVEYOR_CI: '1' GOTRACEBACK: 'crash' - #CPYTHON2DIR: "C:\\Python27-x64" - CPYTHON3DIR: "C:\\Python37-x64" - #PATH: '%GOPATH%\bin;%CPYTHON2DIR%;%CPYTHON2DIR%\\Scripts;%CPYTHON3DIR%;%CPYTHON3DIR%\\Scripts;C:\msys64\mingw64\bin;C:\msys64\usr\bin\;%PATH%' + CPYTHON3DIR: "C:\\Python311-x64" PATH: '%GOPATH%\bin;%GOROOT%\bin;%CPYTHON3DIR%;%CPYTHON3DIR%\\Scripts;C:\msys64\mingw64\bin;C:\msys64\usr\bin\;%PATH%' -stack: go 1.19 +stack: go 1.21 build_script: - python --version @@ -33,6 +31,7 @@ build_script: - go version - go env - go get -v -t ./... + - go install golang.org/x/tools/cmd/goimports@latest test_script: - go test ./... diff --git a/bind/gen.go b/bind/gen.go index c8b6756..05fd70e 100644 --- a/bind/gen.go +++ b/bind/gen.go @@ -863,3 +863,13 @@ func (g *pyGen) genGoPkg() { g.genType(sym, false, false) // not exttypes } } + +// genStringerCall generates a call to either self.String() or self.string() +// depending on RenameCase option +func (g *pyGen) genStringerCall() { + if g.cfg.RenameCase { + g.pywrap.Printf("return self.string()\n") + } else { + g.pywrap.Printf("return self.String()\n") + } +} diff --git a/bind/gen_map.go b/bind/gen_map.go index 27c1d86..f65111f 100644 --- a/bind/gen_map.go +++ b/bind/gen_map.go @@ -134,7 +134,7 @@ otherwise parameter is a python list that we copy from if isStringer(m.obj) { g.pywrap.Printf("def __str__(self):\n") g.pywrap.Indent() - g.pywrap.Printf("return self.String()\n") + g.genStringerCall() g.pywrap.Outdent() g.pywrap.Printf("\n") } @@ -303,7 +303,14 @@ otherwise parameter is a python list that we copy from g.gofile.Outdent() g.gofile.Printf("}\n") if esym.go2py != "" { - g.gofile.Printf("return %s(v)%s\n", esym.go2py, esym.go2pyParenEx) + // If the go2py starts with handleFromPtr_, use &v, otherwise just v + val_str := "" + if strings.HasPrefix(esym.go2py, "handleFromPtr_") { + val_str = "&v" + } else { + val_str = "v" + } + g.gofile.Printf("return %s(%s)%s\n", esym.go2py, val_str, esym.go2pyParenEx) } else { g.gofile.Printf("return v\n") } diff --git a/bind/gen_slice.go b/bind/gen_slice.go index bb33214..8df9ded 100644 --- a/bind/gen_slice.go +++ b/bind/gen_slice.go @@ -129,7 +129,7 @@ otherwise parameter is a python list that we copy from if isStringer(m.obj) { g.pywrap.Printf("def __str__(self):\n") g.pywrap.Indent() - g.pywrap.Printf("return self.String()\n") + g.genStringerCall() g.pywrap.Outdent() } } @@ -277,6 +277,25 @@ otherwise parameter is a python list that we copy from g.pywrap.Outdent() g.pywrap.Outdent() } + + if slNm == "Slice_byte" { + g.pywrap.Printf("@staticmethod\n") + g.pywrap.Printf("def from_bytes(value):\n") + g.pywrap.Indent() + g.pywrap.Printf(`"""Create a Go []byte object from a Python bytes object""" +`) + g.pywrap.Printf("handle = _%s_from_bytes(value)\n", qNm) + g.pywrap.Printf("return Slice_byte(handle=handle)\n") + g.pywrap.Outdent() + g.pywrap.Printf("def __bytes__(self):\n") + g.pywrap.Indent() + g.pywrap.Printf(`"""Convert the slice to a bytes object.""" +`) + g.pywrap.Printf("return _%s_to_bytes(self.handle)\n", qNm) + g.pywrap.Outdent() + g.pywrap.Outdent() + + } } if !extTypes || !pyWrapOnly { @@ -306,11 +325,14 @@ otherwise parameter is a python list that we copy from g.gofile.Indent() g.gofile.Printf("s := deptrFromHandle_%s(handle)\n", slNm) if esym.go2py != "" { - if !esym.isPointer() && esym.isStruct() { - g.gofile.Printf("return %s(&(s[_idx]))%s\n", esym.go2py, esym.go2pyParenEx) + // If the go2py starts with handleFromPtr_, use reference &, otherwise just return the value + val_str := "" + if strings.HasPrefix(esym.go2py, "handleFromPtr_") { + val_str = "&(s[_idx])" } else { - g.gofile.Printf("return %s(s[_idx])%s\n", esym.go2py, esym.go2pyParenEx) + val_str = "s[_idx]" } + g.gofile.Printf("return %s(%s)%s\n", esym.go2py, val_str, esym.go2pyParenEx) } else { g.gofile.Printf("return s[_idx]\n") } @@ -367,6 +389,37 @@ otherwise parameter is a python list that we copy from g.pybuild.Printf("mod.add_function('%s_append', None, [param('%s', 'handle'), param('%s', 'value'%s)])\n", slNm, PyHandle, esym.cpyname, transfer_ownership) } + + if slNm == "Slice_byte" { + g.gofile.Printf("//export Slice_byte_from_bytes\n") + g.gofile.Printf("func Slice_byte_from_bytes(o *C.PyObject) CGoHandle {\n") + g.gofile.Indent() + g.gofile.Printf("size := C.PyBytes_Size(o)\n") + g.gofile.Printf("ptr := unsafe.Pointer(C.PyBytes_AsString(o))\n") + g.gofile.Printf("data := make([]byte, size)\n") + g.gofile.Printf("tmp := unsafe.Slice((*byte)(ptr), size)\n") + g.gofile.Printf("copy(data, tmp)\n") + g.gofile.Printf("return handleFromPtr_Slice_byte(&data)\n") + g.gofile.Outdent() + g.gofile.Printf("}\n\n") + + g.gofile.Printf("//export Slice_byte_to_bytes\n") + g.gofile.Printf("func Slice_byte_to_bytes(handle CGoHandle) *C.PyObject {\n") + g.gofile.Indent() + g.gofile.Printf("s := deptrFromHandle_Slice_byte(handle)\n") + g.gofile.Printf("ptr := unsafe.Pointer(&s[0])\n") + g.gofile.Printf("size := len(s)\n") + if WindowsOS { + g.gofile.Printf("return C.PyBytes_FromStringAndSize((*C.char)(ptr), C.longlong(size))\n") + } else { + g.gofile.Printf("return C.PyBytes_FromStringAndSize((*C.char)(ptr), C.long(size))\n") + } + g.gofile.Outdent() + g.gofile.Printf("}\n\n") + + g.pybuild.Printf("mod.add_function('Slice_byte_from_bytes', retval('%s'%s), [param('PyObject*', 'o', transfer_ownership=False)])\n", PyHandle, caller_owns_ret) + g.pybuild.Printf("mod.add_function('Slice_byte_to_bytes', retval('PyObject*', caller_owns_return=True), [param('%s', 'handle')])\n", PyHandle) + } } } diff --git a/bind/gen_struct.go b/bind/gen_struct.go index 815c083..b50ddfc 100644 --- a/bind/gen_struct.go +++ b/bind/gen_struct.go @@ -101,7 +101,7 @@ in which case a new Go object is constructed first } g.pywrap.Printf("def __str__(self):\n") g.pywrap.Indent() - g.pywrap.Printf("return self.String()\n") + g.genStringerCall() g.pywrap.Outdent() g.pywrap.Printf("\n") } @@ -345,7 +345,7 @@ handle=A Go-side object is always initialized with an explicit handle=arg } g.pywrap.Printf("def __str__(self):\n") g.pywrap.Indent() - g.pywrap.Printf("return self.String()\n") + g.genStringerCall() g.pywrap.Outdent() g.pywrap.Printf("\n") } diff --git a/bind/package.go b/bind/package.go index 2136abb..cf346c6 100644 --- a/bind/package.go +++ b/bind/package.go @@ -337,8 +337,15 @@ func (p *Package) process() error { funcs[name] = fv case *types.TypeName: - named := obj.Type().(*types.Named) - switch typ := named.Underlying().(type) { + typ := obj.Type() + if named, ok := typ.(*types.Named); ok { + typ = named.Underlying() + } else { + // we are dealing with a type alias to a type literal. + // this is a cursed feature used to do structural typing. + // just pass it as-is. + } + switch typ := typ.(type) { case *types.Struct: sv, err := newStruct(p, obj) if err != nil { diff --git a/bind/utils.go b/bind/utils.go index 3048445..5c13c30 100644 --- a/bind/utils.go +++ b/bind/utils.go @@ -198,21 +198,17 @@ else: fmt.Printf("no LibPy -- set to: %s\n", raw.LibPy) } - if strings.HasSuffix(raw.LibPy, ".a") { - raw.LibPy = raw.LibPy[:len(raw.LibPy)-len(".a")] - } - if strings.HasPrefix(raw.LibPy, "lib") { - raw.LibPy = raw.LibPy[len("lib"):] - } + raw.LibPy = strings.TrimSuffix(raw.LibPy, ".a") + raw.LibPy = strings.TrimPrefix(raw.LibPy, "lib") cfg.Version = raw.Version cfg.ExtSuffix = raw.ExtSuffix cfg.CFlags = strings.Join([]string{ - "-I" + raw.IncDir, + `"-I` + raw.IncDir + `"`, }, " ") cfg.LdFlags = strings.Join([]string{ - "-L" + raw.LibDir, - "-l" + raw.LibPy, + `"-L` + raw.LibDir + `"`, + `"-l` + raw.LibPy + `"`, raw.ShLibs, raw.SysLibs, }, " ") diff --git a/cmd_build.go b/cmd_build.go index 7b57a7e..6c63dff 100644 --- a/cmd_build.go +++ b/cmd_build.go @@ -231,10 +231,13 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error { } cflags := strings.Fields(strings.TrimSpace(pycfg.CFlags)) - cflags = append(cflags, "-fPIC", "-Ofast") + cflags = append(cflags, "-fPIC", "-O3", "-ffast-math") if include, exists := os.LookupEnv("GOPY_INCLUDE"); exists { cflags = append(cflags, "-I"+filepath.ToSlash(include)) } + if oldcflags, exists := os.LookupEnv("CGO_CFLAGS"); exists { + cflags = append(cflags, oldcflags) + } var ldflags []string if cfg.DynamicLinking { ldflags = strings.Fields(strings.TrimSpace(pycfg.LdDynamicFlags)) @@ -250,6 +253,9 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error { if libname, exists := os.LookupEnv("GOPY_PYLIB"); exists { ldflags = append(ldflags, "-l"+filepath.ToSlash(libname)) } + if oldldflags, exists := os.LookupEnv("CGO_LDFLAGS"); exists { + ldflags = append(ldflags, oldldflags) + } removeEmpty := func(src []string) []string { o := make([]string, 0, len(src)) diff --git a/cmd_pkg.go b/cmd_pkg.go index c0d6cba..0f58480 100644 --- a/cmd_pkg.go +++ b/cmd_pkg.go @@ -167,7 +167,7 @@ func buildPkgRecurse(odir, path, rootpath string, exmap map[string]struct{}, bui drs := Dirs(dir) for _, dr := range drs { _, ex := exmap[dr] - if ex || dr[0] == '.' || dr[0] == '_' { + if ex || dr[0] == '.' || dr[0] == '_' || dr == "internal" { continue } sp := filepath.Join(path, dr) diff --git a/main_test.go b/main_test.go index 38c03e3..e7d4292 100644 --- a/main_test.go +++ b/main_test.go @@ -23,6 +23,7 @@ var ( testBackends = map[string]string{} features = map[string][]string{ "_examples/hi": []string{"py3"}, + "_examples/gobytes": []string{"py3"}, "_examples/funcs": []string{"py3"}, "_examples/sliceptr": []string{"py3"}, "_examples/simple": []string{"py3"}, @@ -267,6 +268,25 @@ OK } +func TestBytes(t *testing.T) { + // t.Parallel() + path := "_examples/gobytes" + testPkg(t, pkg{ + path: path, + lang: features[path], + cmd: "build", + extras: nil, + want: []byte(`Python bytes: b'\x00\x01\x02\x03' +Go slice: go.Slice_byte len: 10 handle: 1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +gobytes.HashBytes from Go bytes: gobytes.Array_4_byte len: 4 handle: 2 [12, 13, 81, 81] +Python bytes to Go: go.Slice_byte len: 4 handle: 3 [0, 1, 2, 3] +Go bytes to Python: b'\x03\x04\x05' +OK +`), + }) + +} + func TestBindFuncs(t *testing.T) { // t.Parallel() path := "_examples/funcs" @@ -596,6 +616,7 @@ struct slice: slices.Slice_Ptr_slices_S len: 3 handle: 11 [slices.S{Name=S0, ha struct slice[0]: slices.S{Name=S0, handle=15} struct slice[1]: slices.S{Name=S1, handle=16} struct slice[2].Name: S2 +[][]bool working as expected OK `), }) diff --git a/main_unix.go b/main_unix.go index 1acf66f..bd84a69 100644 --- a/main_unix.go +++ b/main_unix.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (linux && !android) || dragonfly || openbsd -// +build linux,!android dragonfly openbsd +//go:build (linux && !android) || dragonfly || openbsd || freebsd +// +build linux,!android dragonfly openbsd freebsd package main diff --git a/version.go b/version.go index 26b7c0c..55a0f48 100644 --- a/version.go +++ b/version.go @@ -3,7 +3,7 @@ package main const ( - Version = "v0.4.8" - GitCommit = "034c7db" // the commit JUST BEFORE the release - VersionDate = "2023-12-12 20:15" // UTC + Version = "v0.4.10" + GitCommit = "b735a58" // the commit JUST BEFORE the release + VersionDate = "2024-05-03 22:57" // UTC )