Skip to content

Commit 1f9f33f

Browse files
committed
all: split unix/windows support
1 parent cbb7913 commit 1f9f33f

23 files changed

+521
-346
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ ex:
5555
$ gopy gen github.com/go-python/gopy/_examples/hi
5656

5757
Options:
58-
-lang="py2": target language for bindings
58+
-api="cpython": bindings API to use (cpython, cffi)
5959
-output="": output directory for bindings
60-
60+
-vm="python": path to python interpreter
6161

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

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

7679

appveyor.yml

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ build_script:
2727
- "%CPYTHON3DIR%\\python -m pip install --upgrade pip"
2828
- "%CPYTHON2DIR%\\python -m pip install cffi"
2929
- "%CPYTHON3DIR%\\python -m pip install cffi"
30-
# - go get -v -t -tags purego ./... ## FIXME(sbinet)
30+
- go get -v -t ./...
3131

3232
test_script:
33-
- go env
34-
# - go test -tags purego ./... ## FIXME(sbinet)
33+
- go test ./...

bind/bind.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ func (list ErrorList) Error() string {
2727
}
2828

2929
// GenCPython generates a (C)Python package from a Go package
30-
func GenCPython(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error {
30+
func GenCPython(w io.Writer, fset *token.FileSet, pkg *Package, vm string, lang int) error {
3131
gen := &cpyGen{
3232
decl: &printer{buf: new(bytes.Buffer), indentEach: []byte("\t")},
3333
impl: &printer{buf: new(bytes.Buffer), indentEach: []byte("\t")},
3434
fset: fset,
3535
pkg: pkg,
36+
vm: vm,
3637
lang: lang,
3738
}
3839
err := gen.gen()
@@ -60,11 +61,12 @@ func GenCPython(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error
6061
// GenCFFI generates a wrapper python script by 2 steps.
6162
// First, GenCFFI analyzes which interfaces should be exposed from the Go package.
6263
// Then, GenCFFI writes a wrapper python script.
63-
func GenCFFI(w io.Writer, fset *token.FileSet, pkg *Package, lang int) error {
64+
func GenCFFI(w io.Writer, fset *token.FileSet, pkg *Package, vm string, lang int) error {
6465
gen := &cffiGen{
6566
wrapper: &printer{buf: new(bytes.Buffer), indentEach: []byte(" ")},
6667
fset: fset,
6768
pkg: pkg,
69+
vm: vm,
6870
lang: lang,
6971
}
7072

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

8486
// GenGo generates a cgo package from a Go package
85-
func GenGo(w io.Writer, fset *token.FileSet, pkg *Package, lang int, capi string) error {
87+
func GenGo(w io.Writer, fset *token.FileSet, pkg *Package, lang int, vm, capi string) error {
8688
buf := new(bytes.Buffer)
8789
gen := &goGen{
8890
printer: &printer{buf: buf, indentEach: []byte("\t")},
8991
fset: fset,
9092
pkg: pkg,
93+
vm: vm,
9194
lang: lang,
9295
capi: capi,
9396
}

bind/bind_unix.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2019 The go-python Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build !windows
6+
7+
package bind
8+
9+
const (
10+
shlibExt = ".so"
11+
)

bind/bind_windows.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2019 The go-python Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build windows
6+
7+
package bind
8+
9+
const (
10+
shlibExt = ".dll"
11+
)

bind/gencffi.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extern void cgo_pkg_%[2]s_init();
6666
class _cffi_helper(object):
6767
6868
here = os.path.dirname(os.path.abspath(__file__))
69-
lib = ffi.dlopen(os.path.join(here, "_%[1]s.so"))
69+
lib = ffi.dlopen(os.path.join(here, "_%[1]s%[2]s"))
7070
7171
@staticmethod
7272
def cffi_cgopy_cnv_py2c_bool(o):
@@ -227,7 +227,8 @@ type cffiGen struct {
227227
pkg *Package
228228
err ErrorList
229229

230-
lang int // c-python api version (2,3)
230+
vm string // python interpreter
231+
lang int // c-python api version (2,3)
231232
}
232233

233234
func (g *cffiGen) gen() error {
@@ -306,7 +307,7 @@ func (g *cffiGen) genCffiCdef() {
306307

307308
func (g *cffiGen) genWrappedPy() {
308309
n := g.pkg.pkg.Name()
309-
g.wrapper.Printf(cffiHelperPreamble, n)
310+
g.wrapper.Printf(cffiHelperPreamble, n, shlibExt)
310311
g.wrapper.Indent()
311312

312313
// first, process slices, arrays

bind/gencpy.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
const (
1313
cPreamble = `/*
1414
C stubs for package %[1]s.
15-
gopy gen -lang=python %[1]s
15+
gopy gen -api=cpython %[1]s
1616
1717
File is generated by gopy gen. Do not edit.
1818
*/
@@ -180,7 +180,8 @@ type cpyGen struct {
180180
pkg *Package
181181
err ErrorList
182182

183-
lang int // c-python api version (2,3)
183+
vm string // python interpreter
184+
lang int // c-python api version (2,3)
184185
}
185186

186187
func (g *cpyGen) gen() error {

bind/gengo.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func _cgopy_CheckGoVersion() {
4343
}
4444
`
4545
goPreamble = `// Package main is an autogenerated binder stub for package %[1]s.
46-
// gopy gen -lang=go %[1]s
46+
// gopy gen %[1]s
4747
//
4848
// File is generated by gopy gen. Do not edit.
4949
package main
@@ -182,7 +182,8 @@ type goGen struct {
182182

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

1128-
var pkgcfg string
1129-
var err error
1129+
var (
1130+
pkgcfg string
1131+
err error
1132+
)
1133+
11301134
if g.capi == "cpython" {
1131-
pkgcfg, err = getPkgConfig(g.lang)
1135+
var pycfg pyconfig
1136+
pycfg, err = getPythonConfig(g.vm)
11321137
if err != nil {
11331138
panic(err)
11341139
}
1135-
pkgcfg = "//#cgo pkg-config: " + pkgcfg
1140+
pkgcfg = fmt.Sprintf(`//
1141+
//#cgo CFLAGS: %s
1142+
//#cgo LDFLAGS: %s
1143+
//`, pycfg.cflags, pycfg.ldflags)
11361144
}
11371145

11381146
version := runtime.Version()

bind/utils.go

+63-53
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@
55
package bind
66

77
import (
8-
"bufio"
98
"bytes"
9+
"encoding/json"
1010
"fmt"
1111
"go/types"
12-
"io"
12+
"os"
1313
"os/exec"
1414
"regexp"
15-
"sort"
1615
"strconv"
1716
"strings"
17+
18+
"github.com/pkg/errors"
1819
)
1920

2021
func isErrorType(typ types.Type) bool {
@@ -83,68 +84,77 @@ func isConstructor(sig *types.Signature) bool {
8384
return false
8485
}
8586

86-
// getPkgConfig returns the name of the pkg-config python's pc file
87-
func getPkgConfig(vers int) (string, error) {
88-
bin, err := exec.LookPath("pkg-config")
87+
type pyconfig struct {
88+
version int
89+
cflags string
90+
ldflags string
91+
}
92+
93+
// getPythonConfig returns the needed python configuration for the given
94+
// python VM (python, python2, python3, pypy, etc...)
95+
func getPythonConfig(vm string) (pyconfig, error) {
96+
code := `import sys
97+
import distutils.sysconfig as ds
98+
import json
99+
print(json.dumps({
100+
"version": sys.version_info.major,
101+
"incdir": ds.get_python_inc(),
102+
"libdir": ds.get_config_var("LIBDIR"),
103+
"libpy": ds.get_config_var("LIBRARY"),
104+
"shlibs": ds.get_config_var("SHLIBS"),
105+
"syslibs": ds.get_config_var("SYSLIBS"),
106+
"shlinks": ds.get_config_var("LINKFORSHARED"),
107+
}))
108+
`
109+
110+
var cfg pyconfig
111+
bin, err := exec.LookPath(vm)
89112
if err != nil {
90-
return "", fmt.Errorf(
91-
"gopy: could not locate 'pkg-config' executable (err: %v)",
92-
err,
93-
)
113+
return cfg, errors.Wrapf(err, "could not locate python vm %q", vm)
94114
}
95115

96-
out, err := exec.Command(bin, "--list-all").Output()
116+
buf := new(bytes.Buffer)
117+
cmd := exec.Command(bin, "-c", code)
118+
cmd.Stdin = os.Stdin
119+
cmd.Stdout = buf
120+
cmd.Stderr = os.Stderr
121+
err = cmd.Run()
97122
if err != nil {
98-
return "", fmt.Errorf(
99-
"gopy: error retrieving the list of packages known to pkg-config (err: %v)",
100-
err,
101-
)
123+
return cfg, errors.Wrap(err, "could not run python-config script")
102124
}
103125

104-
pkgs := []string{}
105-
re := regexp.MustCompile(fmt.Sprintf(`^python(\s|-|\.|)%d.*?`, vers))
106-
s := bufio.NewScanner(bytes.NewReader(out))
107-
for s.Scan() {
108-
err = s.Err()
109-
if err != nil {
110-
if err == io.EOF {
111-
err = nil
112-
}
113-
break
114-
}
115-
116-
line := s.Bytes()
117-
if !bytes.HasPrefix(line, []byte("python")) {
118-
continue
119-
}
120-
121-
if !re.Match(line) {
122-
continue
123-
}
124-
125-
pkg := bytes.Split(line, []byte(" "))
126-
pkgs = append(pkgs, string(pkg[0]))
126+
var raw struct {
127+
Version int `json:"version"`
128+
IncDir string `json:"incdir"`
129+
LibDir string `json:"libdir"`
130+
LibPy string `json:"libpy"`
131+
ShLibs string `json:"shlibs"`
132+
SysLibs string `json:"syslibs"`
127133
}
128-
134+
err = json.NewDecoder(buf).Decode(&raw)
129135
if err != nil {
130-
return "", fmt.Errorf(
131-
"gopy: error scanning pkg-config output (err: %v)",
132-
err,
133-
)
136+
return cfg, errors.Wrapf(err, "could not decode JSON script output")
134137
}
135138

136-
if len(pkgs) <= 0 {
137-
return "", fmt.Errorf(
138-
"gopy: could not find pkg-config file (no python.pc installed?)",
139-
)
139+
if strings.HasSuffix(raw.LibPy, ".a") {
140+
raw.LibPy = raw.LibPy[:len(raw.LibPy)-len(".a")]
141+
}
142+
if strings.HasPrefix(raw.LibPy, "lib") {
143+
raw.LibPy = raw.LibPy[len("lib"):]
140144
}
141145

142-
sort.Strings(pkgs)
143-
144-
// FIXME(sbinet): make sure we take the latest version?
145-
pkgcfg := pkgs[0]
146-
147-
return pkgcfg, nil
146+
cfg.version = raw.Version
147+
cfg.cflags = strings.Join([]string{
148+
"-I" + raw.IncDir,
149+
}, " ")
150+
cfg.ldflags = strings.Join([]string{
151+
"-L" + raw.LibDir,
152+
"-l" + raw.LibPy,
153+
raw.ShLibs,
154+
raw.SysLibs,
155+
}, " ")
156+
157+
return cfg, nil
148158
}
149159

150160
func getGoVersion(version string) (int64, int64, error) {

bind/utils_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,29 @@ func TestExtractPythonName(t *testing.T) {
6969
}
7070
}
7171
}
72+
73+
func TestPythonConfig(t *testing.T) {
74+
t.Skip()
75+
76+
for _, tc := range []struct {
77+
vm string
78+
want pyconfig
79+
}{
80+
{
81+
vm: "python2",
82+
},
83+
{
84+
vm: "python3",
85+
},
86+
} {
87+
t.Run(tc.vm, func(t *testing.T) {
88+
cfg, err := getPythonConfig(tc.vm)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
if cfg != tc.want {
93+
t.Fatalf("error:\ngot= %#v\nwant=%#v\n", cfg, tc.want)
94+
}
95+
})
96+
}
97+
}

0 commit comments

Comments
 (0)