gopy/bind/gen.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)
}