Skip to content

Commit

Permalink
all: split unix/windows support
Browse files Browse the repository at this point in the history
  • Loading branch information
sbinet committed Jan 16, 2019
1 parent cbb7913 commit 1f9f33f
Show file tree
Hide file tree
Showing 23 changed files with 521 additions and 346 deletions.
9 changes: 6 additions & 3 deletions README.md
Expand Up @@ -55,9 +55,9 @@ ex:
$ gopy gen github.com/go-python/gopy/_examples/hi

Options:
-lang="py2": target language for bindings
-api="cpython": bindings API to use (cpython, cffi)
-output="": output directory for bindings

-vm="python": path to python interpreter

$ gopy help bind
Usage: gopy bind <go-package-name>
Expand All @@ -69,8 +69,11 @@ ex:
$ gopy bind github.com/go-python/gopy/_examples/hi

Options:
-lang="py2": python version to use for bindings (python2|py2|python3|py3|cffi)
-api="cpython": bindings API to use (cpython, cffi)
-output="": output directory for bindings
-symbols=true: include symbols in output
-vm="python": path to python interpreter
-work=false: print the name of temporary work directory and do not delete it when exiting
```


Expand Down
5 changes: 2 additions & 3 deletions appveyor.yml
Expand Up @@ -27,8 +27,7 @@ build_script:
- "%CPYTHON3DIR%\\python -m pip install --upgrade pip"
- "%CPYTHON2DIR%\\python -m pip install cffi"
- "%CPYTHON3DIR%\\python -m pip install cffi"
# - go get -v -t -tags purego ./... ## FIXME(sbinet)
- go get -v -t ./...

test_script:
- go env
# - go test -tags purego ./... ## FIXME(sbinet)
- go test ./...
9 changes: 6 additions & 3 deletions bind/bind.go
Expand Up @@ -27,12 +27,13 @@ func (list ErrorList) Error() string {
}

// GenCPython generates a (C)Python package from a Go package
func GenCPython(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error {
func GenCPython(w io.Writer, fset *token.FileSet, pkg *Package, vm string, lang int) error {
gen := &cpyGen{
decl: &printer{buf: new(bytes.Buffer), indentEach: []byte("\t")},
impl: &printer{buf: new(bytes.Buffer), indentEach: []byte("\t")},
fset: fset,
pkg: pkg,
vm: vm,
lang: lang,
}
err := gen.gen()
Expand Down Expand Up @@ -60,11 +61,12 @@ func GenCPython(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 {
func GenCFFI(w io.Writer, fset *token.FileSet, pkg *Package, vm string, lang int) error {
gen := &cffiGen{
wrapper: &printer{buf: new(bytes.Buffer), indentEach: []byte(" ")},
fset: fset,
pkg: pkg,
vm: vm,
lang: lang,
}

Expand All @@ -82,12 +84,13 @@ func GenCFFI(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error {
}

// GenGo generates a cgo package from a Go package
func GenGo(w io.Writer, fset *token.FileSet, pkg *Package, lang int, capi string) error {
func GenGo(w io.Writer, fset *token.FileSet, pkg *Package, lang int, vm, capi string) error {
buf := new(bytes.Buffer)
gen := &goGen{
printer: &printer{buf: buf, indentEach: []byte("\t")},
fset: fset,
pkg: pkg,
vm: vm,
lang: lang,
capi: capi,
}
Expand Down
11 changes: 11 additions & 0 deletions bind/bind_unix.go
@@ -0,0 +1,11 @@
// 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.

// +build !windows

package bind

const (
shlibExt = ".so"
)
11 changes: 11 additions & 0 deletions bind/bind_windows.go
@@ -0,0 +1,11 @@
// 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.

// +build windows

package bind

const (
shlibExt = ".dll"
)
7 changes: 4 additions & 3 deletions bind/gencffi.go
Expand Up @@ -66,7 +66,7 @@ extern void cgo_pkg_%[2]s_init();
class _cffi_helper(object):
here = os.path.dirname(os.path.abspath(__file__))
lib = ffi.dlopen(os.path.join(here, "_%[1]s.so"))
lib = ffi.dlopen(os.path.join(here, "_%[1]s%[2]s"))
@staticmethod
def cffi_cgopy_cnv_py2c_bool(o):
Expand Down Expand Up @@ -227,7 +227,8 @@ type cffiGen struct {
pkg *Package
err ErrorList

lang int // c-python api version (2,3)
vm string // python interpreter
lang int // c-python api version (2,3)
}

func (g *cffiGen) gen() error {
Expand Down Expand Up @@ -306,7 +307,7 @@ func (g *cffiGen) genCffiCdef() {

func (g *cffiGen) genWrappedPy() {
n := g.pkg.pkg.Name()
g.wrapper.Printf(cffiHelperPreamble, n)
g.wrapper.Printf(cffiHelperPreamble, n, shlibExt)
g.wrapper.Indent()

// first, process slices, arrays
Expand Down
5 changes: 3 additions & 2 deletions bind/gencpy.go
Expand Up @@ -12,7 +12,7 @@ import (
const (
cPreamble = `/*
C stubs for package %[1]s.
gopy gen -lang=python %[1]s
gopy gen -api=cpython %[1]s
File is generated by gopy gen. Do not edit.
*/
Expand Down Expand Up @@ -180,7 +180,8 @@ type cpyGen struct {
pkg *Package
err ErrorList

lang int // c-python api version (2,3)
vm string // python interpreter
lang int // c-python api version (2,3)
}

func (g *cpyGen) gen() error {
Expand Down
20 changes: 14 additions & 6 deletions bind/gengo.go
Expand Up @@ -43,7 +43,7 @@ func _cgopy_CheckGoVersion() {
}
`
goPreamble = `// Package main is an autogenerated binder stub for package %[1]s.
// gopy gen -lang=go %[1]s
// gopy gen %[1]s
//
// File is generated by gopy gen. Do not edit.
package main
Expand Down Expand Up @@ -182,7 +182,8 @@ type goGen struct {

fset *token.FileSet
pkg *Package
lang int // python's version API (2 or 3)
vm string // python interpreter used to generate the bindings
lang int // python's version API (2 or 3)
err ErrorList
capi string
}
Expand Down Expand Up @@ -1125,14 +1126,21 @@ func (g *goGen) genPreamble() {
pkgimport = fmt.Sprintf("_ %q", g.pkg.pkg.Path())
}

var pkgcfg string
var err error
var (
pkgcfg string
err error
)

if g.capi == "cpython" {
pkgcfg, err = getPkgConfig(g.lang)
var pycfg pyconfig
pycfg, err = getPythonConfig(g.vm)
if err != nil {
panic(err)
}
pkgcfg = "//#cgo pkg-config: " + pkgcfg
pkgcfg = fmt.Sprintf(`//
//#cgo CFLAGS: %s
//#cgo LDFLAGS: %s
//`, pycfg.cflags, pycfg.ldflags)
}

version := runtime.Version()
Expand Down
116 changes: 63 additions & 53 deletions bind/utils.go
Expand Up @@ -5,16 +5,17 @@
package bind

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"go/types"
"io"
"os"
"os/exec"
"regexp"
"sort"
"strconv"
"strings"

"github.com/pkg/errors"
)

func isErrorType(typ types.Type) bool {
Expand Down Expand Up @@ -83,68 +84,77 @@ func isConstructor(sig *types.Signature) bool {
return false
}

// getPkgConfig returns the name of the pkg-config python's pc file
func getPkgConfig(vers int) (string, error) {
bin, err := exec.LookPath("pkg-config")
type pyconfig struct {
version int
cflags string
ldflags string
}

// getPythonConfig returns the needed python configuration for the given
// python VM (python, python2, python3, pypy, etc...)
func getPythonConfig(vm string) (pyconfig, error) {
code := `import sys
import distutils.sysconfig as ds
import json
print(json.dumps({
"version": sys.version_info.major,
"incdir": ds.get_python_inc(),
"libdir": ds.get_config_var("LIBDIR"),
"libpy": ds.get_config_var("LIBRARY"),
"shlibs": ds.get_config_var("SHLIBS"),
"syslibs": ds.get_config_var("SYSLIBS"),
"shlinks": ds.get_config_var("LINKFORSHARED"),
}))
`

var cfg pyconfig
bin, err := exec.LookPath(vm)
if err != nil {
return "", fmt.Errorf(
"gopy: could not locate 'pkg-config' executable (err: %v)",
err,
)
return cfg, errors.Wrapf(err, "could not locate python vm %q", vm)
}

out, err := exec.Command(bin, "--list-all").Output()
buf := new(bytes.Buffer)
cmd := exec.Command(bin, "-c", code)
cmd.Stdin = os.Stdin
cmd.Stdout = buf
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return "", fmt.Errorf(
"gopy: error retrieving the list of packages known to pkg-config (err: %v)",
err,
)
return cfg, errors.Wrap(err, "could not run python-config script")
}

pkgs := []string{}
re := regexp.MustCompile(fmt.Sprintf(`^python(\s|-|\.|)%d.*?`, vers))
s := bufio.NewScanner(bytes.NewReader(out))
for s.Scan() {
err = s.Err()
if err != nil {
if err == io.EOF {
err = nil
}
break
}

line := s.Bytes()
if !bytes.HasPrefix(line, []byte("python")) {
continue
}

if !re.Match(line) {
continue
}

pkg := bytes.Split(line, []byte(" "))
pkgs = append(pkgs, string(pkg[0]))
var raw struct {
Version int `json:"version"`
IncDir string `json:"incdir"`
LibDir string `json:"libdir"`
LibPy string `json:"libpy"`
ShLibs string `json:"shlibs"`
SysLibs string `json:"syslibs"`
}

err = json.NewDecoder(buf).Decode(&raw)
if err != nil {
return "", fmt.Errorf(
"gopy: error scanning pkg-config output (err: %v)",
err,
)
return cfg, errors.Wrapf(err, "could not decode JSON script output")
}

if len(pkgs) <= 0 {
return "", fmt.Errorf(
"gopy: could not find pkg-config file (no python.pc installed?)",
)
if strings.HasSuffix(raw.LibPy, ".a") {
raw.LibPy = raw.LibPy[:len(raw.LibPy)-len(".a")]
}
if strings.HasPrefix(raw.LibPy, "lib") {
raw.LibPy = raw.LibPy[len("lib"):]
}

sort.Strings(pkgs)

// FIXME(sbinet): make sure we take the latest version?
pkgcfg := pkgs[0]

return pkgcfg, nil
cfg.version = raw.Version
cfg.cflags = strings.Join([]string{
"-I" + raw.IncDir,
}, " ")
cfg.ldflags = strings.Join([]string{
"-L" + raw.LibDir,
"-l" + raw.LibPy,
raw.ShLibs,
raw.SysLibs,
}, " ")

return cfg, nil
}

func getGoVersion(version string) (int64, int64, error) {
Expand Down
26 changes: 26 additions & 0 deletions bind/utils_test.go
Expand Up @@ -69,3 +69,29 @@ func TestExtractPythonName(t *testing.T) {
}
}
}

func TestPythonConfig(t *testing.T) {
t.Skip()

for _, tc := range []struct {
vm string
want pyconfig
}{
{
vm: "python2",
},
{
vm: "python3",
},
} {
t.Run(tc.vm, func(t *testing.T) {
cfg, err := getPythonConfig(tc.vm)
if err != nil {
t.Fatal(err)
}
if cfg != tc.want {
t.Fatalf("error:\ngot= %#v\nwant=%#v\n", cfg, tc.want)
}
})
}
}

0 comments on commit 1f9f33f

Please sign in to comment.