mirror of https://github.com/go-python/gopy.git
378 lines
9.0 KiB
Go
378 lines
9.0 KiB
Go
// 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 <Python.h>
|
|
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)
|
|
}
|