diff --git a/_examples/vars/test.py b/_examples/vars/test.py index 3d307a37..1dbf04bf 100644 --- a/_examples/vars/test.py +++ b/_examples/vars/test.py @@ -7,6 +7,11 @@ import vars +print("doc(vars):\n%s" % repr(vars.__doc__)) +print("doc(vars.GetV1()):\n%s" % repr(vars.GetV1.__doc__)) +print("doc(vars.SetV1()):\n%s" % repr(vars.SetV1.__doc__)) + +print("Initial values") print("v1 = %s" % vars.GetV1()) print("v2 = %s" % vars.GetV2()) print("v3 = %s" % vars.GetV3()) @@ -21,3 +26,24 @@ #print("k3 = %s" % vars.GetKind3()) #print("k4 = %s" % vars.GetKind4()) +vars.SetV1("test1") +vars.SetV2(90) +vars.SetV3(1111.1111) +vars.SetV4("test2") +vars.SetV5(50) +vars.SetV6(50) +vars.SetV7(1111.1111) + +vars.SetKind1(123) +vars.SetKind2(456) +print("New values") +print("v1 = %s" % vars.GetV1()) +print("v2 = %s" % vars.GetV2()) +print("v3 = %s" % vars.GetV3()) +print("v4 = %s" % vars.GetV4()) +print("v5 = %s" % vars.GetV5()) +print("v6 = %s" % vars.GetV6()) +print("v7 = %s" % vars.GetV7()) + +print("k1 = %s" % vars.GetKind1()) +print("k2 = %s" % vars.GetKind2()) diff --git a/bind/bind.go b/bind/bind.go index d320b671..06eb043c 100644 --- a/bind/bind.go +++ b/bind/bind.go @@ -55,16 +55,13 @@ func GenCPython(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error // GenCFFI generates a CFFI package from a Go package // Use 4spaces indentation for Python codes, aka PEP8. -// b is an io.Writer for a builder python script. // w is an io.Writer for a wrapper python script which will be executed by a user. // -// GenCFFI generates a builder python script and a wrapper python script by 3 steps. -// First, GenCFFI analyze which interfaces should be exposed from the Go package. -// Second, Then GenCFFI writes a CFFI builder package with writing exposed interfaces -// Third, The GenCFFI writes a wrapper python script. -func GenCFFI(b io.Writer, w io.Writer, fset *token.FileSet, pkg *Package, lang int) error { +// GenCFFI generates a wrapper python script by 2 steps. +// First, GenCFFI analyzes which interfaces should be exposed from the Go package. +// Then, GenCFFI writes a wrapper python script. +func GenCFFI(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error { gen := &cffiGen{ - builder: &printer{buf: new(bytes.Buffer), indentEach: []byte(" ")}, wrapper: &printer{buf: new(bytes.Buffer), indentEach: []byte(" ")}, fset: fset, pkg: pkg, @@ -76,11 +73,6 @@ func GenCFFI(b io.Writer, w io.Writer, fset *token.FileSet, pkg *Package, lang i return err } - _, err = io.Copy(b, gen.builder) - if err != nil { - return err - } - _, err = io.Copy(w, gen.wrapper) if err != nil { return err diff --git a/bind/gencffi.go b/bind/gencffi.go index 9abcfb1f..84561a41 100644 --- a/bind/gencffi.go +++ b/bind/gencffi.go @@ -9,28 +9,18 @@ import ( ) const ( - /* - FIXME(corona10): ffibuilder.cdef should be written this way. - ffibuilder.cdef(""" - //header exported from 'go tool cgo' - #include "%[3]s.h" - """) - - * discuss: https://github.com/go-python/gopy/pull/93#discussion_r119652220 - */ - builderPreamble = `import os - -from sys import argv -from cffi import FFI - -ffibuilder = FFI() -ffibuilder.set_source( - '%[1]s', - None, - extra_objects=["_%[1]s.so"], -) + // FIXME(corona10): ffibuilder.cdef should be written this way. + // ffi.cdef(""" + // //header exported from 'go tool cgo' + // #include "%[3]s.h" + // """) + // discuss: https://github.com/go-python/gopy/pull/93#discussion_r119652220 + cffiPreamble = `"""%[1]s""" +import os +import cffi as _cffi_backend -ffibuilder.cdef(""" +ffi = _cffi_backend.FFI() +ffi.cdef(""" typedef signed char GoInt8; typedef unsigned char GoUint8; typedef short GoInt16; @@ -57,69 +47,63 @@ extern char* _cgopy_ErrorString(GoInterface p0); extern void cgopy_incref(void* p0); extern void cgopy_decref(void* p0); -extern void cgo_pkg_%[1]s_init(); - +extern void cgo_pkg_%[2]s_init(); ` - builderPreambleEnd = `""") - -if __name__ == "__main__": - ffibuilder.compile() - # Remove itself after compile. - os.remove(argv[0]) -` - - cffiPreamble = ` -__doc__="""%[1]s""" - -import os + cffiHelperPreamble = `""") # python <--> cffi helper. class _cffi_helper(object): here = os.path.dirname(os.path.abspath(__file__)) - lib = ffi.dlopen(os.path.join(here, "_%[2]s.so")) + lib = ffi.dlopen(os.path.join(here, "_%[1]s.so")) @staticmethod - def cffi_cnv_py2c_string(o): + def cffi_cgopy_cnv_py2c_string(o): s = ffi.new("char[]", o) return _cffi_helper.lib._cgopy_GoString(s) @staticmethod - def cffi_cnv_py2c_int(o): + def cffi_cgopy_cnv_py2c_int(o): return ffi.cast('int', o) @staticmethod - def cffi_cnv_py2c_float32(o): + def cffi_cgopy_cnv_py2c_float32(o): return ffi.cast('float', o) @staticmethod - def cffi_cnv_py2c_float64(o): + def cffi_cgopy_cnv_py2c_float64(o): return ffi.cast('double', o) @staticmethod - def cffi_cnv_c2py_string(c): + def cffi_cgopy_cnv_py2c_uint(o): + return ffi.cast('uint', o) + + @staticmethod + def cffi_cgopy_cnv_c2py_string(c): s = _cffi_helper.lib._cgopy_CString(c) return ffi.string(s) @staticmethod - def cffi_cnv_c2py_int(c): + def cffi_cgopy_cnv_c2py_int(c): return int(c) @staticmethod - def cffi_cnv_c2py_float32(c): + def cffi_cgopy_cnv_c2py_float32(c): return float(c) @staticmethod - def cffi_cnv_c2py_float64(c): + def cffi_cgopy_cnv_c2py_float64(c): return float(c) + @staticmethod + def cffi_cgopy_cnv_c2py_uint(c): + return int(c) # make sure Cgo is loaded and initialized -_cffi_helper.lib.cgo_pkg_%[2]s_init() +_cffi_helper.lib.cgo_pkg_%[1]s_init() ` ) type cffiGen struct { - builder *printer wrapper *printer fset *token.FileSet @@ -130,31 +114,137 @@ type cffiGen struct { } func (g *cffiGen) gen() error { - // Write preamble for CFFI builder.py - g.genBuilderPreamble() // Write preamble for CFFI library wrapper. g.genCffiPreamble() + g.genCffiCdef() + g.genWrappedPy() + return nil +} + +func (g *cffiGen) genCffiPreamble() { + n := g.pkg.pkg.Name() + pkgDoc := g.pkg.doc.Doc + g.wrapper.Printf(cffiPreamble, pkgDoc, n) +} + +func (g *cffiGen) genCffiCdef() { + + // first, process slices, arrays + { + names := g.pkg.syms.names() + for _, n := range names { + sym := g.pkg.syms.sym(n) + if !sym.isType() { + continue + } + g.genCdefType(sym) + } + } + for _, f := range g.pkg.funcs { - g.genFunc(f) - g.genCdef(f) + g.genCdefFunc(f) } - // Finalizing preamble for CFFI builder.py - g.genBuilderPreambleEnd() - return nil + for _, c := range g.pkg.consts { + g.genCdefConst(c) + } + + for _, v := range g.pkg.vars { + g.genCdefVar(v) + } } -func (g *cffiGen) genBuilderPreamble() { +func (g *cffiGen) genWrappedPy() { n := g.pkg.pkg.Name() - g.builder.Printf(builderPreamble, n) + g.wrapper.Printf(cffiHelperPreamble, n) + + for _, f := range g.pkg.funcs { + g.genFunc(f) + } + + for _, c := range g.pkg.consts { + g.genConst(c) + } + + for _, v := range g.pkg.vars { + g.genVar(v) + } } -func (g *cffiGen) genBuilderPreambleEnd() { - g.builder.Printf(builderPreambleEnd) +func (g *cffiGen) genConst(c Const) { + g.genGetFunc(c.f) } -func (g *cffiGen) genCffiPreamble() { - n := g.pkg.pkg.Name() - pkgDoc := g.pkg.doc.Doc - g.wrapper.Printf(cffiPreamble, pkgDoc, n) +func (g *cffiGen) genVar(v Var) { + id := g.pkg.Name() + "_" + v.Name() + doc := v.doc + { + res := []*Var{newVar(g.pkg, v.GoType(), "ret", v.Name(), doc)} + sig := newSignature(g.pkg, nil, nil, res) + fget := Func{ + pkg: g.pkg, + sig: sig, + typ: nil, + name: v.Name(), + id: id + "_get", + doc: "returns " + g.pkg.Name() + "." + v.Name(), + ret: v.GoType(), + err: false, + } + g.genGetFunc(fget) + } + { + params := []*Var{newVar(g.pkg, v.GoType(), "arg", v.Name(), doc)} + sig := newSignature(g.pkg, nil, params, nil) + fset := Func{ + pkg: g.pkg, + sig: sig, + typ: nil, + name: v.Name(), + id: id + "_set", + doc: "sets " + g.pkg.Name() + "." + v.Name(), + ret: nil, + err: false, + } + g.genSetFunc(fset) + } +} + +func (g *cffiGen) genCdefConst(c Const) { + g.genCdefFunc(c.f) +} + +func (g *cffiGen) genCdefVar(v Var) { + id := g.pkg.Name() + "_" + v.Name() + doc := v.doc + { + res := []*Var{newVar(g.pkg, v.GoType(), "ret", v.Name(), doc)} + sig := newSignature(g.pkg, nil, nil, res) + fget := Func{ + pkg: g.pkg, + sig: sig, + typ: nil, + name: v.Name(), + id: id + "_get", + doc: "returns " + g.pkg.Name() + "." + v.Name(), + ret: v.GoType(), + err: false, + } + g.genCdefFunc(fget) + } + { + params := []*Var{newVar(g.pkg, v.GoType(), "arg", v.Name(), doc)} + sig := newSignature(g.pkg, nil, params, nil) + fset := Func{ + pkg: g.pkg, + sig: sig, + typ: nil, + name: v.Name(), + id: id + "_set", + doc: "sets " + g.pkg.Name() + "." + v.Name(), + ret: nil, + err: false, + } + g.genCdefFunc(fset) + } } diff --git a/bind/gencffi_cdef.go b/bind/gencffi_cdef.go index 21089eea..a8b2c01a 100644 --- a/bind/gencffi_cdef.go +++ b/bind/gencffi_cdef.go @@ -9,7 +9,28 @@ import ( "strings" ) -func (g *cffiGen) genCdef(f Func) { +func (g *cffiGen) genCdefType(sym *symbol) { + if !sym.isType() { + return + } + + if sym.isStruct() { + return + } + + if sym.isBasic() && !sym.isNamed() { + return + } + + if sym.isBasic() { + btyp := g.pkg.syms.symtype(sym.GoType().Underlying()) + g.wrapper.Printf("typedef %s %s;\n\n", btyp.cgoname, sym.cgoname) + } else { + g.wrapper.Printf("typedef void* %s;\n\n", sym.cgoname) + } +} + +func (g *cffiGen) genCdefFunc(f Func) { var params []string var cdef_ret string sig := f.sig @@ -31,5 +52,5 @@ func (g *cffiGen) genCdef(f Func) { } paramString := strings.Join(params, ", ") - g.builder.Printf("extern %[1]s cgo_func_%[2]s_%[3]s(%[4]s);\n", cdef_ret, g.pkg.Name(), f.name, paramString) + g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, f.id, paramString) } diff --git a/bind/gencffi_func.go b/bind/gencffi_func.go index f8e05254..d764ab93 100644 --- a/bind/gencffi_func.go +++ b/bind/gencffi_func.go @@ -20,10 +20,62 @@ func (g *cffiGen) genFunc(o Func) { g.wrapper.Printf(` # pythonization of: %[1]s.%[2]s def %[2]s(%[3]s): + """%[4]s""" `, g.pkg.pkg.Name(), o.GoName(), strings.Join(funcArgs, ", "), + o.Doc(), + ) + + g.wrapper.Indent() + g.genFuncBody(o) + g.wrapper.Outdent() + g.wrapper.Printf("\n") +} + +func (g *cffiGen) genGetFunc(o Func) { + sig := o.Signature() + args := sig.Params() + + var funcArgs []string + for _, arg := range args { + funcArgs = append(funcArgs, arg.Name()) + } + g.wrapper.Printf(` +# pythonization of: %[1]s.%[2]s +def Get%[2]s(%[3]s): + """%[4]s""" +`, + g.pkg.pkg.Name(), + o.GoName(), + strings.Join(funcArgs, ", "), + o.Doc(), + ) + + g.wrapper.Indent() + g.genFuncBody(o) + g.wrapper.Outdent() + g.wrapper.Printf("\n") +} + +func (g *cffiGen) genSetFunc(o Func) { + sig := o.Signature() + args := sig.Params() + + var funcArgs []string + for _, arg := range args { + funcArgs = append(funcArgs, arg.Name()) + } + g.wrapper.Printf(` +# pythonization of: %[1]s.%[2]s +def Set%[2]s(%[3]s): + """%[4]s""" +`, + g.pkg.pkg.Name(), + o.GoName(), + strings.Join(funcArgs, ", "), + o.Doc(), ) g.wrapper.Indent() @@ -40,8 +92,12 @@ func (g *cffiGen) genFuncBody(f Func) { var funcArgs []string for _, arg := range args { - g.wrapper.Printf("%[1]s = _cffi_helper.cffi_cnv_py2c_%[2]s(%[3]s)\n", arg.getFuncArg(), arg.GoType(), arg.Name()) - funcArgs = append(funcArgs, arg.getFuncArg()) + if arg.sym.hasConverter() { + g.wrapper.Printf("%[1]s = _cffi_helper.cffi_%[2]s(%[3]s)\n", arg.getFuncArg(), arg.sym.py2c, arg.Name()) + funcArgs = append(funcArgs, arg.getFuncArg()) + } else { + funcArgs = append(funcArgs, arg.Name()) + } } if res != nil { @@ -51,15 +107,19 @@ func (g *cffiGen) genFuncBody(f Func) { } } - g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_%[2]s(%[3]s)\n", g.pkg.Name(), f.name, strings.Join(funcArgs, ", ")) + g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s(%[2]s)\n", f.id, strings.Join(funcArgs, ", ")) switch nres { case 0: // no-op case 1: ret := res[0] - g.wrapper.Printf("ret = _cffi_helper.cffi_cnv_c2py_%[1]s(cret)\n", ret.GoType()) - g.wrapper.Printf("return ret\n") + if ret.sym.hasConverter() { + g.wrapper.Printf("ret = _cffi_helper.cffi_%[1]s(cret)\n", ret.sym.c2py) + g.wrapper.Printf("return ret\n") + } else { + g.wrapper.Printf("return cret\n") + } default: panic(fmt.Errorf("gopy: Not yet implemeted for multiple return.")) } diff --git a/cmd_bind.go b/cmd_bind.go index 80f368ec..ee2437b0 100644 --- a/cmd_bind.go +++ b/cmd_bind.go @@ -148,10 +148,33 @@ func gopyRunCmdBind(cmdr *commander.Command, args []string) error { if err != nil { return err } - err = processCFFI(pkg.Name(), buildname, odir, work, wbind) + + cmd = exec.Command( + "/bin/cp", + filepath.Join(wbind, buildname)+".so", + filepath.Join(odir, buildname)+".so", + ) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return err + } + + cmd = exec.Command( + "/bin/cp", + filepath.Join(work, pkg.Name())+".py", + filepath.Join(odir, pkg.Name())+".py", + ) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() if err != nil { return err } + case "python2", "py2": cmd = exec.Command( "go", "build", "-buildmode=c-shared", @@ -186,65 +209,3 @@ func gopyRunCmdBind(cmdr *commander.Command, args []string) error { } return err } - -func processCFFI(pkgname string, buildname string, odir string, work string, wbind string) error { - cmd := exec.Command( - "/bin/cp", - filepath.Join(wbind, buildname)+".so", - filepath.Join(odir, buildname)+".so", - ) - - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - return err - } - - cmd = exec.Command( - "/bin/cp", - filepath.Join(work, "build_"+pkgname)+".py", - filepath.Join(odir, "build_"+pkgname)+".py", - ) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { - return err - } - - cmd = exec.Command( - "python", - filepath.Join(odir, "build_"+pkgname)+".py", - "", - ) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = odir - err = cmd.Run() - - if err != nil { - return err - } - - // FIXME: This sections should be handled by genPy function. - wrappedPy, err := ioutil.ReadFile(filepath.Join(work, pkgname) + ".py") - if err != nil { - return err - } - - f, err := os.OpenFile(filepath.Join(odir, pkgname)+".py", os.O_APPEND|os.O_WRONLY, os.ModeAppend) - if err != nil { - return err - } - defer f.Close() - - _, err = f.WriteString(string(wrappedPy)) - if err != nil { - return err - } - return err -} diff --git a/gen.go b/gen.go index 16f2a24a..66430e67 100644 --- a/gen.go +++ b/gen.go @@ -61,19 +61,7 @@ func genPkg(odir string, p *bind.Package, lang string) error { } defer o.Close() - // File for builder.py - var b *os.File - b, err = os.Create(filepath.Join(odir, "build_"+p.Name()+".py")) - if err != nil { - return err - } - defer b.Close() - err = bind.GenCFFI(b, o, fset, p, 2) - if err != nil { - return err - } - - err = b.Close() + err = bind.GenCFFI(o, fset, p, 2) if err != nil { return err } diff --git a/main_test.go b/main_test.go index 19f1c18e..561bc99f 100644 --- a/main_test.go +++ b/main_test.go @@ -398,6 +398,14 @@ func TestBindEmpty(t *testing.T) { want: []byte(`empty.init()... [CALLED] doc(pkg): 'Package empty does not expose anything.\nWe may want to wrap and import it just for its side-effects.\n' +`), + }) + + testPkgWithCFFI(t, pkg{ + path: "_examples/empty", + want: []byte(`empty.init()... [CALLED] +doc(pkg): +'Package empty does not expose anything.\nWe may want to wrap and import it just for its side-effects.\n' `), }) } @@ -523,6 +531,20 @@ c6 = 42 c7 = 666.666 k1 = 1 k2 = 2 +`), + }) + + testPkgWithCFFI(t, pkg{ + path: "_examples/consts", + want: []byte(`c1 = c1 +c2 = 42 +c3 = 666.666 +c4 = c4 +c5 = 42 +c6 = 42 +c7 = 666.666 +k1 = 1 +k2 = 2 `), }) } @@ -531,7 +553,14 @@ func TestBindVars(t *testing.T) { t.Parallel() testPkg(t, pkg{ path: "_examples/vars", - want: []byte(`v1 = v1 + want: []byte(`doc(vars): +'' +doc(vars.GetV1()): +'' +doc(vars.SetV1()): +'' +Initial values +v1 = v1 v2 = 42 v3 = 666.666 v4 = c4 @@ -540,6 +569,47 @@ v6 = 42 v7 = 666.666 k1 = 1 k2 = 2 +New values +v1 = test1 +v2 = 90 +v3 = 1111.1111 +v4 = test2 +v5 = 50 +v6 = 50 +v7 = 1111.1111 +k1 = 123 +k2 = 456 +`), + }) + + testPkgWithCFFI(t, pkg{ + path: "_examples/vars", + want: []byte(`doc(vars): +'' +doc(vars.GetV1()): +'returns vars.V1' +doc(vars.SetV1()): +'sets vars.V1' +Initial values +v1 = v1 +v2 = 42 +v3 = 666.666 +v4 = c4 +v5 = 42 +v6 = 42 +v7 = 666.666 +k1 = 1 +k2 = 2 +New values +v1 = test1 +v2 = 90 +v3 = 1111.1111 +v4 = test2 +v5 = 50 +v6 = 50 +v7 = 1111.1111 +k1 = 123 +k2 = 456 `), }) } @@ -586,6 +656,14 @@ func TestBindCgoPackage(t *testing.T) { want: []byte(`cgo.doc: 'Package cgo tests bindings of CGo-based packages.\n' cgo.Hi()= 'hi from go\n' cgo.Hello(you)= 'hello you from go\n' +`), + }) + + testPkgWithCFFI(t, pkg{ + path: "_examples/empty", + want: []byte(`empty.init()... [CALLED] +doc(pkg): +'Package empty does not expose anything.\nWe may want to wrap and import it just for its side-effects.\n' `), }) }