forked from go-python/gopy
/
utils.go
313 lines (282 loc) · 7.81 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// Copyright 2015 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 (
"bytes"
"encoding/json"
"fmt"
"go/types"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/pkg/errors"
)
func isErrorType(typ types.Type) bool {
return typ == types.Universe.Lookup("error").Type()
}
func isStringer(obj types.Object) bool {
switch obj := obj.(type) {
case *types.Func:
if obj.Name() != "String" {
return false
}
sig, ok := obj.Type().(*types.Signature)
if !ok {
return false
}
if sig.Recv() == nil {
return false
}
if sig.Params().Len() != 0 {
return false
}
res := sig.Results()
if res.Len() != 1 {
return false
}
ret := res.At(0).Type()
if ret != types.Universe.Lookup("string").Type() {
return false
}
return true
default:
return false
}
}
func hasError(sig *types.Signature) bool {
res := sig.Results()
if res == nil || res.Len() <= 0 {
return false
}
nerr := 0
for i := 0; i < res.Len(); i++ {
ret := res.At(i)
if isErrorType(ret.Type()) {
nerr++
}
}
switch {
case nerr == 0:
return false
case nerr == 1:
return true
default:
panic(fmt.Errorf(
"gopy: invalid number of comma-errors (%d)",
nerr,
))
}
}
func isConstructor(sig *types.Signature) bool {
//TODO(sbinet)
return false
}
type PyConfig struct {
Version int
CFlags string
LdFlags string
LdDynamicFlags string
ExtSuffix string
}
// AllFlags returns CFlags + " " + LdFlags
func (pc *PyConfig) AllFlags() string {
return strings.TrimSpace(pc.CFlags) + " " + strings.TrimSpace(pc.LdFlags)
}
// GetPythonConfig returns the needed python configuration for the given
// python VM (python, python2, python3, pypy, etc...)
func GetPythonConfig(vm string, linkLibpython bool) (PyConfig, error) {
code := `import sys
try:
import sysconfig as ds
def _get_python_inc():
return ds.get_path('include')
except ImportError:
import distutils.sysconfig as ds
_get_python_inc = ds.get_config_var
import json
import os
version=sys.version_info.major
def clear_ld_flags(s):
if s is None:
return ''
skip_first_word = s.split(' ', 1)[1] # skip compiler name
skip_bundle = skip_first_word.replace('-bundle', '') # cgo already passes -dynamiclib
return skip_bundle
if "GOPY_INCLUDE" in os.environ and "GOPY_LIBDIR" in os.environ and "GOPY_PYLIB" in os.environ:
print(json.dumps({
"version": version,
"minor": sys.version_info.minor,
"incdir": os.environ["GOPY_INCLUDE"],
"libdir": os.environ["GOPY_LIBDIR"],
"libpy": os.environ["GOPY_PYLIB"],
"shlibs": ds.get_config_var("SHLIBS"),
"syslibs": ds.get_config_var("SYSLIBS"),
"shlinks": ds.get_config_var("LINKFORSHARED"),
"shflags": clear_ld_flags(ds.get_config_var("LDSHARED")),
"extsuffix": ds.get_config_var("EXT_SUFFIX"),
}))
else:
print(json.dumps({
"version": sys.version_info.major,
"minor": sys.version_info.minor,
"incdir": _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"),
"shflags": clear_ld_flags(ds.get_config_var("LDSHARED")),
"extsuffix": ds.get_config_var("EXT_SUFFIX"),
}))
`
var cfg PyConfig
bin, err := exec.LookPath(vm)
if err != nil {
return cfg, errors.Wrapf(err, "could not locate python vm %q", vm)
}
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 cfg, errors.Wrap(err, "could not run python-config script")
}
var raw struct {
Version int `json:"version"`
Minor int `json:"minor"`
IncDir string `json:"incdir"`
LibDir string `json:"libdir"`
LibPy string `json:"libpy"`
ShLibs string `json:"shlibs"`
SysLibs string `json:"syslibs"`
ExtSuffix string `json:"extsuffix"`
ShFlags string `json:"shflags"`
}
err = json.NewDecoder(buf).Decode(&raw)
if err != nil {
return cfg, errors.Wrapf(err, "could not decode JSON script output")
}
raw.IncDir = filepath.ToSlash(raw.IncDir)
raw.LibDir = filepath.ToSlash(raw.LibDir)
// on windows these can be empty -- use include dir which is usu good
// replace suffix case insensitive 'include' with 'libs'
if raw.LibDir == "" && raw.IncDir != "" {
regexInc := regexp.MustCompile(`(?i)\binclude$`)
raw.LibDir = regexInc.ReplaceAllString(raw.IncDir, "libs")
fmt.Printf("no LibDir -- copy from IncDir: %s\n", raw.LibDir)
}
if raw.LibPy == "" {
raw.LibPy = fmt.Sprintf("python%d%d", raw.Version, raw.Minor)
fmt.Printf("no LibPy -- set to: %s\n", raw.LibPy)
}
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"):]
}
cfg.Version = raw.Version
cfg.ExtSuffix = raw.ExtSuffix
cfg.CFlags = strings.Join([]string{
"-I" + raw.IncDir,
}, " ")
ldflags := []string{
"-L" + raw.LibDir,
}
if linkLibpython {
ldflags = append(ldflags, "-l"+raw.LibPy)
}
ldflags = append(ldflags, raw.ShLibs)
ldflags = append(ldflags, raw.SysLibs)
cfg.LdFlags = strings.Join(ldflags, " ")
cfg.LdDynamicFlags = raw.ShFlags
return cfg, nil
}
func getGoVersion(version string) (int64, int64, error) {
version_regex := regexp.MustCompile(`^go((\d+)(\.(\d+))*)`)
match := version_regex.FindStringSubmatch(version)
if match == nil {
return -1, -1, fmt.Errorf("gopy: invalid Go version information: %q", version)
}
version_info := strings.Split(match[1], ".")
major, _ := strconv.ParseInt(version_info[0], 10, 0)
minor, _ := strconv.ParseInt(version_info[1], 10, 0)
return major, minor, nil
}
var (
rxValidPythonName = regexp.MustCompile(`^[\pL_][\pL_\pN]+$`)
)
func extractPythonName(gname, gdoc string) (string, string, error) {
const (
PythonName = "gopy:name "
NLPythonName = "\n" + PythonName
)
i := -1
var tag string
// Check for either a doc string that starts with our tag,
// or as the first token of a newline
if strings.HasPrefix(gdoc, PythonName) {
i = 0
tag = PythonName
} else {
i = strings.Index(gdoc, NLPythonName)
tag = NLPythonName
}
if i < 0 {
return gname, gdoc, nil
}
s := gdoc[i+len(tag):]
if end := strings.Index(s, "\n"); end > 0 {
if !isValidPythonName(s[:end]) {
return "", "", fmt.Errorf("gopy: invalid identifier: %s", s[:end])
}
return s[:end], gdoc[:i] + s[end:], nil
}
return gname, gdoc, nil
}
// extractPythonNameFieldTag parses a struct field tag and returns
// a new python name. If the tag is not defined then the original
// name is returned.
// If the tag name is specified but is an invalid python identifier,
// then an error is returned.
func extractPythonNameFieldTag(gname, tag string) (string, error) {
const tagKey = "gopy"
if tag == "" {
return gname, nil
}
tagVal := reflect.StructTag(tag).Get(tagKey)
if tagVal == "" {
return gname, nil
}
if !isValidPythonName(tagVal) {
return "", fmt.Errorf("gopy: invalid identifier for struct field tag: %s", tagVal)
}
return tagVal, nil
}
// isValidPythonName returns true if the string is a valid
// python identifier name
func isValidPythonName(name string) bool {
if name == "" {
return false
}
return rxValidPythonName.MatchString(name)
}
var (
rxMatchFirstCap = regexp.MustCompile("([A-Z])([A-Z][a-z])")
rxMatchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
)
// toSnakeCase converts the provided string to snake_case.
// Based on https://gist.github.com/stoewer/fbe273b711e6a06315d19552dd4d33e6
func toSnakeCase(input string) string {
output := rxMatchFirstCap.ReplaceAllString(input, "${1}_${2}")
output = rxMatchAllCap.ReplaceAllString(output, "${1}_${2}")
output = strings.ReplaceAll(output, "-", "_")
return strings.ToLower(output)
}