From 90f5fcd36e4a3e0a4ae05c053b56e40765b7d85e Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Mon, 16 Nov 2015 13:00:54 +0100 Subject: [PATCH] gopy: load from export data, not source This CL, inspired from https://golang.org/cl/16911, drops the use of `x/tools/go/loader` (importing from sources) and replaces it with the stdlib `go/importer`, working off the (binary) exported data under $GOPATH/pkg/pkgname.a. This allows us: - to rely completely on the stdlib-1.5, - to support cgo-based packages (which are not supported by `x/tools/go/loader`) Fixes #28 Fixes #36 Fixes #68 Fixes #70 Change-Id: I0321645dfeec8738687a675a7e80283053c89123 --- _examples/cgo/cgo.go | 42 +++++++++++++++++++++++++++++++++ _examples/cgo/test.py | 12 ++++++++++ gen.go | 55 +++++++++++++++++++++++++------------------ main_test.go | 11 +++++++++ 4 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 _examples/cgo/cgo.go create mode 100644 _examples/cgo/test.py diff --git a/_examples/cgo/cgo.go b/_examples/cgo/cgo.go new file mode 100644 index 0000000..98a64a3 --- /dev/null +++ b/_examples/cgo/cgo.go @@ -0,0 +1,42 @@ +// 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 cgo tests bindings of CGo-based packages. +package cgo + +//#include +//#include +//#include +//const char* cpkg_sprintf(const char *str) { +// char *o = (char*)malloc(strlen(str)); +// sprintf(o, "%s", str); +// return o; +//} +import "C" + +import ( + "fmt" + "unsafe" +) + +// Hi returns a string from Go (via C's stdio) +func Hi() string { + cstr := C.CString("hi from go\n") + defer C.free(unsafe.Pointer(cstr)) + cout := C.cpkg_sprintf(cstr) + defer C.free(unsafe.Pointer(cout)) + return C.GoString(cout) +} + +// Hello returns a string via C's stdio +func Hello(s string) string { + if s == "" { + s = "you" + } + cstr := C.CString(fmt.Sprintf("hello %s from go\n", s)) + defer C.free(unsafe.Pointer(cstr)) + cout := C.cpkg_sprintf(cstr) + defer C.free(unsafe.Pointer(cout)) + return C.GoString(cout) +} diff --git a/_examples/cgo/test.py b/_examples/cgo/test.py new file mode 100644 index 0000000..2204c6b --- /dev/null +++ b/_examples/cgo/test.py @@ -0,0 +1,12 @@ +# 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. + +## py2/py3 compat +from __future__ import print_function + +import cgo + +print("cgo.doc: %r" % (cgo.__doc__,)) +print("cgo.Hi()= %r" % (cgo.Hi(),)) +print("cgo.Hello(you)= %r" % (cgo.Hello("you"),)) diff --git a/gen.go b/gen.go index d3e45c1..f961ede 100644 --- a/gen.go +++ b/gen.go @@ -9,6 +9,7 @@ import ( "go/ast" "go/build" "go/doc" + "go/importer" "go/parser" "go/scanner" "go/token" @@ -20,7 +21,6 @@ import ( "path/filepath" "github.com/go-python/gopy/bind" - "golang.org/x/tools/go/loader" ) var ( @@ -160,24 +160,40 @@ func newPackage(path string) (*bind.Package, error) { return nil, err } - pkg, err := build.Import(path, cwd, 0) - pkgfiles := make([]string, 0, len(pkg.GoFiles)+len(pkg.CgoFiles)) - pkgfiles = append(pkgfiles, pkg.GoFiles...) - pkgfiles = append(pkgfiles, pkg.CgoFiles...) - files, err := parseFiles(pkg.Dir, pkgfiles) + cmd := exec.Command("go", "install", path) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = cwd + + err = cmd.Run() if err != nil { + log.Printf("error installing [%s]: %v\n", + path, + err, + ) return nil, err } - conf := loader.Config{ - Fset: fset, - } - conf.TypeChecker.Error = func(e error) { - log.Printf("%v\n", e) - err = e + bpkg, err := build.Import(path, cwd, 0) + if err != nil { + log.Printf("error resolving import path [%s]: %v\n", + path, + err, + ) + return nil, err } - p, err := newPackageFrom(files, &conf, pkg) + pkg, err := importer.Default().Import(bpkg.ImportPath) + if err != nil { + log.Printf("error importing package [%v]: %v\n", + bpkg.ImportPath, + err, + ) + return nil, err + } + + p, err := newPackageFrom(bpkg, pkg) if err != nil { log.Printf("%v\n", err) return nil, err @@ -186,17 +202,10 @@ func newPackage(path string) (*bind.Package, error) { return p, err } -func newPackageFrom(files []*ast.File, conf *loader.Config, pkg *build.Package) (*bind.Package, error) { - - conf.CreateFromFiles(pkg.ImportPath, files...) - program, err := conf.Load() - if err != nil { - return nil, err - } - p := program.Created[0].Pkg +func newPackageFrom(bpkg *build.Package, p *types.Package) (*bind.Package, error) { var pkgast *ast.Package - pkgs, err := parser.ParseDir(fset, pkg.Dir, nil, parser.ParseComments) + pkgs, err := parser.ParseDir(fset, bpkg.Dir, nil, parser.ParseComments) if err != nil { return nil, err } @@ -205,7 +214,7 @@ func newPackageFrom(files []*ast.File, conf *loader.Config, pkg *build.Package) return nil, fmt.Errorf("gopy: could not find AST for package %q", p.Name()) } - pkgdoc := doc.New(pkgast, pkg.ImportPath, 0) + pkgdoc := doc.New(pkgast, bpkg.ImportPath, 0) return bind.NewPackage(p, pkgdoc) } diff --git a/main_test.go b/main_test.go index cca0543..0fca93c 100644 --- a/main_test.go +++ b/main_test.go @@ -453,3 +453,14 @@ func TestBindInterfaces(t *testing.T) { `), }) } + +func TestBindCgoPackage(t *testing.T) { + t.Parallel() + testPkg(t, pkg{ + path: "_examples/cgo", + want: []byte(`cgo.doc: 'Package cgo tests bindings of CGo-based packages.\n' +cgo.Hi()= 'hi from go\n' +cgo.Hello(you)= 'hello you from go\n' +`), + }) +}