200 lines
6.5 KiB
Go
200 lines
6.5 KiB
Go
package loader
|
|
|
|
// This file handles cgo preprocessing of files containing `import "C"`.
|
|
//
|
|
// DESIGN
|
|
//
|
|
// The approach taken is to run the cgo processor on the package's
|
|
// CgoFiles and parse the output, faking the filenames of the
|
|
// resulting ASTs so that the synthetic file containing the C types is
|
|
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
|
|
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
|
|
// not the names of the actual temporary files.
|
|
//
|
|
// The advantage of this approach is its fidelity to 'go build'. The
|
|
// downside is that the token.Position.Offset for each AST node is
|
|
// incorrect, being an offset within the temporary file. Line numbers
|
|
// should still be correct because of the //line comments.
|
|
//
|
|
// The logic of this file is mostly plundered from the 'go build'
|
|
// tool, which also invokes the cgo preprocessor.
|
|
//
|
|
//
|
|
// REJECTED ALTERNATIVE
|
|
//
|
|
// An alternative approach that we explored is to extend go/types'
|
|
// Importer mechanism to provide the identity of the importing package
|
|
// so that each time `import "C"` appears it resolves to a different
|
|
// synthetic package containing just the objects needed in that case.
|
|
// The loader would invoke cgo but parse only the cgo_types.go file
|
|
// defining the package-level objects, discarding the other files
|
|
// resulting from preprocessing.
|
|
//
|
|
// The benefit of this approach would have been that source-level
|
|
// syntax information would correspond exactly to the original cgo
|
|
// file, with no preprocessing involved, making source tools like
|
|
// godoc, oracle, and eg happy. However, the approach was rejected
|
|
// due to the additional complexity it would impose on go/types. (It
|
|
// made for a beautiful demo, though.)
|
|
//
|
|
// cgo files, despite their *.go extension, are not legal Go source
|
|
// files per the specification since they may refer to unexported
|
|
// members of package "C" such as C.int. Also, a function such as
|
|
// C.getpwent has in effect two types, one matching its C type and one
|
|
// which additionally returns (errno C.int). The cgo preprocessor
|
|
// uses name mangling to distinguish these two functions in the
|
|
// processed code, but go/types would need to duplicate this logic in
|
|
// its handling of function calls, analogous to the treatment of map
|
|
// lookups in which y=m[k] and y,ok=m[k] are both legal.
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/build"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses
|
|
// the output and returns the resulting ASTs.
|
|
//
|
|
func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
|
|
tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
pkgdir := bp.Dir
|
|
if DisplayPath != nil {
|
|
pkgdir = DisplayPath(pkgdir)
|
|
}
|
|
|
|
cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var files []*ast.File
|
|
for i := range cgoFiles {
|
|
rd, err := os.Open(cgoFiles[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rd.Close()
|
|
display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
|
|
f, err := parser.ParseFile(fset, display, rd, mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
files = append(files, f)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
var cgoRe = regexp.MustCompile(`[/\\:]`)
|
|
|
|
// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
|
|
// lists of files: the resulting processed files (in temporary
|
|
// directory tmpdir) and the corresponding names of the unprocessed files.
|
|
//
|
|
// runCgo is adapted from (*builder).cgo in
|
|
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
|
|
// pkg-config, Objective C, CGOPKGPATH, CGO_FLAGS.
|
|
//
|
|
func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) {
|
|
cgoCPPFLAGS, _, _, _ := cflags(bp, true)
|
|
_, cgoexeCFLAGS, _, _ := cflags(bp, false)
|
|
|
|
if len(bp.CgoPkgConfig) > 0 {
|
|
return nil, nil, fmt.Errorf("cgo pkg-config not supported")
|
|
}
|
|
|
|
// Allows including _cgo_export.h from .[ch] files in the package.
|
|
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
|
|
|
|
// _cgo_gotypes.go (displayed "C") contains the type definitions.
|
|
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
|
|
displayFiles = append(displayFiles, "C")
|
|
for _, fn := range bp.CgoFiles {
|
|
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
|
|
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
|
|
files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
|
|
displayFiles = append(displayFiles, fn)
|
|
}
|
|
|
|
var cgoflags []string
|
|
if bp.Goroot && bp.ImportPath == "runtime/cgo" {
|
|
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
|
|
}
|
|
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
|
|
cgoflags = append(cgoflags, "-import_syscall=false")
|
|
}
|
|
|
|
args := stringList(
|
|
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
|
|
cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles,
|
|
)
|
|
if false {
|
|
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
|
|
}
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Dir = pkgdir
|
|
cmd.Stdout = os.Stderr
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
|
|
}
|
|
|
|
return files, displayFiles, nil
|
|
}
|
|
|
|
// -- unmodified from 'go build' ---------------------------------------
|
|
|
|
// Return the flags to use when invoking the C or C++ compilers, or cgo.
|
|
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
|
|
var defaults string
|
|
if def {
|
|
defaults = "-g -O2"
|
|
}
|
|
|
|
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
|
|
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
|
|
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
|
|
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
|
|
return
|
|
}
|
|
|
|
// envList returns the value of the given environment variable broken
|
|
// into fields, using the default value when the variable is empty.
|
|
func envList(key, def string) []string {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
v = def
|
|
}
|
|
return strings.Fields(v)
|
|
}
|
|
|
|
// stringList's arguments should be a sequence of string or []string values.
|
|
// stringList flattens them into a single []string.
|
|
func stringList(args ...interface{}) []string {
|
|
var x []string
|
|
for _, arg := range args {
|
|
switch arg := arg.(type) {
|
|
case []string:
|
|
x = append(x, arg...)
|
|
case string:
|
|
x = append(x, arg)
|
|
default:
|
|
panic("stringList: invalid argument")
|
|
}
|
|
}
|
|
return x
|
|
}
|