github.com/llvm-mirror/llgo@v0.0.0-20190322182713-bf6f0a60fce1/third_party/gotools/go/loader/cgo.go (about)

     1  package loader
     2  
     3  // This file handles cgo preprocessing of files containing `import "C"`.
     4  //
     5  // DESIGN
     6  //
     7  // The approach taken is to run the cgo processor on the package's
     8  // CgoFiles and parse the output, faking the filenames of the
     9  // resulting ASTs so that the synthetic file containing the C types is
    10  // called "C" (e.g. "~/go/src/net/C") and the preprocessed files
    11  // have their original names (e.g. "~/go/src/net/cgo_unix.go"),
    12  // not the names of the actual temporary files.
    13  //
    14  // The advantage of this approach is its fidelity to 'go build'.  The
    15  // downside is that the token.Position.Offset for each AST node is
    16  // incorrect, being an offset within the temporary file.  Line numbers
    17  // should still be correct because of the //line comments.
    18  //
    19  // The logic of this file is mostly plundered from the 'go build'
    20  // tool, which also invokes the cgo preprocessor.
    21  //
    22  //
    23  // REJECTED ALTERNATIVE
    24  //
    25  // An alternative approach that we explored is to extend go/types'
    26  // Importer mechanism to provide the identity of the importing package
    27  // so that each time `import "C"` appears it resolves to a different
    28  // synthetic package containing just the objects needed in that case.
    29  // The loader would invoke cgo but parse only the cgo_types.go file
    30  // defining the package-level objects, discarding the other files
    31  // resulting from preprocessing.
    32  //
    33  // The benefit of this approach would have been that source-level
    34  // syntax information would correspond exactly to the original cgo
    35  // file, with no preprocessing involved, making source tools like
    36  // godoc, oracle, and eg happy.  However, the approach was rejected
    37  // due to the additional complexity it would impose on go/types.  (It
    38  // made for a beautiful demo, though.)
    39  //
    40  // cgo files, despite their *.go extension, are not legal Go source
    41  // files per the specification since they may refer to unexported
    42  // members of package "C" such as C.int.  Also, a function such as
    43  // C.getpwent has in effect two types, one matching its C type and one
    44  // which additionally returns (errno C.int).  The cgo preprocessor
    45  // uses name mangling to distinguish these two functions in the
    46  // processed code, but go/types would need to duplicate this logic in
    47  // its handling of function calls, analogous to the treatment of map
    48  // lookups in which y=m[k] and y,ok=m[k] are both legal.
    49  
    50  import (
    51  	"fmt"
    52  	"go/ast"
    53  	"go/build"
    54  	"go/parser"
    55  	"go/token"
    56  	"io/ioutil"
    57  	"log"
    58  	"os"
    59  	"os/exec"
    60  	"path/filepath"
    61  	"regexp"
    62  	"strings"
    63  )
    64  
    65  // processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses
    66  // the output and returns the resulting ASTs.
    67  //
    68  func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
    69  	tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	defer os.RemoveAll(tmpdir)
    74  
    75  	pkgdir := bp.Dir
    76  	if DisplayPath != nil {
    77  		pkgdir = DisplayPath(pkgdir)
    78  	}
    79  
    80  	cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	var files []*ast.File
    85  	for i := range cgoFiles {
    86  		rd, err := os.Open(cgoFiles[i])
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		defer rd.Close()
    91  		display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
    92  		f, err := parser.ParseFile(fset, display, rd, mode)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		files = append(files, f)
    97  	}
    98  	return files, nil
    99  }
   100  
   101  var cgoRe = regexp.MustCompile(`[/\\:]`)
   102  
   103  // runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
   104  // lists of files: the resulting processed files (in temporary
   105  // directory tmpdir) and the corresponding names of the unprocessed files.
   106  //
   107  // runCgo is adapted from (*builder).cgo in
   108  // $GOROOT/src/cmd/go/build.go, but these features are unsupported:
   109  // pkg-config, Objective C, CGOPKGPATH, CGO_FLAGS.
   110  //
   111  func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) {
   112  	cgoCPPFLAGS, _, _, _ := cflags(bp, true)
   113  	_, cgoexeCFLAGS, _, _ := cflags(bp, false)
   114  
   115  	if len(bp.CgoPkgConfig) > 0 {
   116  		return nil, nil, fmt.Errorf("cgo pkg-config not supported")
   117  	}
   118  
   119  	// Allows including _cgo_export.h from .[ch] files in the package.
   120  	cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
   121  
   122  	// _cgo_gotypes.go (displayed "C") contains the type definitions.
   123  	files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
   124  	displayFiles = append(displayFiles, "C")
   125  	for _, fn := range bp.CgoFiles {
   126  		// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
   127  		f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
   128  		files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
   129  		displayFiles = append(displayFiles, fn)
   130  	}
   131  
   132  	var cgoflags []string
   133  	if bp.Goroot && bp.ImportPath == "runtime/cgo" {
   134  		cgoflags = append(cgoflags, "-import_runtime_cgo=false")
   135  	}
   136  	if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
   137  		cgoflags = append(cgoflags, "-import_syscall=false")
   138  	}
   139  
   140  	args := stringList(
   141  		"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
   142  		cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles,
   143  	)
   144  	if false {
   145  		log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
   146  	}
   147  	cmd := exec.Command(args[0], args[1:]...)
   148  	cmd.Dir = pkgdir
   149  	cmd.Stdout = os.Stderr
   150  	cmd.Stderr = os.Stderr
   151  	if err := cmd.Run(); err != nil {
   152  		return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
   153  	}
   154  
   155  	return files, displayFiles, nil
   156  }
   157  
   158  // -- unmodified from 'go build' ---------------------------------------
   159  
   160  // Return the flags to use when invoking the C or C++ compilers, or cgo.
   161  func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
   162  	var defaults string
   163  	if def {
   164  		defaults = "-g -O2"
   165  	}
   166  
   167  	cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
   168  	cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
   169  	cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
   170  	ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
   171  	return
   172  }
   173  
   174  // envList returns the value of the given environment variable broken
   175  // into fields, using the default value when the variable is empty.
   176  func envList(key, def string) []string {
   177  	v := os.Getenv(key)
   178  	if v == "" {
   179  		v = def
   180  	}
   181  	return strings.Fields(v)
   182  }
   183  
   184  // stringList's arguments should be a sequence of string or []string values.
   185  // stringList flattens them into a single []string.
   186  func stringList(args ...interface{}) []string {
   187  	var x []string
   188  	for _, arg := range args {
   189  		switch arg := arg.(type) {
   190  		case []string:
   191  			x = append(x, arg...)
   192  		case string:
   193  			x = append(x, arg)
   194  		default:
   195  			panic("stringList: invalid argument")
   196  		}
   197  	}
   198  	return x
   199  }