diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1b0b9e0..a639f411 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: env: TAGS: "-tags=ci" - COVERAGE: "-coverpkg=github.com/go-python/gopy/..." + COVERAGE: "-coverpkg=github.com/rudderlabs/gopy/..." # Init() in main_test will make sure all backends are available if # GOPY_TRAVIS_CI is set GOPY_TRAVIS_CI: 1 diff --git a/SUPPORT_MATRIX.md b/SUPPORT_MATRIX.md index 5e473e01..bd5e1ec5 100644 --- a/SUPPORT_MATRIX.md +++ b/SUPPORT_MATRIX.md @@ -17,6 +17,7 @@ _examples/hi | no | yes _examples/iface | no | yes _examples/lot | yes | yes _examples/maps | yes | yes +_examples/multireturn | no | yes _examples/named | yes | yes _examples/osfile | yes | yes _examples/pkgconflict | yes | yes @@ -29,4 +30,5 @@ _examples/sliceptr | yes | yes _examples/slices | yes | yes _examples/structs | yes | yes _examples/unicode | no | yes +_examples/variadic | no | yes _examples/vars | yes | yes diff --git a/_examples/cpkg/run.go b/_examples/cpkg/run.go index 85bd647d..5fc61ac2 100644 --- a/_examples/cpkg/run.go +++ b/_examples/cpkg/run.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build ignore // +build ignore package main @@ -9,7 +10,7 @@ package main import ( "fmt" - "github.com/go-python/gopy/_examples/cpkg" + "github.com/rudderlabs/gopy/_examples/cpkg" ) func main() { diff --git a/_examples/funcs/funcs.go b/_examples/funcs/funcs.go index baac0111..0c0fc269 100644 --- a/_examples/funcs/funcs.go +++ b/_examples/funcs/funcs.go @@ -7,7 +7,7 @@ package funcs import ( "fmt" - "github.com/go-python/gopy/_examples/cpkg" + "github.com/rudderlabs/gopy/_examples/cpkg" ) type FunStruct struct { diff --git a/_examples/gopyerrors/gopyerrors.go b/_examples/gopyerrors/gopyerrors.go deleted file mode 100644 index fbec63d5..00000000 --- a/_examples/gopyerrors/gopyerrors.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 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 gopyerrors contains functions that generate error -// messages from gopy itself. -package gopyerrors - -func NotErrorMany() (int, int) { - return 0, 0 -} - -func TooMany() (int, int, string) { - return 0, 1, "Hi" -} - -func OK() (int, error) { - return 0, nil -} - -type Struct struct{} - -func (s *Struct) NotErrorMany() (int, string) { - return 0, "Hi" -} - -func (s *Struct) TooMany() (int, int, string) { - return 0, 1, "Hi" -} diff --git a/_examples/gopyerrors/test.py b/_examples/gopyerrors/test.py deleted file mode 100644 index eb6e169a..00000000 --- a/_examples/gopyerrors/test.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020 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. - -# py2/py3 compat -from __future__ import print_function - -import gopyerrors - -# This is empty, its only purpose is to have a test that catches -# errors generated by the gopy itself. - -print("OK") diff --git a/_examples/hi/hi.go b/_examples/hi/hi.go index 0d62e1d0..d968928d 100644 --- a/_examples/hi/hi.go +++ b/_examples/hi/hi.go @@ -8,8 +8,8 @@ package hi import ( "fmt" - "github.com/go-python/gopy/_examples/cpkg" - "github.com/go-python/gopy/_examples/structs" + "github.com/rudderlabs/gopy/_examples/cpkg" + "github.com/rudderlabs/gopy/_examples/structs" ) const ( diff --git a/_examples/iface/iface.go b/_examples/iface/iface.go index 56d98633..6c8d3765 100644 --- a/_examples/iface/iface.go +++ b/_examples/iface/iface.go @@ -6,7 +6,7 @@ package iface import ( - "github.com/go-python/gopy/_examples/cpkg" + "github.com/rudderlabs/gopy/_examples/cpkg" ) // Iface has a single F() method diff --git a/_examples/multireturn/multireturn.go b/_examples/multireturn/multireturn.go new file mode 100644 index 00000000..55a6e433 --- /dev/null +++ b/_examples/multireturn/multireturn.go @@ -0,0 +1,111 @@ +package multireturn + +import ( + "fmt" +) + +/////////////// No Return ////////////// +func NoReturnFunc() { +} + +/////////////// Single WithoutError Return ////////////// +func SingleWithoutErrorFunc() int { + return 100 +} + +/////////////// Single Str WithoutError Return ////////////// +func SingleStrWithoutErrorFunc(vargs ...int) string { + return "150" +} + +/////////////// Single WithError Return ////////////// +func SingleWithErrorFunc(throwError bool) error { + if throwError { + return fmt.Errorf("Error") + } else { + return nil + } +} + +/////////////// Double WithoutError Return ////////////// +func DoubleWithoutErrorFunc1() (int, int) { + return 200, 300 +} +func DoubleWithoutErrorFunc2() (string, string) { + return "200", "300" +} + +/////////////// Double WithError Return ////////////// +func DoubleWithErrorFunc(throwError bool) (string, error) { + if throwError { + return "400", fmt.Errorf("Error") + } else { + return "500", nil + } +} + +/////////////// Triple Returns Without Error ////////////// +func TripleWithoutErrorFunc1(vargs ...int) (int, int, int) { + return 600, 700, 800 +} +func TripleWithoutErrorFunc2(vargs ...int) (int, string, int) { + return 600, "700", 800 +} + +/////////////// Triple Returns With Error ////////////// +func TripleWithErrorFunc(throwError bool) (int, int, error) { + if throwError { + return 900, 1000, fmt.Errorf("Error") + } else { + return 1100, 1200, nil + } +} + +/////////////// Triple Struct Returns With Error ////////////// +type IntStrUct struct { + P int +} + +func NewIntStrUct(n int) IntStrUct { + return IntStrUct{ + P: n, + } +} + +func TripleWithStructWithErrorFunc(throwError bool) (*IntStrUct, IntStrUct, error) { + s1300 := IntStrUct{P: 1300} + s1400 := IntStrUct{P: 1400} + s1500 := IntStrUct{P: 1500} + s1600 := IntStrUct{P: 1600} + if throwError { + return &s1300, s1400, fmt.Errorf("Error") + } else { + return &s1500, s1600, nil + } +} + +/////////////// Triple Interface Returns Without Error ////////////// +type IntInterFace interface { + Number() int +} + +func (is *IntStrUct) Number() int { + return is.P +} + +func TripleWithInterfaceWithoutErrorFunc() (IntInterFace, IntStrUct, *IntStrUct) { + i1700 := IntStrUct{P: 1700} + s1800 := IntStrUct{P: 1800} + s1900 := IntStrUct{P: 1900} + + return &i1700, s1800, &s1900 +} + +//// Function returning function ///// +type FunctionType func(input int) int + +func FunctionReturningFunction() FunctionType { + return func(input int) int { + return input + } +} diff --git a/_examples/multireturn/test.py b/_examples/multireturn/test.py new file mode 100644 index 00000000..e885a868 --- /dev/null +++ b/_examples/multireturn/test.py @@ -0,0 +1,77 @@ +# Copyright 2018 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. +import multireturn, go + +############### No Return ############## +noResult = multireturn.NoReturnFunc() +print("No Return %r" % noResult) + +############### Single WithoutError Return ############## +oneResult = multireturn.SingleWithoutErrorFunc() +print("Single WithoutError Return %r" % oneResult) + +############### Single Str WithoutError Return ############## +oneStrResult = multireturn.SingleStrWithoutErrorFunc() +print("Single Str WithoutError Return %r" % oneStrResult) + +############### Single WithError Return ############## +errorFalseResult1 = multireturn.SingleWithErrorFunc(False) +print("Single WithError(False) Return %r" % errorFalseResult1) + +try: + errorTrueResult1 = multireturn.SingleWithErrorFunc(True) + print("Failed to throw an exception") +except RuntimeError as ex: + print("Single WithError(True). Exception: %r" % ex) + +############### Double WithoutError Return ############## +twoResults = multireturn.DoubleWithoutErrorFunc1() +print("Double WithoutError(Without String) Return (%r, %r)" % twoResults) + +twoResults = multireturn.DoubleWithoutErrorFunc2() +print("Double WithoutError(With String) Return (%r, %r)" % twoResults) + +############### Double WithError Return ############## +try: + value400 = multireturn.DoubleWithErrorFunc(True) + print("Failed to throw an exception. Return (%r, %r)." % value400) +except RuntimeError as ex: + print("Double WithError(True). Exception: %r" % ex) + +value500 = multireturn.DoubleWithErrorFunc(False) +print("Double WithError(False) Return %r" % value500) + +############### Triple Without Error Return ############## +threeResults = multireturn.TripleWithoutErrorFunc1() +print("Triple WithoutError(Without String) Return (%r, %r, %r)" % threeResults) + +threeResults = multireturn.TripleWithoutErrorFunc2() +print("Triple WithoutError(With String) Return (%r, %r, %r)" % threeResults) + +############### Triple With Error Return ############## +try: + (value900, value1000) = multireturn.TripleWithErrorFunc(True) + print("Triple WithError(True) Return (%r, %r, %r)" % (value900, value1000)) +except RuntimeError as ex: + print("Triple WithError(True) Exception: %r" % ex) + +(value1100, value1200) = multireturn.TripleWithErrorFunc(False) +print("Triple WithError(False) Return (%r, %r)" % (value1100, value1200)) + +############### Triple Struct Return With Error ############## +try: + (ptr1300, struct1400) = multireturn.TripleWithStructWithErrorFunc(True) + print("Triple WithError(True) Return (%r, %r)" % (ptr1300.P, struct1400.P)) +except RuntimeError as ex: + print("Triple WithError(True) Exception: %r" % ex) + +(value1500, value1600) = multireturn.TripleWithStructWithErrorFunc(False) +print("Triple WithError(False) Return (%r, %r)" % (value1500.P, value1600.P)) + +############### Triple Interface Return Without Error ############## +(interface1700, struct1800, ptr1900) = multireturn.TripleWithInterfaceWithoutErrorFunc() +print("Triple WithoutError() Return (%r, %r, %r)" % (interface1700.Number(), struct1800.P, ptr1900.P)) + +############## Function Returning Functions ignored ############## +assert("FunctionReturningFunction" not in dir(multireturn)) diff --git a/_examples/variadic/variadic.go b/_examples/variadic/variadic.go index dce6447d..1766d6b7 100644 --- a/_examples/variadic/variadic.go +++ b/_examples/variadic/variadic.go @@ -1,7 +1,7 @@ package variadic /////////////// Non Variadic ////////////// -func NonVariFunc(arg1 int, arg2 []int, arg3 int) int{ +func NonVariFunc(arg1 int, arg2 []int, arg3 int) int { total := arg1 for _, num := range arg2 { total += num @@ -12,7 +12,7 @@ func NonVariFunc(arg1 int, arg2 []int, arg3 int) int{ } /////////////// Variadic Over Int ////////////// -func VariFunc(vargs ...int) int{ +func VariFunc(vargs ...int) int { total := 0 for _, num := range vargs { total += num @@ -26,15 +26,15 @@ type IntStrUct struct { } func NewIntStrUct(n int) IntStrUct { - return IntStrUct { - p:n, + return IntStrUct{ + p: n, } -} +} -func VariStructFunc(vargs ...IntStrUct) int{ +func VariStructFunc(vargs ...IntStrUct) int { total := 0 for _, inst := range vargs { - total += inst.p + total += inst.p } return total } @@ -48,7 +48,7 @@ func (is *IntStrUct) Number() int { return is.p } -func VariInterFaceFunc(vargs ...IntInterFace) int{ +func VariInterFaceFunc(vargs ...IntInterFace) int { total := 0 for _, inst := range vargs { total += inst.Number() diff --git a/_examples/wrapper/pywrapper/wrapper_code.go b/_examples/wrapper/pywrapper/wrapper_code.go index 1ded41b0..2b928ef3 100644 --- a/_examples/wrapper/pywrapper/wrapper_code.go +++ b/_examples/wrapper/pywrapper/wrapper_code.go @@ -7,7 +7,7 @@ package pywrapper import ( "fmt" - "github.com/go-python/gopy/_examples/wrapper" + "github.com/rudderlabs/gopy/_examples/wrapper" ) type WrapperStruct struct { diff --git a/bind/bind.go b/bind/bind.go index a3b7a853..32b8690b 100644 --- a/bind/bind.go +++ b/bind/bind.go @@ -16,18 +16,34 @@ import ( type BindCfg struct { // output directory for bindings OutputDir string + // name of output package (otherwise name of first package is used) Name string + // code string to run in the go main() function in the cgo library Main string + // the full command args as a string, without path to exe Cmd string + // path to python interpreter VM string + // package prefix used when generating python import statements PkgPrefix string + // rename Go exported symbols to python PEP snake_case RenameCase bool + + // If set, python exceptions are not thrown. + NoPyExceptions bool + + // Path to Go module, which is to be used to translate Go errors to Python exceptions. + ModPathGoErr2PyEx string + + // If set, when a Go function returns a (value, err), python returns (value, ) tuple. + // By default, we return just value. + UsePyTuple4VE bool } // ErrorList is a list of errors diff --git a/bind/gen.go b/bind/gen.go index d8c83930..c151748d 100644 --- a/bind/gen.go +++ b/bind/gen.go @@ -38,7 +38,8 @@ var WindowsOS = false // for all preambles: 1 = name of package (outname), 2 = cmdstr -// 3 = libcfg, 4 = GoHandle, 5 = CGoHandle, 6 = all imports, 7 = mainstr, 8 = exe pre C, 9 = exe pre go +// 3 = libcfg, 4 = GoHandle, 5 = CGoHandle, 6 = all imports, 7 = goerr2pyex Package path, 8 = mainstr, +// 9 = exe pre C, 10 = exe pre go const ( goPreamble = `/* cgo stubs for package %[1]s. @@ -85,17 +86,32 @@ static inline void gopy_err_handle() { PyErr_Print(); } } -%[8]s +static PyObject* Py_BuildValue1(char *format, void* arg0) +{ + PyObject *retval = Py_BuildValue(format, arg0); + free(format); + return retval; +} +static PyObject* Py_BuildValue2(char *format, long long arg0) +{ + PyObject *retval = Py_BuildValue(format, arg0); + free(format); + return retval; +} + +%[9]s */ import "C" import ( - "github.com/go-python/gopy/gopyh" // handler + "github.com/rudderlabs/gopy/gopyh" // handler + "%[7]s" // Error Translator + %[6]s ) // main doesn't do anything in lib / pkg mode, but is essential for exe mode func main() { - %[7]s + %[8]s } // initialization functions -- can be called from python after library is loaded @@ -104,7 +120,7 @@ func main() { //export GoPyInit func GoPyInit() { - %[7]s + %[8]s } // type for the handle -- int64 for speed (can switch to string) @@ -164,7 +180,7 @@ func complex128PyToGo(o *C.PyObject) complex128 { return complex(float64(v.real), float64(v.imag)) } -%[9]s +%[10]s ` goExePreambleC = ` @@ -229,19 +245,22 @@ class CheckedFunction(Function): failure_cleanup = self._failure_cleanup or None self.before_call.write_error_check(check, failure_cleanup) -def add_checked_function(mod, name, retval, params, failure_expression='', *a, **kw): - fn = CheckedFunction(name, retval, params, *a, **kw) - fn.set_failure_expression(failure_expression) - mod._add_function_obj(fn) - return fn - -def add_checked_string_function(mod, name, retval, params, failure_expression='', *a, **kw): - fn = CheckedFunction(name, retval, params, *a, **kw) - fn.set_failure_cleanup('if (retval != NULL) free(retval);') - fn.after_call.add_cleanup_code('free(retval);') - fn.set_failure_expression(failure_expression) - mod._add_function_obj(fn) - return fn +def add_checked_function_generator(pyTupleBuilt, retvals_to_free): + if not pyTupleBuilt: + if retvals_to_free: + assert(len(retvals_to_free) == 1) + assert(retvals_to_free[0] == 0) + def rv_format(format_str, rv): + return format_str.format("PyTuple_GetItem(retval, {0})".format(rv)) + def add_checked_function(mod, name, retval, params, failure_expression='', *a, **kw): + fn = CheckedFunction(name, retval, params, *a, **kw) + #TODO: Figure out how to free allocated variables. Stop leaking memory. + #fn.set_failure_cleanup('\n'.join([rv_format('if ({0} != NULL) free({0});', rv) for rv in retvals_to_free])) + #fn.after_call.add_cleanup_code('\n'.join([rv_format('free({0});', rv) for rv in retvals_to_free])) + fn.set_failure_expression(failure_expression) + mod._add_function_obj(fn) + return fn + return add_checked_function mod = Module('_%[1]s') mod.add_include('"%[1]s_go.h"') @@ -372,6 +391,10 @@ build: # generated %[1]s.py python wrapper imports this c-code package %[9]s $(GCC) %[1]s.c %[6]s %[1]s_go$(LIBEXT) -o _%[1]s$(LIBEXT) $(CFLAGS) $(LDFLAGS) -fPIC --shared -w + +halfbuild: + $(GOBUILD) -buildmode=c-shared -o %[1]s_go$(LIBEXT) %[1]s.go + $(GCC) %[1]s.c %[6]s %[1]s_go$(LIBEXT) -o _%[1]s$(LIBEXT) $(CFLAGS) $(LDFLAGS) -fPIC --shared -w ` @@ -430,6 +453,9 @@ var NoWarn = false // NoMake turns off generation of Makefiles var NoMake = false +// NoPyExceptions turns off generation of Python Exceptions +var NoPyExceptions = false + // GenPyBind generates a .go file, build.py file to enable pybindgen to create python bindings, // and wrapper .py file(s) that are loaded as the interface to the package with shadow // python-side classes @@ -603,7 +629,7 @@ func (g *pyGen) genGoPreamble() { exeprego = goExePreambleGo } g.gofile.Printf(goPreamble, g.cfg.Name, g.cfg.Cmd, libcfg, GoHandle, CGoHandle, - pkgimport, g.cfg.Main, exeprec, exeprego) + pkgimport, g.cfg.ModPathGoErr2PyEx, g.cfg.Main, exeprec, exeprego) g.gofile.Printf("\n// --- generated code for package: %[1]s below: ---\n\n", g.cfg.Name) } diff --git a/bind/gen_func.go b/bind/gen_func.go index 991165f9..43f267fd 100644 --- a/bind/gen_func.go +++ b/bind/gen_func.go @@ -7,9 +7,22 @@ package bind import ( "fmt" "go/types" + "log" + "strconv" "strings" ) +func buildPyTuple(fsym *Func) bool { + npyres := len(fsym.sig.Results()) + if fsym.haserr { + if !NoPyExceptions { + npyres -= 1 + } + } + + return (npyres > 1) +} + func (g *pyGen) recurse(gotype types.Type, prefix, name string) { switch t := gotype.(type) { case *types.Basic: @@ -58,13 +71,11 @@ func (g *pyGen) genFuncSig(sym *symbol, fsym *Func) bool { res := sig.Results() nargs := 0 nres := len(res) - - // note: this is enforced in creation of Func, in newFuncFrom - if nres > 2 { - return false - } - if nres == 2 && !fsym.err { - return false + npyres := nres + if fsym.haserr { + if !NoPyExceptions { + npyres -= 1 + } } var ( @@ -100,7 +111,7 @@ func (g *pyGen) genFuncSig(sym *symbol, fsym *Func) bool { } } - if i!=nargs-1 || !fsym.isVariadic { + if i != nargs-1 || !fsym.isVariadic { wpArgs = append(wpArgs, anm) } } @@ -121,18 +132,24 @@ func (g *pyGen) genFuncSig(sym *symbol, fsym *Func) bool { // a function that adds function calls with exception checking. // But given specific return types, we may want to add more // behavior to the wrapped function code gen. - addFuncName := "add_checked_function" - if len(res) > 0 { - ret := res[0] - switch t := ret.GoType().(type) { - case *types.Basic: - // string return types need special memory leak patches - // to free the allocated char* - if t.Kind() == types.String { - addFuncName = "add_checked_string_function" + retvalsToFree := make([]string, 0, npyres) + if npyres > 0 { + for i := 0; i < npyres; i++ { + switch t := res[i].GoType().(type) { + case *types.Basic: + // string return types need special memory leak patches + // to free the allocated char* + if t.Kind() == types.String { + retvalsToFree = append(retvalsToFree, strconv.Itoa(i)) + } } } } + pyTupleBuilt := "True" + if !buildPyTuple(fsym) { + pyTupleBuilt = "False" + } + addFuncName := "add_checked_function_generator(" + pyTupleBuilt + ", [" + strings.Join(retvalsToFree, ", ") + "])" switch { case isMethod: @@ -154,36 +171,47 @@ func (g *pyGen) genFuncSig(sym *symbol, fsym *Func) bool { } goRet := "" - nres = len(res) - if nres > 0 { - ret := res[0] - sret := current.symtype(ret.GoType()) - if sret == nil { - panic(fmt.Errorf( - "gopy: could not find symbol for %q", - ret.Name(), - )) + if npyres == 0 { + g.pybuild.Printf("None") + } else if buildPyTuple(fsym) { + // We are returning PyTuple*. Setup pybindgen accordingly. + g.pybuild.Printf("retval('PyObject*', caller_owns_return=True)") + + // On Go side, return *C.PyObject. + goRet = "unsafe.Pointer" + } else { + ownership := "" + pyrets := make([]string, npyres, npyres) + gorets := make([]string, npyres, npyres) + for i := 0; i < npyres; i++ { + sret := current.symtype(res[i].GoType()) + if sret == nil { + panic(fmt.Errorf( + "gopy: could not find symbol for %q", + res[i].Name(), + )) + } + gorets[i] = sret.cgoname + pyrets[i] = "'" + sret.cpyname + "'" + if sret.cpyname == "PyObject*" { + ownership = ", caller_owns_return=True" + } } - if sret.cpyname == "PyObject*" { - g.pybuild.Printf("retval('%s', caller_owns_return=True)", sret.cpyname) - } else { - g.pybuild.Printf("retval('%s')", sret.cpyname) + g.pybuild.Printf("retval(%s%s)", strings.Join(pyrets, ", "), ownership) + + goRet = strings.Join(gorets, ", ") + if npyres > 1 { + goRet = "(" + goRet + ")" } - goRet = fmt.Sprintf("%s", sret.cgoname) - } else { - g.pybuild.Printf("None") } if len(goArgs) > 0 { - gstr := strings.Join(goArgs, ", ") - g.gofile.Printf("%v) %v", gstr, goRet) + g.gofile.Printf("%v) %v", strings.Join(goArgs, ", "), goRet) - pstr := strings.Join(pyArgs, ", ") - g.pybuild.Printf(", [%v])\n", pstr) + g.pybuild.Printf(", [%v])\n", strings.Join(pyArgs, ", ")) - wstr := strings.Join(wpArgs, ", ") - g.pywrap.Printf("%v)", wstr) + g.pywrap.Printf("%v)", strings.Join(wpArgs, ", ")) } else { g.gofile.Printf(") %v", goRet) @@ -216,6 +244,74 @@ func isIfaceHandle(gdoc string) (bool, string) { return false, gdoc } +func isPointer(pyfmt string) bool { + if pyfmt == "s" { + return true + } + return false +} + +func (g *pyGen) generateReturn(buildPyTuple bool, npyres int, results []*Var, retvals *[]string) { + g.gofile.Printf("\n") + valueCalls := make([]string, npyres, npyres) + for i := 0; i < npyres; i++ { + result := results[i] + sret := current.symtype(result.GoType()) + if sret == nil { + panic(fmt.Errorf( + "gopy: could not find symbol for %q", + result.Name(), + )) + } + formatStr := sret.pyfmt + if sret.pyfmt == "" { + formatStr = "?" + } + + retval := "" + if retvals != nil { + retval = (*retvals)[i] + } else if result.sym.zval != "" { + retval = result.sym.zval + } else { + fmt.Printf("gopy: programmer error: empty zval zero value in symbol: %v\n", result.sym) + } + + if result.sym.go2py != "" { + retval = result.sym.go2py + "(" + retval + ")" + result.sym.go2pyParenEx + } + + if buildPyTuple { + buildValueFunc := "C.Py_BuildValue1" + typeCast := "unsafe.Pointer" + if !isPointer(formatStr) { + buildValueFunc = "C.Py_BuildValue2" + typeCast = "C.longlong" + formatStr = "L" + } + valueCalls[i] = fmt.Sprintf("%s(C.CString(\"%s\"), %s(%s))", + buildValueFunc, + formatStr, + typeCast, + retval) + } else { + valueCalls[i] = retval + } + } + + if npyres == 0 { + g.gofile.Printf("return\n") + } else if buildPyTuple { + g.gofile.Printf("retTuple := C.PyTuple_New(%d);\n", npyres) + for i := 0; i < npyres; i++ { + g.gofile.Printf("C.PyTuple_SetItem(retTuple, %d, %s);\n", i, valueCalls[i]) + } + g.gofile.Printf("return unsafe.Pointer(retTuple);") + } else { + g.gofile.Printf("return %s\n", strings.Join(valueCalls, ", ")) + } +} + func (g *pyGen) genFuncBody(sym *symbol, fsym *Func) { isMethod := (sym != nil) isIface := false @@ -230,22 +326,23 @@ func (g *pyGen) genFuncBody(sym *symbol, fsym *Func) { pkgname := g.cfg.Name - _, gdoc, _ := extractPythonName(fsym.GoName(), fsym.Doc()) - ifchandle, gdoc := isIfaceHandle(gdoc) - sig := fsym.Signature() res := sig.Results() args := sig.Params() nres := len(res) - - rvIsErr := false // set to true if the main return is an error - if nres == 1 { - ret := res[0] - if isErrorType(ret.GoType()) { - rvIsErr = true + npyres := nres + rvHasErr := false // set to true if the main return is an error + if fsym.haserr { + if NoPyExceptions { + rvHasErr = true + } else { + npyres -= 1 } } + _, gdoc, _ := extractPythonName(fsym.GoName(), fsym.Doc()) + ifchandle, gdoc := isIfaceHandle(gdoc) + g.pywrap.Printf(":\n") g.pywrap.Indent() g.pywrap.Printf(`"""%s"""`, gdoc) @@ -260,28 +357,17 @@ func (g *pyGen) genFuncBody(sym *symbol, fsym *Func) { } } } + if isMethod { g.gofile.Printf( `vifc, __err := gopyh.VarFromHandleTry((gopyh.CGoHandle)(_handle), "%s") if __err != nil { `, symNm) g.gofile.Indent() - if nres > 0 { - ret := res[0] - if ret.sym.zval == "" { - fmt.Printf("gopy: programmer error: empty zval zero value in symbol: %v\n", ret.sym) - } - if ret.sym.go2py != "" { - g.gofile.Printf("return %s(%s)%s\n", ret.sym.go2py, ret.sym.zval, ret.sym.go2pyParenEx) - } else { - g.gofile.Printf("return %s\n", ret.sym.zval) - } - } else { - g.gofile.Printf("return\n") - } + g.generateReturn(buildPyTuple(fsym), npyres, res, nil) g.gofile.Outdent() g.gofile.Printf("}\n") - } else if rvIsErr { + } else if rvHasErr { g.gofile.Printf("var __err error\n") } @@ -303,19 +389,19 @@ if __err != nil { default: na = anm } - if i == len(args) - 1 && fsym.isVariadic { + if i == len(args)-1 && fsym.isVariadic { na = na + "..." } callArgs = append(callArgs, na) switch { case arg.sym.goname == "interface{}": if ifchandle { - wrapArgs = append(wrapArgs, fmt.Sprintf("%s.handle", anm)) + wrapArgs = append(wrapArgs, fmt.Sprintf("(-1 if %s == None else %s.handle)", anm, anm)) } else { wrapArgs = append(wrapArgs, anm) } case arg.sym.hasHandle(): - wrapArgs = append(wrapArgs, fmt.Sprintf("%s.handle", anm)) + wrapArgs = append(wrapArgs, fmt.Sprintf("(-1 if %s == None else %s.handle)", anm, anm)) default: wrapArgs = append(wrapArgs, anm) } @@ -335,120 +421,131 @@ if __err != nil { if isMethod { mnm = sym.id + "_" + fsym.GoName() } - rvHasHandle := false - if nres > 0 { - ret := res[0] - if !rvIsErr && ret.sym.hasHandle() { - rvHasHandle = true - cvnm := ret.sym.pyPkgId(g.pkg.pkg) - g.pywrap.Printf("return %s(handle=_%s.%s(", cvnm, pkgname, mnm) - } else { - g.pywrap.Printf("return _%s.%s(", pkgname, mnm) - } - } else { - g.pywrap.Printf("_%s.%s(", pkgname, mnm) - } - hasRetCvt := false - hasAddrOfTmp := false - if nres > 0 { - ret := res[0] - switch { - case rvIsErr: - g.gofile.Printf("__err = ") - case nres == 2: - g.gofile.Printf("cret, __err := ") - case ret.sym.hasHandle() && !ret.sym.isPtrOrIface(): - hasAddrOfTmp = true - g.gofile.Printf("cret := ") - case ret.sym.go2py != "": - hasRetCvt = true - g.gofile.Printf("return %s(", ret.sym.go2py) - default: - g.gofile.Printf("return ") - } - } + log.Println(mnm) + if nres == 0 { wrapArgs = append(wrapArgs, "goRun") } - g.pywrap.Printf("%s)", strings.Join(wrapArgs, ", ")) - if rvHasHandle { - g.pywrap.Printf(")") + + pyFuncCall := fmt.Sprintf("_%s.%s(%s)", pkgname, mnm, strings.Join(wrapArgs, ", ")) + if npyres > 0 { + retvars := make([]string, nres, nres) + for i := 0; i < npyres; i++ { + retvars[i] = "ret" + strconv.Itoa(i) + if res[i].sym.hasHandle() { + retvars[i] = "_" + retvars[i] + } + } + + if fsym.haserr { + // Only used if npyres == nres. + retvars[nres-1] = "__err" + } + + // May drop the error var, if it is not supposed to be returned. + retvars = retvars[0:npyres] + + // Call upstream method and collect returns. + g.pywrap.Printf(fmt.Sprintf("%s = %s\n", strings.Join(retvars, ", "), pyFuncCall)) + + // ReMap handle returns from pyFuncCall. + for i := 0; i < npyres; i++ { + if res[i].sym.hasHandle() { + cvnm := res[i].sym.pyPkgId(g.pkg.pkg) + g.pywrap.Printf("ret%d = %s(handle=_ret%d)\n", i, cvnm, i) + retvars[i] = "ret" + strconv.Itoa(i) + } + } + + g.pywrap.Printf("return %s", strings.Join(retvars, ", ")) + } else { + g.pywrap.Printf(pyFuncCall) } - funCall := "" + g.pywrap.Printf("\n") + g.pywrap.Outdent() + g.pywrap.Printf("\n") + + goFuncCall := "" if isMethod { if sym.isStruct() { - funCall = fmt.Sprintf("gopyh.Embed(vifc, reflect.TypeOf(%s{})).(%s).%s(%s)", nonPtrName(symNm), symNm, fsym.GoName(), strings.Join(callArgs, ", ")) + goFuncCall = fmt.Sprintf("gopyh.Embed(vifc, reflect.TypeOf(%s{})).(%s).%s(%s)", + nonPtrName(symNm), + symNm, + fsym.GoName(), + strings.Join(callArgs, ", ")) } else { - funCall = fmt.Sprintf("vifc.(%s).%s(%s)", symNm, fsym.GoName(), strings.Join(callArgs, ", ")) + goFuncCall = fmt.Sprintf("vifc.(%s).%s(%s)", + symNm, + fsym.GoName(), + strings.Join(callArgs, ", ")) } } else { - funCall = fmt.Sprintf("%s(%s)", fsym.GoFmt(), strings.Join(callArgs, ", ")) - } - if hasRetCvt { - ret := res[0] - funCall += fmt.Sprintf(")%s", ret.sym.go2pyParenEx) + goFuncCall = fmt.Sprintf("%s(%s)", + fsym.GoFmt(), + strings.Join(callArgs, ", ")) } - if nres == 0 { + if nres > 0 { + retvals := make([]string, nres, nres) + for i := 0; i < npyres; i++ { + retvals[i] = "cret" + strconv.Itoa(i) + } + if fsym.haserr { + retvals[nres-1] = "__err_ret" + } + + // Call upstream method and collect returns. + g.gofile.Printf(fmt.Sprintf("%s := %s\n", strings.Join(retvals, ", "), goFuncCall)) + + // ReMap handle returns from pyFuncCall. + for i := 0; i < npyres; i++ { + if res[i].sym.hasHandle() && !res[i].sym.isPtrOrIface() { + retvals[i] = "&" + retvals[i] + } + } + + if fsym.haserr { + g.gofile.Printf("\n") + + if rvHasErr { + retvals[npyres-1] = "estr" // NOTE: leaked string + g.gofile.Printf("var estr C.CString\n") + g.gofile.Printf("if __err_ret != nil {\n") + g.gofile.Indent() + g.gofile.Printf("estr = C.CString(__err_ret.Error())// NOTE: leaked string\n") // NOTE: leaked string + g.gofile.Outdent() + g.gofile.Printf("} else {\n") + g.gofile.Indent() + g.gofile.Printf("estr = C.CString(\"\")// NOTE: leaked string\n") // NOTE: leaked string + g.gofile.Outdent() + g.gofile.Printf("}\n") + } else { + g.gofile.Printf("if __err_ret != nil {\n") + g.gofile.Indent() + g.gofile.Printf("estr := C.CString(__err_ret.Error())\n") // NOTE: freed string + g.gofile.Printf("C.PyErr_SetString(C.PyExc_RuntimeError, estr)\n") + g.gofile.Printf("C.free(unsafe.Pointer(estr))\n") // python should have converted, safe + g.gofile.Outdent() + g.gofile.Printf("}\n") + } + } + + g.generateReturn(buildPyTuple(fsym), npyres, res, &retvals) + } else { g.gofile.Printf("if boolPyToGo(goRun) {\n") g.gofile.Indent() - g.gofile.Printf("go %s\n", funCall) + g.gofile.Printf("go %s\n", goFuncCall) g.gofile.Outdent() g.gofile.Printf("} else {\n") g.gofile.Indent() - g.gofile.Printf("%s\n", funCall) + g.gofile.Printf("%s\n", goFuncCall) g.gofile.Outdent() g.gofile.Printf("}") - } else { - g.gofile.Printf("%s\n", funCall) } - if rvIsErr || nres == 2 { - g.gofile.Printf("\n") - g.gofile.Printf("if __err != nil {\n") - g.gofile.Indent() - g.gofile.Printf("estr := C.CString(__err.Error())\n") - g.gofile.Printf("C.PyErr_SetString(C.PyExc_RuntimeError, estr)\n") - if rvIsErr { - g.gofile.Printf("return estr\n") // NOTE: leaked string - } else { - g.gofile.Printf("C.free(unsafe.Pointer(estr))\n") // python should have converted, safe - ret := res[0] - if ret.sym.zval == "" { - fmt.Printf("gopy: programmer error: empty zval zero value in symbol: %v\n", ret.sym) - } - if ret.sym.go2py != "" { - g.gofile.Printf("return %s(%s)%s\n", ret.sym.go2py, ret.sym.zval, ret.sym.go2pyParenEx) - } else { - g.gofile.Printf("return %s\n", ret.sym.zval) - } - } - g.gofile.Outdent() - g.gofile.Printf("}\n") - if rvIsErr { - g.gofile.Printf("return C.CString(\"\")") // NOTE: leaked string - } else { - ret := res[0] - if ret.sym.go2py != "" { - if ret.sym.hasHandle() && !ret.sym.isPtrOrIface() { - g.gofile.Printf("return %s(&cret)%s", ret.sym.go2py, ret.sym.go2pyParenEx) - } else { - g.gofile.Printf("return %s(cret)%s", ret.sym.go2py, ret.sym.go2pyParenEx) - } - } else { - g.gofile.Printf("return cret") - } - } - } else if hasAddrOfTmp { - ret := res[0] - g.gofile.Printf("\nreturn %s(&cret)%s", ret.sym.go2py, ret.sym.go2pyParenEx) - } g.gofile.Printf("\n") g.gofile.Outdent() g.gofile.Printf("}\n") - - g.pywrap.Printf("\n") - g.pywrap.Outdent() } diff --git a/bind/package.go b/bind/package.go index 8a87df57..044e2e25 100644 --- a/bind/package.go +++ b/bind/package.go @@ -163,7 +163,7 @@ func (p *Package) getDoc(parent string, o types.Object) string { case *types.Func: sig := o.Type().(*types.Signature) - _, _, _, err := isPyCompatFunc(sig) + _, _, err := isPyCompatFunc(sig) if err != nil { return "" } @@ -241,11 +241,26 @@ func (p *Package) getDoc(parent string, o types.Object) string { } params := parseFn(sig.Params()) - results := parseFn(sig.Results()) + + res := sig.Results() + results := parseFn(res) + + if len(results) > 0 { + lastResult := res.At(len(results) - 1) + if isErrorType(lastResult.Type()) { + if !NoPyExceptions { + results = results[0 : len(results)-1] + } + } + } paramString := strings.Join(params, ", ") resultString := strings.Join(results, ", ") + if len(results) > 1 { + resultString = "(" + resultString + ")" + } + //FIXME(sbinet): add receiver for methods? docSig := fmt.Sprintf("%s(%s) %s", o.Name(), paramString, resultString) @@ -407,12 +422,12 @@ func (p *Package) process() error { continue } ret := fct.Return() - if ret == nil { + if len(ret) == 0 || len(ret) > 1 { continue } - retptr, retIsPtr := ret.(*types.Pointer) + retptr, retIsPtr := ret[0].(*types.Pointer) - if ret == styp || (retIsPtr && retptr.Elem() == styp) { + if ret[0] == styp || (retIsPtr && retptr.Elem() == styp) { delete(funcs, name) fct.doc = p.getDoc(sname, scope.Lookup(name)) fct.ctor = true diff --git a/bind/symbols.go b/bind/symbols.go index a9249d26..20be4c6e 100644 --- a/bind/symbols.go +++ b/bind/symbols.go @@ -144,48 +144,50 @@ func isPyCompatField(f *types.Var) (*symbol, error) { return ftyp, isPyCompatVar(ftyp) } +func getPyReturnType(sig *types.Signature) (ret []types.Type, err error) { + results := sig.Results() + + if results.Len() == 0 { + return make([]types.Type, 0, 0), nil + } else { + outCount := results.Len() + if !NoPyExceptions && isErrorType(results.At(outCount-1).Type()) { + outCount -= 1 + } + + retval := make([]types.Type, outCount, outCount) + for i := 0; i < outCount; i++ { + retval[i] = results.At(i).Type() + } + + return retval, nil + } +} + // isPyCompatFunc checks if function signature is a python-compatible function. // Returns nil if function is compatible, err message if not. // Also returns the return type of the function // haserr is true if 2nd arg is an error type, which is only // supported form of multi-return-value functions // hasfun is true if one of the args is a function signature -func isPyCompatFunc(sig *types.Signature) (ret types.Type, haserr, hasfun bool, err error) { - res := sig.Results() - - switch res.Len() { - case 2: - if !isErrorType(res.At(1).Type()) { - err = fmt.Errorf("gopy: second result value must be of type error: %s", sig.String()) - return - } - haserr = true - ret = res.At(0).Type() - case 1: - if isErrorType(res.At(0).Type()) { - haserr = true - ret = nil +func isPyCompatFunc(sig *types.Signature) (haserr, hasfun bool, err error) { + results := sig.Results() + + for i := 0; i < results.Len(); i++ { + result := results.At(i) + if i < results.Len()-1 { + if isErrorType(result.Type()) { + err = fmt.Errorf("gopy: Only last result value can be of type error: %s", sig.String()) + return + } } else { - ret = res.At(0).Type() - } - case 0: - ret = nil - default: - err = fmt.Errorf("gopy: too many results to return: %s", sig.String()) - return - } - - if ret != nil { - if err = isPyCompatType(ret); err != nil { - return - } - if _, isSig := ret.Underlying().(*types.Signature); isSig { - err = fmt.Errorf("gopy: return type is signature") - return - } - if ret.Underlying().String() == "interface{}" { - err = fmt.Errorf("gopy: return type is interface{}") - return + if isErrorType(result.Type()) { + haserr = true + } + if isFuncType(result.Type()) { + err = fmt.Errorf("gopy: Result values of type function are not supported: %s", sig.String()) + return + } } } @@ -554,7 +556,7 @@ func (sym *symtab) addSymbol(obj types.Object) error { case *types.Func: sig := obj.Type().Underlying().(*types.Signature) - _, _, _, err := isPyCompatFunc(sig) + _, _, err := isPyCompatFunc(sig) if err == nil { sym.syms[fn] = &symbol{ gopkg: pkg, @@ -1137,7 +1139,7 @@ func (sym *symtab) addSignatureType(pkg *types.Package, obj types.Object, t type func (sym *symtab) addMethod(pkg *types.Package, obj types.Object, t types.Type, kind symkind, id, n string) error { sig := t.Underlying().(*types.Signature) - _, _, _, err := isPyCompatFunc(sig) + _, _, err := isPyCompatFunc(sig) if err != nil { if !NoWarn { fmt.Printf("ignoring python incompatible method: %v.%v: %v: %v\n", pkg.Name(), obj.String(), t.String(), err) diff --git a/bind/types.go b/bind/types.go index 1de871e3..58a4e2ec 100644 --- a/bind/types.go +++ b/bind/types.go @@ -369,19 +369,24 @@ type Func struct { id string doc string - ret types.Type // return type, if any - err bool // true if original go func has comma-error - ctor bool // true if this is a newXXX function - hasfun bool // true if this function has a function argument - isVariadic bool // True, if this is a variadic function. + ret []types.Type // return type, if any + haserr bool // true if original go func has comma-error + ctor bool // true if this is a newXXX function + hasfun bool // true if this function has a function argument + isVariadic bool // True, if this is a variadic function. } func newFuncFrom(p *Package, parent string, obj types.Object, sig *types.Signature) (*Func, error) { - ret, haserr, hasfun, err := isPyCompatFunc(sig) + haserr, hasfun, err := isPyCompatFunc(sig) if err != nil { return nil, err } + ret, err2 := getPyReturnType(sig) + if err2 != nil { + return nil, err2 + } + id := obj.Pkg().Name() + "_" + obj.Name() if parent != "" { id = obj.Pkg().Name() + "_" + parent + "_" + obj.Name() @@ -401,7 +406,7 @@ func newFuncFrom(p *Package, parent string, obj types.Object, sig *types.Signatu id: id, doc: p.getDoc(parent, obj), ret: ret, - err: haserr, + haserr: haserr, hasfun: hasfun, isVariadic: sig.Variadic(), }, nil @@ -452,7 +457,7 @@ func (f *Func) Signature() *Signature { return f.sig } -func (f *Func) Return() types.Type { +func (f *Func) Return() []types.Type { return f.ret } diff --git a/bind/utils.go b/bind/utils.go index a78fe4f2..ab2f158b 100644 --- a/bind/utils.go +++ b/bind/utils.go @@ -24,6 +24,11 @@ func isErrorType(typ types.Type) bool { return typ == types.Universe.Lookup("error").Type() } +func isFuncType(typ types.Type) bool { + _, ok := typ.Underlying().(*types.Signature) + return ok +} + func isStringer(obj types.Object) bool { switch obj := obj.(type) { case *types.Func: diff --git a/cmd_build.go b/cmd_build.go index 43d3c393..da03d2c7 100644 --- a/cmd_build.go +++ b/cmd_build.go @@ -16,7 +16,7 @@ import ( "github.com/gonuts/commander" "github.com/gonuts/flag" - "github.com/go-python/gopy/bind" + "github.com/rudderlabs/gopy/bind" ) func gopyMakeCmdBuild() *commander.Command { @@ -29,21 +29,18 @@ build generates and compiles (C)Python language bindings for Go package(s). ex: $ gopy build [options] [other-go-package...] - $ gopy build github.com/go-python/gopy/_examples/hi + $ gopy build github.com/rudderlabs/gopy/_examples/hi `, Flag: *flag.NewFlagSet("gopy-build", flag.ExitOnError), } - cmd.Flag.String("vm", "python", "path to python interpreter") - cmd.Flag.String("output", "", "output directory for bindings") - cmd.Flag.String("name", "", "name of output package (otherwise name of first package is used)") - cmd.Flag.String("main", "", "code string to run in the go main() function in the cgo library") + AddCommonCmdFlags(&cmd.Flag) + + // Build Specific flags. cmd.Flag.String("package-prefix", ".", "custom package prefix used when generating import "+ "statements for generated package") - cmd.Flag.Bool("rename", false, "rename Go symbols to python PEP snake_case") cmd.Flag.Bool("symbols", true, "include symbols in output") - cmd.Flag.Bool("no-warn", false, "suppress warning messages, which may be expected") - cmd.Flag.Bool("no-make", false, "do not generate a Makefile, e.g., when called from Makefile") + return cmd } @@ -54,19 +51,12 @@ func gopyRunCmdBuild(cmdr *commander.Command, args []string) error { return err } - cfg := NewBuildCfg() - cfg.OutputDir = cmdr.Flag.Lookup("output").Value.Get().(string) - cfg.Name = cmdr.Flag.Lookup("name").Value.Get().(string) - cfg.Main = cmdr.Flag.Lookup("main").Value.Get().(string) - cfg.VM = cmdr.Flag.Lookup("vm").Value.Get().(string) - cfg.PkgPrefix = cmdr.Flag.Lookup("package-prefix").Value.Get().(string) - cfg.RenameCase = cmdr.Flag.Lookup("rename").Value.Get().(bool) + cfg := NewBuildCfg(&cmdr.Flag) cfg.Symbols = cmdr.Flag.Lookup("symbols").Value.Get().(bool) - cfg.NoWarn = cmdr.Flag.Lookup("no-warn").Value.Get().(bool) - cfg.NoMake = cmdr.Flag.Lookup("no-make").Value.Get().(bool) bind.NoWarn = cfg.NoWarn bind.NoMake = cfg.NoMake + bind.NoPyExceptions = cfg.NoPyExceptions for _, path := range args { bpkg, err := loadPackage(path, true) // build first @@ -116,8 +106,6 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error { return err } - pycfg, err := bind.GetPythonConfig(cfg.VM) - if mode == bind.ModeExe { of, err := os.Create(buildname + ".h") // overwrite existing fmt.Fprintf(of, "typedef uint8_t bool;\n") @@ -155,38 +143,13 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error { } } else { - buildLib := buildname + libExt - extext := libExt - if runtime.GOOS == "windows" { - extext = ".pyd" - } - if pycfg.ExtSuffix != "" { - extext = pycfg.ExtSuffix - } - modlib := "_" + cfg.Name + extext - - // build the go shared library upfront to generate the header - // needed by our generated cpython code - args := []string{"build", "-mod=mod", "-buildmode=c-shared"} - if !cfg.Symbols { - // These flags will omit the various symbol tables, thereby - // reducing the final size of the binary. From https://golang.org/cmd/link/ - // -s Omit the symbol table and debug information - // -w Omit the DWARF symbol table - args = append(args, "-ldflags=-s -w") - } - args = append(args, "-o", buildLib, ".") - fmt.Printf("go %v\n", strings.Join(args, " ")) - cmd = exec.Command("go", args...) - cmdout, err = cmd.CombinedOutput() + + // build extension with go + c + args, env, err := getBuildArgsAndEnv(cfg) if err != nil { - fmt.Printf("cmd had error: %v output:\n%v\n", err, string(cmdout)) + fmt.Printf("error building environment: %v\n", err) return err } - // update the output name to the one with the ABI extension - args[len(args)-2] = modlib - // we don't need this initial lib because we are going to relink - os.Remove(buildLib) // generate c code fmt.Printf("%v build.py\n", cfg.VM) @@ -207,48 +170,6 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error { } } - cflags := strings.Fields(strings.TrimSpace(pycfg.CFlags)) - cflags = append(cflags, "-fPIC", "-Ofast") - if include, exists := os.LookupEnv("GOPY_INCLUDE"); exists { - cflags = append(cflags, "-I"+filepath.ToSlash(include)) - } - - ldflags := strings.Fields(strings.TrimSpace(pycfg.LdFlags)) - if !cfg.Symbols { - ldflags = append(ldflags, "-s") - } - if lib, exists := os.LookupEnv("GOPY_LIBDIR"); exists { - ldflags = append(ldflags, "-L"+filepath.ToSlash(lib)) - } - if libname, exists := os.LookupEnv("GOPY_PYLIB"); exists { - ldflags = append(ldflags, "-l"+filepath.ToSlash(libname)) - } - - removeEmpty := func(src []string) []string { - o := make([]string, 0, len(src)) - for _, v := range src { - if v == "" { - continue - } - o = append(o, v) - } - return o - } - - cflags = removeEmpty(cflags) - ldflags = removeEmpty(ldflags) - - cflagsEnv := fmt.Sprintf("CGO_CFLAGS=%s", strings.Join(cflags, " ")) - ldflagsEnv := fmt.Sprintf("CGO_LDFLAGS=%s", strings.Join(ldflags, " ")) - - env := os.Environ() - env = append(env, cflagsEnv) - env = append(env, ldflagsEnv) - - fmt.Println(cflagsEnv) - fmt.Println(ldflagsEnv) - - // build extension with go + c fmt.Printf("go %v\n", strings.Join(args, " ")) cmd = exec.Command("go", args...) cmd.Env = env @@ -261,3 +182,89 @@ func runBuild(mode bind.BuildMode, cfg *BuildCfg) error { return err } + +func getBuildArgsAndEnv(cfg *BuildCfg) (args []string, env []string, err error) { + buildname := cfg.Name + "_go" + buildLib := buildname + libExt + + var pycfg bind.PyConfig + pycfg, err = bind.GetPythonConfig(cfg.VM) + if err != nil { + fmt.Printf("error creating pycfg: %r\n", err) + return args, env, err + } + + extext := libExt + if runtime.GOOS == "windows" { + extext = ".pyd" + } + if pycfg.ExtSuffix != "" { + extext = pycfg.ExtSuffix + } + modlib := "_" + cfg.Name + extext + + // build the go shared library upfront to generate the header + // needed by our generated cpython code + args = []string{"build", "-mod=mod", "-buildmode=c-shared"} + if !cfg.Symbols { + // These flags will omit the various symbol tables, thereby + // reducing the final size of the binary. From https://golang.org/cmd/link/ + // -s Omit the symbol table and debug information + // -w Omit the DWARF symbol table + args = append(args, "-ldflags=-s -w") + } + args = append(args, "-o", buildLib, ".") + 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)) + return args, env, err + } + // update the output name to the one with the ABI extension + args[len(args)-2] = modlib + // we don't need this initial lib because we are going to relink + os.Remove(buildLib) + + cflags := strings.Fields(strings.TrimSpace(pycfg.CFlags)) + cflags = append(cflags, "-fPIC", "-Ofast") + if include, exists := os.LookupEnv("GOPY_INCLUDE"); exists { + cflags = append(cflags, "-I"+filepath.ToSlash(include)) + } + + ldflags := strings.Fields(strings.TrimSpace(pycfg.LdFlags)) + if !cfg.Symbols { + ldflags = append(ldflags, "-s") + } + if lib, exists := os.LookupEnv("GOPY_LIBDIR"); exists { + ldflags = append(ldflags, "-L"+filepath.ToSlash(lib)) + } + if libname, exists := os.LookupEnv("GOPY_PYLIB"); exists { + ldflags = append(ldflags, "-l"+filepath.ToSlash(libname)) + } + + removeEmpty := func(src []string) []string { + o := make([]string, 0, len(src)) + for _, v := range src { + if v == "" { + continue + } + o = append(o, v) + } + return o + } + + cflags = removeEmpty(cflags) + ldflags = removeEmpty(ldflags) + + cflagsEnv := fmt.Sprintf("CGO_CFLAGS=%s", strings.Join(cflags, " ")) + ldflagsEnv := fmt.Sprintf("CGO_LDFLAGS=%s", strings.Join(ldflags, " ")) + + env = os.Environ() + env = append(env, cflagsEnv) + env = append(env, ldflagsEnv) + + fmt.Println(cflagsEnv) + fmt.Println(ldflagsEnv) + return args, env, err +} diff --git a/cmd_exe.go b/cmd_exe.go index 22b43665..1f0dd6d2 100644 --- a/cmd_exe.go +++ b/cmd_exe.go @@ -11,9 +11,9 @@ import ( "path/filepath" "strings" - "github.com/go-python/gopy/bind" "github.com/gonuts/commander" "github.com/gonuts/flag" + "github.com/rudderlabs/gopy/bind" ) // python packaging links: @@ -35,19 +35,16 @@ When including multiple packages, list in order of increasing dependency, and us ex: $ gopy exe [options] [other-go-package...] - $ gopy exe github.com/go-python/gopy/_examples/hi + $ gopy exe github.com/rudderlabs/gopy/_examples/hi `, Flag: *flag.NewFlagSet("gopy-exe", flag.ExitOnError), } - cmd.Flag.String("vm", "python", "path to python interpreter") - cmd.Flag.String("output", "", "output directory for root of package") - cmd.Flag.String("name", "", "name of output package (otherwise name of first package is used)") - cmd.Flag.String("main", "", "code string to run in the go main() function in the cgo library "+ - "-- defaults to GoPyMainRun() but typically should be overriden") + AddCommonCmdFlags(&cmd.Flag) + + // Exe specific flags. // cmd.Flag.String("package-prefix", ".", "custom package prefix used when generating import "+ // "statements for generated package") - cmd.Flag.Bool("rename", false, "rename Go symbols to python PEP snake_case") cmd.Flag.Bool("symbols", true, "include symbols in output") cmd.Flag.String("exclude", "", "comma-separated list of package names to exclude") cmd.Flag.String("user", "", "username on https://www.pypa.io/en/latest/ for package name suffix") @@ -55,9 +52,7 @@ ex: cmd.Flag.String("author", "gopy", "author name") cmd.Flag.String("email", "gopy@example.com", "author email") cmd.Flag.String("desc", "", "short description of project (long comes from README.md)") - cmd.Flag.String("url", "https://github.com/go-python/gopy", "home page for project") - cmd.Flag.Bool("no-warn", false, "suppress warning messages, which may be expected") - cmd.Flag.Bool("no-make", false, "do not generate a Makefile, e.g., when called from Makefile") + cmd.Flag.String("url", "https://github.com/rudderlabs/gopy", "home page for project") return cmd } @@ -69,17 +64,8 @@ func gopyRunCmdExe(cmdr *commander.Command, args []string) error { return err } - cfg := NewBuildCfg() - cfg.OutputDir = cmdr.Flag.Lookup("output").Value.Get().(string) - cfg.Name = cmdr.Flag.Lookup("name").Value.Get().(string) - cfg.Main = cmdr.Flag.Lookup("main").Value.Get().(string) - cfg.VM = cmdr.Flag.Lookup("vm").Value.Get().(string) - // cfg.PkgPrefix = cmdr.Flag.Lookup("package-prefix").Value.Get().(string) - cfg.PkgPrefix = "" // doesn't make sense for exe - cfg.RenameCase = cmdr.Flag.Lookup("rename").Value.Get().(bool) + cfg := NewBuildCfg(&cmdr.Flag) cfg.Symbols = cmdr.Flag.Lookup("symbols").Value.Get().(bool) - cfg.NoWarn = cmdr.Flag.Lookup("no-warn").Value.Get().(bool) - cfg.NoMake = cmdr.Flag.Lookup("no-make").Value.Get().(bool) var ( exclude = cmdr.Flag.Lookup("exclude").Value.Get().(string) @@ -93,6 +79,7 @@ func gopyRunCmdExe(cmdr *commander.Command, args []string) error { bind.NoWarn = cfg.NoWarn bind.NoMake = cfg.NoMake + bind.NoPyExceptions = cfg.NoPyExceptions if cfg.Name == "" { path := args[0] diff --git a/cmd_gen.go b/cmd_gen.go index 97a62384..660cc0cd 100644 --- a/cmd_gen.go +++ b/cmd_gen.go @@ -8,9 +8,9 @@ import ( "fmt" "log" - "github.com/go-python/gopy/bind" "github.com/gonuts/commander" "github.com/gonuts/flag" + "github.com/rudderlabs/gopy/bind" ) func gopyMakeCmdGen() *commander.Command { @@ -23,20 +23,17 @@ gen generates (C)Python language bindings for Go package(s). ex: $ gopy gen [options] [other-go-package...] - $ gopy gen github.com/go-python/gopy/_examples/hi + $ gopy gen github.com/rudderlabs/gopy/_examples/hi `, Flag: *flag.NewFlagSet("gopy-gen", flag.ExitOnError), } - cmd.Flag.String("vm", "python", "path to python interpreter") - cmd.Flag.String("output", "", "output directory for bindings") - cmd.Flag.String("name", "", "name of output package (otherwise name of first package is used)") - cmd.Flag.String("main", "", "code string to run in the go main() function in the cgo library") + AddCommonCmdFlags(&cmd.Flag) + + // Gen specific flags. cmd.Flag.String("package-prefix", ".", "custom package prefix used when generating import "+ "statements for generated package") - cmd.Flag.Bool("rename", false, "rename Go symbols to python PEP snake_case") - cmd.Flag.Bool("no-warn", false, "suppress warning messages, which may be expected") - cmd.Flag.Bool("no-make", false, "do not generate a Makefile, e.g., when called from Makefile") + return cmd } @@ -49,19 +46,7 @@ func gopyRunCmdGen(cmdr *commander.Command, args []string) error { return err } - cfg := NewBuildCfg() - cfg.OutputDir = cmdr.Flag.Lookup("output").Value.Get().(string) - cfg.VM = cmdr.Flag.Lookup("vm").Value.Get().(string) - cfg.Name = cmdr.Flag.Lookup("name").Value.Get().(string) - cfg.Main = cmdr.Flag.Lookup("main").Value.Get().(string) - cfg.PkgPrefix = cmdr.Flag.Lookup("package-prefix").Value.Get().(string) - cfg.RenameCase = cmdr.Flag.Lookup("rename").Value.Get().(bool) - cfg.NoWarn = cmdr.Flag.Lookup("no-warn").Value.Get().(bool) - cfg.NoMake = cmdr.Flag.Lookup("no-make").Value.Get().(bool) - - if cfg.VM == "" { - cfg.VM = "python" - } + cfg := NewBuildCfg(&cmdr.Flag) bind.NoWarn = cfg.NoWarn bind.NoMake = cfg.NoMake diff --git a/cmd_pkg.go b/cmd_pkg.go index ce0f437f..ba913305 100644 --- a/cmd_pkg.go +++ b/cmd_pkg.go @@ -11,9 +11,9 @@ import ( "path/filepath" "strings" - "github.com/go-python/gopy/bind" "github.com/gonuts/commander" "github.com/gonuts/flag" + "github.com/rudderlabs/gopy/bind" ) // python packaging links: @@ -33,18 +33,16 @@ When including multiple packages, list in order of increasing dependency, and us ex: $ gopy pkg [options] [other-go-package...] - $ gopy pkg github.com/go-python/gopy/_examples/hi + $ gopy pkg github.com/rudderlabs/gopy/_examples/hi `, Flag: *flag.NewFlagSet("gopy-pkg", flag.ExitOnError), } - cmd.Flag.String("vm", "python", "path to python interpreter") - cmd.Flag.String("output", "", "output directory for root of package") - cmd.Flag.String("name", "", "name of output package (otherwise name of first package is used)") - cmd.Flag.String("main", "", "code string to run in the go GoPyInit() function in the cgo library") + AddCommonCmdFlags(&cmd.Flag) + + // Pkg specific flags. cmd.Flag.String("package-prefix", ".", "custom package prefix used when generating import "+ "statements for generated package") - cmd.Flag.Bool("rename", false, "rename Go symbols to python PEP snake_case") cmd.Flag.Bool("symbols", true, "include symbols in output") cmd.Flag.String("exclude", "", "comma-separated list of package names to exclude") cmd.Flag.String("user", "", "username on https://www.pypa.io/en/latest/ for package name suffix") @@ -52,9 +50,7 @@ ex: cmd.Flag.String("author", "gopy", "author name") cmd.Flag.String("email", "gopy@example.com", "author email") cmd.Flag.String("desc", "", "short description of project (long comes from README.md)") - cmd.Flag.String("url", "https://github.com/go-python/gopy", "home page for project") - cmd.Flag.Bool("no-warn", false, "suppress warning messages, which may be expected") - cmd.Flag.Bool("no-make", false, "do not generate a Makefile, e.g., when called from Makefile") + cmd.Flag.String("url", "https://github.com/rudderlabs/gopy", "home page for project") return cmd } @@ -66,16 +62,8 @@ func gopyRunCmdPkg(cmdr *commander.Command, args []string) error { return err } - cfg := NewBuildCfg() - cfg.OutputDir = cmdr.Flag.Lookup("output").Value.Get().(string) - cfg.Name = cmdr.Flag.Lookup("name").Value.Get().(string) - cfg.Main = cmdr.Flag.Lookup("main").Value.Get().(string) - cfg.VM = cmdr.Flag.Lookup("vm").Value.Get().(string) - cfg.PkgPrefix = cmdr.Flag.Lookup("package-prefix").Value.Get().(string) - cfg.RenameCase = cmdr.Flag.Lookup("rename").Value.Get().(bool) + cfg := NewBuildCfg(&cmdr.Flag) cfg.Symbols = cmdr.Flag.Lookup("symbols").Value.Get().(bool) - cfg.NoWarn = cmdr.Flag.Lookup("no-warn").Value.Get().(bool) - cfg.NoMake = cmdr.Flag.Lookup("no-make").Value.Get().(bool) var ( exclude = cmdr.Flag.Lookup("exclude").Value.Get().(string) @@ -89,6 +77,7 @@ func gopyRunCmdPkg(cmdr *commander.Command, args []string) error { bind.NoWarn = cfg.NoWarn bind.NoMake = cfg.NoMake + bind.NoPyExceptions = cfg.NoPyExceptions if cfg.Name == "" { path := args[0] diff --git a/gen.go b/gen.go index 78e6235b..88cd9ca9 100644 --- a/gen.go +++ b/gen.go @@ -19,7 +19,7 @@ import ( "github.com/pkg/errors" "golang.org/x/tools/go/packages" - "github.com/go-python/gopy/bind" + "github.com/rudderlabs/gopy/bind" ) // argStr returns the full command args as a string, without path to exe diff --git a/go.mod b/go.mod index 72ad4872..ebbff956 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/go-python/gopy +module github.com/rudderlabs/gopy go 1.15 diff --git a/goerr2pyex/error_translator.go b/goerr2pyex/error_translator.go new file mode 100644 index 00000000..11744089 --- /dev/null +++ b/goerr2pyex/error_translator.go @@ -0,0 +1,18 @@ +package goerr2pyex + +//#include +import "C" +import ( + "unsafe" +) + +func DefaultGoErrorToPyException(err error) bool { + if err != nil { + estr := C.CString(err.Error()) + C.PyErr_SetString(C.PyExc_RuntimeError, estr) + C.free(unsafe.Pointer(estr)) + return true + } else { + return false + } +} diff --git a/goerr2pyex/test.sh b/goerr2pyex/test.sh new file mode 100644 index 00000000..d9e6c438 --- /dev/null +++ b/goerr2pyex/test.sh @@ -0,0 +1,9 @@ +# The script to test build. +#env CGO_ENABLED=1 CGO_CFLAGS="-I/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/include/python3.9 -Wno-error -Wno-implicit-function-declaration -Wno-int-conversion" CGO_LDFLAGS="-L/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 -ldl -framework CoreFoundation" go build -mod=mod -buildmode=c-shared -o ./error_translator.so error_translator.go +#env CGO_ENABLED=1 CGO_CFLAGS="-I/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/include/python3.9 -Wno-error -Wno-implicit-function-declaration -Wno-int-conversion" CGO_LDFLAGS="-L/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 -ldl -framework CoreFoundation" go build -mod=mod -o ./error_translator.so error_translator.go +#env CGO_ENABLED=1 CGO_CFLAGS="-I/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/include/python3.9 -Wno-error -Wno-implicit-function-declaration -Wno-int-conversion" CGO_LDFLAGS="-L/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 -ldl -framework CoreFoundation" go build -o ./error_translator.so error_translator.go +#env CGO_ENABLED=1 CGO_CFLAGS="-I/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/include/python3.9 -Wno-error -Wno-implicit-function-declaration -Wno-int-conversion" CGO_LDFLAGS="-L/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 -ldl -framework CoreFoundation" go build error_translator.go + + +env CGO_ENABLED=1 CGO_CFLAGS="-I/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/include/python3.9 -Wno-error -Wno-implicit-function-declaration -Wno-int-conversion" CGO_LDFLAGS="-L/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/lib -lpython3.9 -ldl -framework CoreFoundation" go build + diff --git a/main.go b/main.go index a8d7dfdb..6c722bd2 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "github.com/gonuts/flag" "github.com/pkg/errors" - "github.com/go-python/gopy/bind" + "github.com/rudderlabs/gopy/bind" ) // BuildCfg contains command options and binding generation options @@ -24,19 +24,58 @@ type BuildCfg struct { // include symbols in output Symbols bool + // suppress warning messages, which may be expected NoWarn bool + // do not generate a Makefile, e.g., when called from Makefile NoMake bool } // NewBuildCfg returns a newly constructed build config -func NewBuildCfg() *BuildCfg { +func NewBuildCfg(flagSet *flag.FlagSet) *BuildCfg { var cfg BuildCfg cfg.Cmd = argStr() + + cfg.OutputDir = flagSet.Lookup("output").Value.Get().(string) + cfg.Name = flagSet.Lookup("name").Value.Get().(string) + cfg.Main = flagSet.Lookup("main").Value.Get().(string) + cfg.VM = flagSet.Lookup("vm").Value.Get().(string) + cfg.RenameCase = flagSet.Lookup("rename").Value.Get().(bool) + cfg.NoWarn = flagSet.Lookup("no-warn").Value.Get().(bool) + cfg.NoMake = flagSet.Lookup("no-make").Value.Get().(bool) + cfg.PkgPrefix = flagSet.Lookup("package-prefix").Value.Get().(string) + cfg.NoPyExceptions = flagSet.Lookup("no-exceptions").Value.Get().(bool) + cfg.ModPathGoErr2PyEx = flagSet.Lookup("gomod-2pyex").Value.Get().(string) + cfg.UsePyTuple4VE = flagSet.Lookup("use-pytuple-4ve").Value.Get().(bool) + + if cfg.ModPathGoErr2PyEx == "" { + cfg.ModPathGoErr2PyEx = "github.com/rudderlabs/gopy/goerr2pyex/" + } + + if cfg.VM == "" { + cfg.VM = "python" + } + return &cfg } +func AddCommonCmdFlags(flagSet *flag.FlagSet) { + flagSet.String("vm", "python", "path to python interpreter") + flagSet.String("output", "", "output directory for root of package") + flagSet.String("name", "", "name of output package (otherwise name of first package is used)") + flagSet.String("main", "", "code string to run in the go main()/GoPyInit() function(s) in the cgo library "+ + "-- defaults to GoPyMainRun() but typically should be overriden") + + flagSet.Bool("rename", false, "rename Go symbols to python PEP snake_case") + flagSet.Bool("no-warn", false, "suppress warning messages, which may be expected") + flagSet.Bool("no-make", false, "do not generate a Makefile, e.g., when called from Makefile") + + flagSet.Bool("no-exceptions", false, "Don't throw python exceptions when the last return is in error.") + flagSet.Bool("use-pytuple-4ve", false, "When Go returns (value, err) with err=nil, return (value, ) tuple to python.") + flagSet.String("gomod-2pyex", "", "Go module to use to translate Go errors to Python exceptions.") +} + func run(args []string) error { app := &commander.Command{ UsageLine: "gopy", diff --git a/main_darwin.go b/main_darwin.go index 8697476a..5edc9205 100644 --- a/main_darwin.go +++ b/main_darwin.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build darwin // +build darwin package main diff --git a/main_test.go b/main_test.go index 12a01930..0a3a1a5e 100644 --- a/main_test.go +++ b/main_test.go @@ -17,7 +17,7 @@ import ( "strings" "testing" - "github.com/go-python/gopy/bind" + "github.com/rudderlabs/gopy/bind" ) var ( @@ -49,6 +49,7 @@ var ( "_examples/cstrings": []string{"py2", "py3"}, "_examples/pkgconflict": []string{"py2", "py3"}, "_examples/variadic": []string{"py3"}, + "_examples/multireturn": []string{"py3"}, } testEnvironment = os.Environ() @@ -60,11 +61,20 @@ func init() { } func TestGovet(t *testing.T) { + buildCmd := gopyMakeCmdBuild() + buildCfg := NewBuildCfg(&buildCmd.Flag) + buildCfg.VM = testBackends["py3"] + buildArgs, buildEnv, err := getBuildArgsAndEnv(buildCfg) + if err != nil { + t.Fatalf("error building env:%v.\n Args:%v.\n Env:%v\n", err, buildArgs, buildEnv) + } + cmd := exec.Command("go", "vet", "./...") buf := new(bytes.Buffer) cmd.Stdout = buf cmd.Stderr = buf - err := cmd.Run() + cmd.Env = buildEnv + err = cmd.Run() if err != nil { t.Fatalf("error running %s:\n%s\n%v", "go vet", string(buf.Bytes()), err) } @@ -99,34 +109,6 @@ func TestGofmt(t *testing.T) { } } -func TestGoPyErrors(t *testing.T) { - pyvm := testBackends["py3"] - workdir, err := ioutil.TempDir("", "gopy-") - if err != nil { - t.Fatalf("could not create workdir: %v\n", err) - } - t.Logf("pyvm: %s making work dir: %s\n", pyvm, workdir) - defer os.RemoveAll(workdir) - - curPkgPath := reflect.TypeOf(pkg{}).PkgPath() - fpath := filepath.Join(curPkgPath, "_examples/gopyerrors") - cmd := exec.Command("go", "run", ".", "gen", "-vm="+pyvm, "-output="+workdir, fpath) - t.Logf("running: %v\n", cmd.Args) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("could not run %v: %+v\n", strings.Join(cmd.Args, " "), err) - } - contains := `--- Processing package: github.com/go-python/gopy/_examples/gopyerrors --- -ignoring python incompatible function: .func github.com/go-python/gopy/_examples/gopyerrors.NotErrorMany() (int, int): func() (int, int): gopy: second result value must be of type error: func() (int, int) -ignoring python incompatible method: gopyerrors.func (*github.com/go-python/gopy/_examples/gopyerrors.Struct).NotErrorMany() (int, string): func() (int, string): gopy: second result value must be of type error: func() (int, string) -ignoring python incompatible method: gopyerrors.func (*github.com/go-python/gopy/_examples/gopyerrors.Struct).TooMany() (int, int, string): func() (int, int, string): gopy: too many results to return: func() (int, int, string) -ignoring python incompatible function: .func github.com/go-python/gopy/_examples/gopyerrors.TooMany() (int, int, string): func() (int, int, string): gopy: too many results to return: func() (int, int, string) -` - if got, want := string(out), contains; !strings.Contains(got, want) { - t.Fatalf("%v does not contain\n%v\n", got, want) - } -} - func TestHi(t *testing.T) { // t.Parallel() path := "_examples/hi" @@ -829,6 +811,34 @@ Type OK }) } +func TestBindMultiReturn(t *testing.T) { + // t.Parallel() + path := "_examples/multireturn" + testPkg(t, pkg{ + path: path, + lang: features[path], + cmd: "build", + extras: nil, + want: []byte(`No Return None +Single WithoutError Return 100 +Single Str WithoutError Return '150' +Single WithError(False) Return None +Single WithError(True). Exception: RuntimeError('Error') +Double WithoutError(Without String) Return (200, 300) +Double WithoutError(With String) Return ('200', '300') +Double WithError(True). Exception: RuntimeError('Error') +Double WithError(False) Return '500' +Triple WithoutError(Without String) Return (600, 700, 800) +Triple WithoutError(With String) Return (600, '700', 800) +Triple WithError(True) Exception: RuntimeError('Error') +Triple WithError(False) Return (1100, 1200) +Triple WithError(True) Exception: RuntimeError('Error') +Triple WithError(False) Return (1500, 1600) +Triple WithoutError() Return (1700, 1800, 1900) +`), + }) +} + // Generate / verify SUPPORT_MATRIX.md from features map. func TestCheckSupportMatrix(t *testing.T) { var buf bytes.Buffer @@ -954,8 +964,8 @@ func writeGoMod(t *testing.T, pkgDir, tstDir string) { template := ` module dummy -require github.com/go-python/gopy v0.0.0 -replace github.com/go-python/gopy => %s +require github.com/rudderlabs/gopy v0.0.0 +replace github.com/rudderlabs/gopy => %s ` contents := fmt.Sprintf(template, pkgDir) if err := ioutil.WriteFile(filepath.Join(tstDir, "go.mod"), []byte(contents), 0666); err != nil { diff --git a/main_unix.go b/main_unix.go index fcd18713..1acf66f9 100644 --- a/main_unix.go +++ b/main_unix.go @@ -2,6 +2,7 @@ // 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 package main diff --git a/main_unix_test.go b/main_unix_test.go index 8d0733a6..0d709f74 100644 --- a/main_unix_test.go +++ b/main_unix_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !windows // +build !windows package main diff --git a/main_windows.go b/main_windows.go index 9800b099..4eaf4222 100644 --- a/main_windows.go +++ b/main_windows.go @@ -7,7 +7,7 @@ package main -import "github.com/go-python/gopy/bind" +import "github.com/rudderlabs/gopy/bind" const ( libExt = ".pyd" diff --git a/pkgsetup.go b/pkgsetup.go index dbc4ad11..54b74189 100644 --- a/pkgsetup.go +++ b/pkgsetup.go @@ -9,7 +9,7 @@ import ( "os" "path/filepath" - "github.com/go-python/gopy/bind" + "github.com/rudderlabs/gopy/bind" ) // 1 = pkg name, 2 = -user, 3 = version 4 = author, 5 = email, 6 = desc, 7 = url