// Copyright 2019 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 bind import ( "fmt" "go/token" "path/filepath" ) // this version uses pybindgen and a generated .go file to do the binding // for all preambles: 1 = name of package, 2 = full import path of package, 3 = gen command // GoHandle is the type to use for the Handle map key, go-side // could be a string for more informative but slower handles var GoHandle = "int64" var CGoHandle = "C.longlong" var PyHandle = "int64_t" // var GoHandle = "string" // var CGoHandle = "*C.char" // var PyHandle = "char*" const ( pybGoPreamble = `/* cgo stubs for package %[2]s. File is generated by gopy gen. Do not edit. %[3]s */ package main // #cgo pkg-config: %[4]s // #define Py_LIMITED_API // #include import "C" import ( "fmt" "reflect" "strconv" "sync" "%[2]s" ) func main() {} // type for the handle -- int64 for speed, string for debug type GoHandle %[5]s type CGoHandle %[6]s func boolGoToPy(b bool) C.char { if b { return 1 } return 0 } func boolPyToGo(b C.char) bool { if b != 0 { return true } return false } // --- variable handles: all pointers managed via handles --- type varHandler struct { ctr int64 vars map[GoHandle]interface{} m sync.RWMutex } var varHand varHandler func IfaceIsNil(it interface{}) bool { if it == nil { return true } v := reflect.ValueOf(it) vk := v.Kind() if vk == reflect.Ptr || vk == reflect.Interface || vk == reflect.Map || vk == reflect.Slice || vk == reflect.Func || vk == reflect.Chan { return v.IsNil() } return false } // varFmHandle gets variable from handle string -- reports error to python but does not return it // for use in inline calls func (vh *varHandler) varFmHandle(h CGoHandle, typnm string) interface{} { v, _ := vh.varFmHandleTry(h, typnm) return v } ` // string version of handle functions varHandleString = ` func (vh *varHandler) register(typnm string, ifc interface{}) CGoHandle { if IfaceIsNil(ifc) { return C.CString("nil") } vh.m.Lock() defer vh.m.Unlock() if vh.vars == nil { vh.vars = make(map[GoHandle]interface{}) } hc := vh.ctr vh.ctr++ rnm := typnm + "_" + strconv.FormatInt(hc, 16) vh.vars[GoHandle(rnm)] = ifc return C.CString(rnm) } // Try version returns the error explicitly, for use when error can be processed func (vh *varHandler) varFmHandleTry(h CGoHandle, typnm string) (interface{}, error) { hs := C.GoString(h) if hs == "" || hs == "nil" { return nil, fmt.Errorf("gopy: nil handle") } vh.m.RLock() defer vh.m.RUnlock() // if !strings.HasPrefix(hs, typnm) { // err := fmt.Errorf("gopy: variable handle is not the correct type, should be: " + typnm + " is: " + hs) // C.PyErr_SetString(C.PyExc_TypeError, C.CString(err.Error())) // return nil, err // } v, has := vh.vars[GoHandle(hs)] if !has { err := fmt.Errorf("gopy: variable handle not registered: " + hs) C.PyErr_SetString(C.PyExc_TypeError, C.CString(err.Error())) return nil, err } return v, nil } ` // int64 version of handle functions varHandleInt64 = ` func init() { varHand.ctr = 1 } func (vh *varHandler) register(typnm string, ifc interface{}) CGoHandle { if IfaceIsNil(ifc) { return -1 } vh.m.Lock() defer vh.m.Unlock() if vh.vars == nil { vh.vars = make(map[GoHandle]interface{}) } hc := vh.ctr vh.ctr++ vh.vars[GoHandle(hc)] = ifc return CGoHandle(hc) } // Try version returns the error explicitly, for use when error can be processed func (vh *varHandler) varFmHandleTry(h CGoHandle, typnm string) (interface{}, error) { if h < 1 { return nil, fmt.Errorf("gopy: nil handle") } vh.m.RLock() defer vh.m.RUnlock() v, has := vh.vars[GoHandle(h)] if !has { err := fmt.Errorf("gopy: variable handle not registered: " + strconv.FormatInt(int64(h), 10)) C.PyErr_SetString(C.PyExc_TypeError, C.CString(err.Error())) return nil, err } return v, nil } ` PyBuildPreamble = `# python build stubs for package %[2]s # File is generated by gopy gen. Do not edit. # %[3]s from pybindgen import retval, param, Module import sys mod = Module('_%[1]s') mod.add_include('"%[1]s_go.h"') ` PyWrapPreamble = `%[4]s # python wrapper for package %[2]s # This is what you import to use the package. # File is generated by gopy gen. Do not edit. # %[3]s import _%[1]s import inspect class GoClass(object): """GoClass is the base class for all GoPy wrapper classes""" pass ` MakefileTemplate = `# Makefile for python interface to Go package %[2]s. # File is generated by gopy gen. Do not edit. # %[3]s GOCMD=go GOBUILD=$(GOCMD) build PYTHON=%[4]s PYTHON_CFG=$(PYTHON)-config GCC=gcc LIBEXT=%[5]s # get the flags used to build python: CFLAGS = $(shell $(PYTHON_CFG) --cflags) LDFLAGS = $(shell $(PYTHON_CFG) --ldflags) all: build gen: %[3]s build: # this will otherwise be built during go build and may be out of date - rm %[1]s.c # generate %[1]s_go$(LIBEXT) from %[1]s.go -- the cgo wrappers to go functions $(GOBUILD) -buildmode=c-shared -ldflags="-s -w" -o %[1]s_go$(LIBEXT) %[1]s.go # use pybindgen to build the %[1]s.c file which are the CPython wrappers to cgo wrappers.. # note: pip install pybindgen to get pybindgen if this fails $(PYTHON) build.py # build the _%[1]s$(LIBEXT) library that contains the cgo and CPython wrappers # generated %[1]s.py python wrapper imports this c-code package $(GCC) %[1]s.c -dynamiclib %[1]s_go$(LIBEXT) -o _%[1]s$(LIBEXT) $(CFLAGS) $(LDFLAGS) ` ) // actually, the weird thing comes from adding symbols to the output, even with dylib! // ifeq ($(LIBEXT), ".dylib") // # python apparently doesn't recognize .dylib on mac, but .so causes extra weird dylib dir.. // - ln -s _%[1]s$(LIBEXT) _%[1]s.so // endif // type pybindGen struct { gofile *printer pybuild *printer pywrap *printer makefile *printer fset *token.FileSet pkg *Package err ErrorList vm string // python interpreter libext string lang int // c-python api version (2,3) } func (g *pybindGen) gen() error { pkgimport := g.pkg.pkg.Path() _, pyonly := filepath.Split(g.vm) cmd := fmt.Sprintf("gopy gen -vm=%s %s", pyonly, pkgimport) g.genGoPreamble(cmd) g.genPyBuildPreamble(cmd) g.genPyWrapPreamble(cmd) g.genMakefile(cmd) g.genAll() n := g.pkg.pkg.Name() g.pybuild.Printf("\nmod.generate(open('%v.c', 'w'))\n\n", n) g.gofile.Printf("\n\n") g.pybuild.Printf("\n\n") g.pywrap.Printf("\n\n") g.makefile.Printf("\n\n") return nil } func (g *pybindGen) genGoPreamble(cmd string) { n := g.pkg.pkg.Name() pkgimport := g.pkg.pkg.Path() pypath, pyonly := filepath.Split(g.vm) pyroot, _ := filepath.Split(filepath.Clean(pypath)) libcfg := filepath.Join(filepath.Join(filepath.Join(pyroot, "lib"), "pkgconfig"), pyonly+".pc") g.gofile.Printf(pybGoPreamble, n, pkgimport, cmd, libcfg, GoHandle, CGoHandle) if GoHandle == "string" { g.gofile.Printf(varHandleString) } else { g.gofile.Printf(varHandleInt64) } g.gofile.Printf("\n// --- generated code for package: %[1]s below: ---\n\n", n) } func (g *pybindGen) genPyBuildPreamble(cmd string) { n := g.pkg.pkg.Name() pkgimport := g.pkg.pkg.Path() g.pybuild.Printf(PyBuildPreamble, n, pkgimport, cmd) } func (g *pybindGen) genPyWrapPreamble(cmd string) { n := g.pkg.pkg.Name() pkgimport := g.pkg.pkg.Path() pkgDoc := g.pkg.doc.Doc if pkgDoc != "" { g.pywrap.Printf(PyWrapPreamble, n, pkgimport, cmd, `"""`+"\n"+pkgDoc+"\n"+`"""`) } else { g.pywrap.Printf(PyWrapPreamble, n, pkgimport, cmd, "") } } func (g *pybindGen) genMakefile(cmd string) { n := g.pkg.pkg.Name() pkgimport := g.pkg.pkg.Path() g.makefile.Printf(MakefileTemplate, n, pkgimport, cmd, g.vm, g.libext) } func (g *pybindGen) genAll() { g.gofile.Printf("\n// ---- Types ---\n") g.pywrap.Printf("\n# ---- Types ---\n") names := g.pkg.syms.names() for _, n := range names { sym := g.pkg.syms.sym(n) if !sym.isType() { continue } g.genType(sym) } g.pywrap.Printf("\n\n#---- Constants from Go: Python can only ask that you please don't change these! ---\n") for _, c := range g.pkg.consts { g.genConst(c) } g.gofile.Printf("\n\n// ---- Global Variables: can only use functions to access ---\n") g.pywrap.Printf("\n\n# ---- Global Variables: can only use functions to access ---\n") for _, v := range g.pkg.vars { g.genVar(v) } g.gofile.Printf("\n\n// ---- Interfaces ---\n") g.pywrap.Printf("\n\n# ---- Interfaces ---\n") for _, ifc := range g.pkg.ifaces { g.genInterface(ifc) } g.gofile.Printf("\n\n// ---- Structs ---\n") g.pywrap.Printf("\n\n# ---- Structs ---\n") for _, s := range g.pkg.structs { g.genStruct(s) } // todo: not sure where these come from g.gofile.Printf("\n\n// ---- Constructors ---\n") g.pywrap.Printf("\n\n# ---- Constructors ---\n") for _, s := range g.pkg.structs { for _, ctor := range s.ctors { g.genFunc(ctor) } } g.gofile.Printf("\n\n// ---- Functions ---\n") g.pywrap.Printf("\n\n# ---- Functions ---\n") for _, f := range g.pkg.funcs { g.genFunc(f) } } func (g *pybindGen) genConst(c Const) { g.genConstValue(c) } func (g *pybindGen) genVar(v Var) { g.genVarGetter(v) g.genVarSetter(v) }