gopy/gen.go

211 lines
3.8 KiB
Go

// 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 main
import (
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/scanner"
"go/token"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/go-python/gopy/bind"
"golang.org/x/tools/go/loader"
)
var (
fset = token.NewFileSet()
)
func genPkg(odir string, p *bind.Package, lang string) error {
var err error
var o *os.File
switch lang {
case "python", "py":
lang, err = getPythonVersion()
if err != nil {
return err
}
default:
// no-op
}
pyvers := 2
switch lang {
case "python2", "py2":
pyvers = 2
case "python3", "py3":
pyvers = 3
}
if err != nil {
return err
}
switch lang {
case "python2", "py2":
o, err = os.Create(filepath.Join(odir, p.Name()+".c"))
if err != nil {
return err
}
defer o.Close()
err = bind.GenCPython(o, fset, p, 2)
if err != nil {
return err
}
case "python3", "py3":
return fmt.Errorf("gopy: python-3 support not yet implemented")
case "go":
o, err = os.Create(filepath.Join(odir, p.Name()+".go"))
if err != nil {
return err
}
defer o.Close()
err = bind.GenGo(o, fset, p, pyvers)
if err != nil {
return err
}
var tmpdir string
tmpdir, err = ioutil.TempDir("", "gopy-go-cgo-")
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
hdr := filepath.Join(odir, p.Name()+".h")
cmd := exec.Command(
"go", "tool", "cgo",
"-exportheader", hdr,
o.Name(),
)
cmd.Dir = tmpdir
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
default:
return fmt.Errorf("unknown target language: %q\n", lang)
}
if err != nil {
if list, _ := err.(bind.ErrorList); len(list) > 0 {
for _, err := range list {
log.Printf("%v\n", err)
}
} else {
log.Printf("%v\n", err)
}
}
if err != nil {
return err
}
err = o.Close()
if err != nil {
return err
}
return err
}
func parseFiles(dir string, fnames []string) ([]*ast.File, error) {
var (
files []*ast.File
err error
)
for _, fname := range fnames {
path := filepath.Join(dir, fname)
file, errf := parser.ParseFile(fset, path, nil, parser.AllErrors)
if errf != nil {
err = errf
if list, _ := err.(scanner.ErrorList); len(list) > 0 {
for _, err := range list {
log.Printf("%v\n", err)
}
} else {
log.Printf("%v\n", err)
}
}
files = append(files, file)
}
return files, err
}
func newPackage(path string) (*bind.Package, error) {
cwd, err := os.Getwd()
if err != nil {
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)
if err != nil {
return nil, err
}
conf := loader.Config{
Fset: fset,
}
conf.TypeChecker.Error = func(e error) {
log.Printf("%v\n", e)
err = e
}
p, err := newPackageFrom(files, &conf, pkg)
if err != nil {
log.Printf("%v\n", err)
return nil, err
}
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
var pkgast *ast.Package
pkgs, err := parser.ParseDir(fset, pkg.Dir, nil, parser.ParseComments)
if err != nil {
return nil, err
}
pkgast = pkgs[p.Name()]
if pkgast == nil {
return nil, fmt.Errorf("gopy: could not find AST for package %q", p.Name())
}
pkgdoc := doc.New(pkgast, pkg.ImportPath, 0)
return bind.NewPackage(p, pkgdoc)
}