diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/.travis.yml b/.travis.yml index 597008b..a13c9f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: go go: - - 1.2 + - 1.14.x + - 1.13.x before_install: - sudo apt-get update -qq diff --git a/README.md b/README.md index 11e7ac3..2e82335 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ go-python ========= -[![Build Status](https://drone.io/github.com/sbinet/go-python/status.png)](https://drone.io/github.com/sbinet/go-python/latest) +**`sbinet/go-python` only supports CPython2. CPython2 isn't supported anymore by [python.org](https://python.org). Thus, `sbinet/go-python` is now archived.** +**A possible alternative may be to use and contribute to [go-python/cpy3](https://github.com/go-python/cpy3) instead.** + +[![Build Status](https://travis-ci.org/sbinet/go-python.svg?branch=master)](https://travis-ci.org/sbinet/go-python) +[![Build status](https://ci.appveyor.com/api/projects/status/n0ujg8no487a89vo/branch/master?svg=true)](https://ci.appveyor.com/project/sbinet/go-python/branch/master) +[![GoDocs](https://godocs.io/github.com/sbinet/go-python?status.svg)](https://godocs.io/github.com/sbinet/go-python) Naive `go` bindings towards the C-API of CPython-2. @@ -47,9 +52,9 @@ If ``go get`` + ``pkg-config`` failed: Documentation ------------- -Is available on ``godoc``: +Is available on ``godocs``: - http://godoc.org/github.com/sbinet/go-python + https://godocs.io/github.com/sbinet/go-python Example: diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..ad332c1 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,29 @@ +build: off + +clone_folder: c:\gopath\src\github.com\sbinet\go-python + +cache: + - '%LocalAppData%\\go-build' + - '%LocalAppData%\\pip' + +branches: + only: + - master + +environment: + GOPATH: c:\gopath + GOPY_APPVEYOR_CI: '1' + GODEBUG: 'cgocheck=0' + GOTRACEBACK: 'crash' + PYTHONUNBUFFERED: "1" + CPYTHON2DIR: "C:\\Python27-x64" + PATH: '%GOPATH%\bin;%CPYTHON2DIR%;%CPYTHON2DIR%\\Scripts;C:\msys64\mingw64\bin;C:\msys64\usr\bin\;%PATH%' + +stack: go 1.13 + +build_script: + - "%CPYTHON2DIR%\\python --version" + - go get -v -t ./... + +test_script: + - go test ./... diff --git a/capi.go b/capi.go index 2d8d777..deec35f 100644 --- a/capi.go +++ b/capi.go @@ -16,11 +16,13 @@ func Py_BuildValue(format string, args ...interface{}) *PyObject { // ml_doc char * points to the contents of the docstring type PyMethodDef struct { Name string // name of the method - Meth func(self, args *PyObject) *PyObject + Meth PyCFunction Flags MethodDefFlags Doc string } +type PyCFunction C.PyCFunction + type MethodDefFlags int const ( diff --git a/cgoflags.go b/cgoflags_unix.go similarity index 81% rename from cgoflags.go rename to cgoflags_unix.go index 5f1f0fa..4a38570 100644 --- a/cgoflags.go +++ b/cgoflags_unix.go @@ -1,7 +1,7 @@ +// +build !windows + package python // #cgo pkg-config: python-2.7 // #include "go-python.h" import "C" - -// EOF diff --git a/cgoflags_windows.go b/cgoflags_windows.go new file mode 100644 index 0000000..cb02cf2 --- /dev/null +++ b/cgoflags_windows.go @@ -0,0 +1,12 @@ +// +build windows + +package python + +// #cgo 386 CFLAGS: -I C:/Python27/include +// #cgo amd64 CFLAGS: -I C:/Python27-x64/include +// #cgo amd64 CFLAGS: -D MS_WIN64 +// #cgo 386 LDFLAGS: -L C:/Python27/libs -lpython27 +// #cgo amd64 LDFLAGS: -L C:/Python27-x64/libs -lpython27 +// +// #include "go-python.h" +import "C" diff --git a/file.go b/file.go new file mode 100644 index 0000000..2d2c5b4 --- /dev/null +++ b/file.go @@ -0,0 +1,47 @@ +package python + +/* +#include "go-python.h" +#include + +PyObject* +_gopy_PyFile_FromFile(int fd, char *name, char *mode) { + FILE *f = fdopen(fd, mode); + PyObject *py = PyFile_FromFile(f, name, mode, NULL); + PyFile_SetBufSize(py, 0); + return py; +} + +*/ +import "C" + +import ( + "os" + "unsafe" +) + +// FromFile converts a Go file to Python file object. +// Calling close from Python will not close a file descriptor. +func FromFile(f *os.File, mode string) *PyObject { + cname := C.CString(f.Name()) + cmode := C.CString(mode) + p := C._gopy_PyFile_FromFile(C.int(f.Fd()), cname, cmode) + C.free(unsafe.Pointer(cname)) + C.free(unsafe.Pointer(cmode)) + return togo(p) +} + +// SetStdin sets a sys.stdin to a specified file descriptor. +func SetStdin(f *os.File) error { + return PySys_SetObject("stdin", FromFile(f, "r")) +} + +// SetStdout sets a sys.stdout to a specified file descriptor. +func SetStdout(f *os.File) error { + return PySys_SetObject("stdout", FromFile(f, "w")) +} + +// SetStderr sets a sys.stderr to a specified file descriptor. +func SetStderr(f *os.File) error { + return PySys_SetObject("stderr", FromFile(f, "w")) +} diff --git a/gen-cgoflags.go b/gen-cgoflags.go new file mode 100644 index 0000000..ce86c5c --- /dev/null +++ b/gen-cgoflags.go @@ -0,0 +1,141 @@ +// +build ignore + +// simple command to generate CGOFLAGS for a given python VM +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "runtime" + "strings" + + "github.com/pkg/errors" +) + +func main() { + vm := flag.String("vm", "python", "path to a python VM") + flag.Parse() + + if *vm == "" { + log.Fatalf("need a python VM") + } + + cfg, err := getPythonConfig(*vm) + if err != nil { + log.Fatalf("could not infer python configuration: %v", err) + } + + oname := "cgoflags_unix.go" + switch runtime.GOOS { + case "windows": + oname = "cgoflags_windows.go" + } + + err = ioutil.WriteFile(oname, []byte(fmt.Sprintf(tmpl, + cfg.cflags, + cfg.ldflags, + runtime.GOOS, + )), 0644) + if err != nil { + log.Fatalf("could not write %q: %v", oname, err) + } +} + +// getPythonConfig returns the needed python configuration for the given +// python VM (python, python2, python3, pypy, etc...) +func getPythonConfig(vm string) (pyconfig, error) { + code := `import sys +import distutils.sysconfig as ds +import json +print(ds.get_config_vars()) +print(ds.get_python_lib()) +print(json.dumps({ + "version": sys.version_info.major, + "prefix": ds.get_config_var("prefix"), + "incdir": ds.get_python_inc(), + "libdir": ds.get_config_var("LIBDIR"), + "libdest": ds.get_config_var("LIBDEST"), + "libpy": ds.get_config_var("LIBRARY"), + "shlibs": ds.get_config_var("SHLIBS"), + "syslibs": ds.get_config_var("SYSLIBS"), + "shlinks": ds.get_config_var("LINKFORSHARED"), + "DLLs": ds.get_config_var("DLLLIBRARY"), +})) +` + + var cfg pyconfig + bin, err := exec.LookPath(vm) + if err != nil { + return cfg, errors.Wrapf(err, "could not locate python vm %q", vm) + } + + buf := new(bytes.Buffer) + cmd := exec.Command(bin, "-c", code) + cmd.Stdin = os.Stdin + cmd.Stdout = buf + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return cfg, errors.Wrap(err, "could not run python-config script") + } + + log.Printf("distutils:\n%s", buf.String()) + + var raw struct { + Version int `json:"version"` + IncDir string `json:"incdir"` + LibDir string `json:"libdir"` + LibPy string `json:"libpy"` + ShLibs string `json:"shlibs"` + SysLibs string `json:"syslibs"` + } + err = json.NewDecoder(buf).Decode(&raw) + if err != nil { + return cfg, errors.Wrapf(err, "could not decode JSON script output") + } + + 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"):] + } + + cfg.version = raw.Version + cfg.cflags = strings.Join([]string{ + "-I " + raw.IncDir, + }, " ") + cfg.ldflags = strings.Join([]string{ + "-L " + raw.LibDir, + "-l " + raw.LibPy, + raw.ShLibs, + raw.SysLibs, + }, " ") + + return cfg, nil +} + +type pyconfig struct { + version int + cflags string + ldflags string +} + +const tmpl = `// Automatically generated. Do not edit. + +// +build %[3]s + +package python + +// #cgo %[3]s CFLAGS: %[1]s +// #cgo %[3]s LDFLAGS: %[2]s +// +// #include "go-python.h" +import "C" +` diff --git a/go-python.go b/go-python.go index 23394e6..75958fb 100644 --- a/go-python.go +++ b/go-python.go @@ -78,8 +78,5 @@ func pyfmt(v interface{}) (unsafe.Pointer, string) { } - panic(fmt.Errorf( - "python: unknown type (%#T)", - v, - )) + panic(fmt.Errorf("python: unknown type (%T)", v)) } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8dc3bb5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/sbinet/go-python + +go 1.13 diff --git a/heap.go b/heap.go index d3a00a1..d5f0c27 100644 --- a/heap.go +++ b/heap.go @@ -24,7 +24,7 @@ func cpyMethodDefs(name string, methods []PyMethodDef) *C.PyMethodDef { for i, meth := range methods { cmeth := C.PyMethodDef{ ml_name: C.CString(meth.Name), - ml_meth: (C.PyCFunction)(unsafe.Pointer(&meth.Meth)), + ml_meth: C.PyCFunction(meth.Meth), ml_flags: C.int(meth.Flags), ml_doc: C.CString(meth.Doc), } @@ -43,6 +43,7 @@ func Py_InitModule(name string, methods []PyMethodDef) (*PyObject, error) { obj := togo(C._gopy_InitModule(c_mname, cmeths)) if obj == nil { + PyErr_Print() return nil, errors.New("python: internal error; module creation failed.") } return obj, nil @@ -59,6 +60,7 @@ func Py_InitModule3(name string, methods []PyMethodDef, doc string) (*PyObject, obj := togo(C._gopy_InitModule3(cname, cmeths, cdoc)) if obj == nil { + PyErr_Print() return nil, errors.New("python: internal error; module creation failed.") } return obj, nil diff --git a/init.go b/init.go new file mode 100644 index 0000000..7f1b485 --- /dev/null +++ b/init.go @@ -0,0 +1,60 @@ +package python + +// #include "go-python.h" +// char *gopy_ProgName = NULL; +// char *gopy_PythonHome = NULL; +import "C" + +import ( + "unsafe" +) + +// Py_SetProgramName should be called before Py_Initialize() is called for +// the first time, if it is called at all. +// It tells the interpreter the value of the argv[0] argument to the main() +// function of the program. This is used by Py_GetPath() and some other +// functions below to find the Python run-time libraries relative to the +// interpreter executable. The default value is 'python'. The argument should +// point to a zero-terminated character string in static storage whose contents +// will not change for the duration of the program’s execution. +// No code in the Python interpreter will change the contents of this storage. +func Py_SetProgramName(name string) { + C.free(unsafe.Pointer(C.gopy_ProgName)) + C.gopy_ProgName = C.CString(name) + C.Py_SetProgramName(C.gopy_ProgName) +} + +// Py_GetProgramName returns the program name set with Py_SetProgramName(), +// or the default. +// The returned string points into static storage; the caller should not +// modify its value. +func Py_GetProgramName() string { + cname := C.Py_GetProgramName() + return C.GoString(cname) +} + +// PySys_SetArgv initializes the 'sys.argv' array in python. +func PySys_SetArgv(argv []string) { + argc := C.int(len(argv)) + cargs := make([]*C.char, len(argv)) + for idx, arg := range argv { + cargs[idx] = C.CString(arg) + defer C.free(unsafe.Pointer(cargs[idx])) + } + C.PySys_SetArgv(argc, &cargs[0]) +} + +// Set the default “home” directory, that is, the location of the standard Python libraries. See PYTHONHOME for the meaning of the argument string. +// +// The argument should point to a zero-terminated character string in static storage whose contents will not change for the duration of the program’s execution. No code in the Python interpreter will change the contents of this storage. +func Py_SetPythonHome(home string) { + C.free(unsafe.Pointer(C.gopy_PythonHome)) + C.gopy_PythonHome = C.CString(home) + C.Py_SetPythonHome(C.gopy_PythonHome) +} + +// Return the default “home”, that is, the value set by a previous call to Py_SetPythonHome(), or the value of the PYTHONHOME environment variable if it is set. +func Py_GetPythonHome() string { + home := C.Py_GetPythonHome() + return C.GoString(home) +} diff --git a/init_test.go b/init_test.go new file mode 100644 index 0000000..f1e73ac --- /dev/null +++ b/init_test.go @@ -0,0 +1,25 @@ +package python_test + +import ( + "testing" + + python "github.com/sbinet/go-python" +) + +func TestProgramName(t *testing.T) { + const want = "foo.exe" + python.Py_SetProgramName(want) + name := python.Py_GetProgramName() + if name != want { + t.Fatalf("got=%q. want=%q", name, want) + } +} + +func TestPythonHome(t *testing.T) { + const want = "/usr/lib/go-python" + python.Py_SetPythonHome(want) + got := python.Py_GetPythonHome() + if got != want { + t.Fatalf("got=%q. want=%q", got, want) + } +} diff --git a/none.go b/none.go index 914899e..427c4cf 100644 --- a/none.go +++ b/none.go @@ -6,6 +6,6 @@ import "C" // The Python None object, denoting lack of value. This object has no methods. // It needs to be treated just like any other object with respect to reference // counts. -var Py_None = togo(C._gopy_pynone()) +var Py_None = &PyObject{ptr: C._gopy_pynone()} // EOF diff --git a/numeric.go b/numeric.go index 31a2c2a..97125ad 100644 --- a/numeric.go +++ b/numeric.go @@ -332,13 +332,13 @@ func PyBool_Check(self *PyObject) bool { // The Python False object. This object has no methods. // It needs to be treated just like any other object with respect to // reference counts. -var Py_False = togo(C._gopy_pyfalse()) +var Py_False = &PyObject{ptr: C._gopy_pyfalse()} // PyObject* Py_True // The Python True object. This object has no methods. // It needs to be treated just like any other object with respect to // reference counts. -var Py_True = togo(C._gopy_pytrue()) +var Py_True = &PyObject{ptr: C._gopy_pytrue()} /* Py_RETURN_FALSE diff --git a/object.go b/object.go index 7be44fa..2e78c2d 100644 --- a/object.go +++ b/object.go @@ -15,19 +15,37 @@ type PyObject struct { ptr *C.PyObject } +// String returns a string representation of the PyObject +func (self *PyObject) String() string { + o := self.Str() + defer o.DecRef() + return PyString_AsString(o) +} + func (self *PyObject) topy() *C.PyObject { return self.ptr } func topy(self *PyObject) *C.PyObject { + if self == nil { + return nil + } return self.ptr } func togo(obj *C.PyObject) *PyObject { - if obj == nil { + switch obj { + case nil: return nil + case Py_None.ptr: + return Py_None + case Py_True.ptr: + return Py_True + case Py_False.ptr: + return Py_False + default: + return &PyObject{ptr: obj} } - return &PyObject{ptr: obj} } // PyObject_FromVoidPtr converts a PyObject from an unsafe.Pointer diff --git a/python_test.go b/python_test.go index 3b7e835..564b64e 100644 --- a/python_test.go +++ b/python_test.go @@ -112,7 +112,7 @@ func TestErrFetch(t *testing.T) { t.Parallel() testPkg(t, pkg{ path: "tests/errfetch", - want: []byte("exc=&{}\nval=&{}\ntb=&{}\n"), + want: []byte("exc=\nval=\ntb=\n"), }) } @@ -130,3 +130,22 @@ ival=1666 `), }) } + +func TestIssue61(t *testing.T) { + t.Parallel() + testPkg(t, pkg{ + path: "tests/issue61", + want: []byte(`['i want this gone'] +[] +`), + }) +} + +func TestCheckNone(t *testing.T) { + t.Parallel() + testPkg(t, pkg{ + path: "tests/none-check", + want: []byte(`type=, str=None, eq_none=true +`), + }) +} diff --git a/sequence.go b/sequence.go index be66482..54b6a91 100644 --- a/sequence.go +++ b/sequence.go @@ -323,8 +323,9 @@ func PyList_Append(self, item *PyObject) error { // // Changed in version 2.5: This function used an int for low and high. This might require changes in your code for properly supporting 64-bit systems. func PyList_SetSlice(self *PyObject, low, high int, itemlist *PyObject) error { - err := C.PyList_SetSlice(topy(self), C.Py_ssize_t(low), C.Py_ssize_t(high), - topy(itemlist)) + err := C.PyList_SetSlice( + topy(self), C.Py_ssize_t(low), C.Py_ssize_t(high), topy(itemlist), + ) return int2err(err) } diff --git a/tests/issue61/main.go b/tests/issue61/main.go new file mode 100644 index 0000000..ae312b3 --- /dev/null +++ b/tests/issue61/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "log" + + "github.com/sbinet/go-python" +) + +func init() { + err := python.Initialize() + if err != nil { + log.Panic(err) + } +} + +func main() { + + origList := python.PyList_New(0) + python.PyList_Append(origList, python.PyString_FromString("i want this gone")) + fmt.Println(python.PyString_AsString(origList.Str())) + + python.PyList_SetSlice(origList, 0, 1, nil) + fmt.Println(python.PyString_AsString(origList.Str())) +} diff --git a/tests/none-check/get_none.py b/tests/none-check/get_none.py new file mode 100644 index 0000000..7d27c14 --- /dev/null +++ b/tests/none-check/get_none.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python2 + +def get_none(): + return None + diff --git a/tests/none-check/main.go b/tests/none-check/main.go new file mode 100644 index 0000000..990fec4 --- /dev/null +++ b/tests/none-check/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "log" + + "github.com/sbinet/go-python" +) + +func init() { + err := python.Initialize() + if err != nil { + log.Panic(err) + } +} + +func main() { + module := python.PyImport_ImportModule("get_none") + if module == nil { + log.Fatal("could not import 'get_none' module") + } + get_none := module.GetAttrString("get_none") + if get_none == nil { + log.Fatal("could not import 'get_none' function") + } + none := get_none.CallFunction() + fmt.Printf("type=%s, str=%s, eq_none=%t\n", + python.PyString_AsString(none.Type().Str()), + python.PyString_AsString(none.Str()), + none == python.Py_None, + ) +} diff --git a/utilities.go b/utilities.go index ec5d543..8bdaed2 100644 --- a/utilities.go +++ b/utilities.go @@ -468,16 +468,3 @@ func PyEval_GetFuncDesc(fct *PyObject) string { c_name := C.PyEval_GetFuncDesc(topy(fct)) return C.GoString(c_name) } - -// PySys_SetArgv initializes the 'sys.argv' array in python. -func PySys_SetArgv(argv []string) { - argc := C.int(len(argv)) - cargs := make([]*C.char, len(argv)) - for idx, arg := range argv { - cargs[idx] = C.CString(arg) - defer C.free(unsafe.Pointer(cargs[idx])) - } - C.PySys_SetArgv(argc, &cargs[0]) -} - -// EOF pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy