github.com/v2fly/tools@v0.100.0/go/internal/cgo/cgo.go (about)

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