cgo.go raw

   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  	"log"
  61  	"os"
  62  	"os/exec"
  63  	"path/filepath"
  64  	"regexp"
  65  	"strings"
  66  )
  67  
  68  // ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses
  69  // the output and returns the resulting ASTs.
  70  func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
  71  	tmpdir, err := os.MkdirTemp("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
  72  	if err != nil {
  73  		return nil, err
  74  	}
  75  	defer os.RemoveAll(tmpdir)
  76  
  77  	pkgdir := bp.Dir
  78  	if DisplayPath != nil {
  79  		pkgdir = DisplayPath(pkgdir)
  80  	}
  81  
  82  	cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false)
  83  	if err != nil {
  84  		return nil, err
  85  	}
  86  	var files []*ast.File
  87  	for i := range cgoFiles {
  88  		rd, err := os.Open(cgoFiles[i])
  89  		if err != nil {
  90  			return nil, err
  91  		}
  92  		display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
  93  		f, err := parser.ParseFile(fset, display, rd, mode)
  94  		rd.Close()
  95  		if err != nil {
  96  			return nil, err
  97  		}
  98  		files = append(files, f)
  99  	}
 100  	return files, nil
 101  }
 102  
 103  var cgoRe = regexp.MustCompile(`[/\\:]`)
 104  
 105  // Run invokes the cgo preprocessor on bp.CgoFiles and returns two
 106  // lists of files: the resulting processed files (in temporary
 107  // directory tmpdir) and the corresponding names of the unprocessed files.
 108  //
 109  // Run is adapted from (*builder).cgo in
 110  // $GOROOT/src/cmd/go/build.go, but these features are unsupported:
 111  // Objective C, CGOPKGPATH, CGO_FLAGS.
 112  //
 113  // If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in
 114  // to the cgo preprocessor. This in turn will set the // line comments
 115  // referring to those files to use absolute paths. This is needed for
 116  // go/packages using the legacy go list support so it is able to find
 117  // the original files.
 118  func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) {
 119  	cgoCPPFLAGS, _, _, _ := cflags(bp, true)
 120  	_, cgoexeCFLAGS, _, _ := cflags(bp, false)
 121  
 122  	if len(bp.CgoPkgConfig) > 0 {
 123  		pcCFLAGS, err := pkgConfigFlags(bp)
 124  		if err != nil {
 125  			return nil, nil, err
 126  		}
 127  		cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
 128  	}
 129  
 130  	// Allows including _cgo_export.h from .[ch] files in the package.
 131  	cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
 132  
 133  	// _cgo_gotypes.go (displayed "C") contains the type definitions.
 134  	files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
 135  	displayFiles = append(displayFiles, "C")
 136  	for _, fn := range bp.CgoFiles {
 137  		// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
 138  		f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
 139  		files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
 140  		displayFiles = append(displayFiles, fn)
 141  	}
 142  
 143  	var cgoflags []string
 144  	if bp.Goroot && bp.ImportPath == "runtime/cgo" {
 145  		cgoflags = append(cgoflags, "-import_runtime_cgo=false")
 146  	}
 147  	if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
 148  		cgoflags = append(cgoflags, "-import_syscall=false")
 149  	}
 150  
 151  	var cgoFiles []string = bp.CgoFiles
 152  	if useabs {
 153  		cgoFiles = make([]string, len(bp.CgoFiles))
 154  		for i := range cgoFiles {
 155  			cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i])
 156  		}
 157  	}
 158  
 159  	args := stringList(
 160  		"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
 161  		cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles,
 162  	)
 163  	if false {
 164  		log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
 165  	}
 166  	cmd := exec.Command(args[0], args[1:]...)
 167  	cmd.Dir = pkgdir
 168  	cmd.Env = append(os.Environ(), "PWD="+pkgdir)
 169  	cmd.Stdout = os.Stderr
 170  	cmd.Stderr = os.Stderr
 171  	if err := cmd.Run(); err != nil {
 172  		return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
 173  	}
 174  
 175  	return files, displayFiles, nil
 176  }
 177  
 178  // -- unmodified from 'go build' ---------------------------------------
 179  
 180  // Return the flags to use when invoking the C or C++ compilers, or cgo.
 181  func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
 182  	var defaults string
 183  	if def {
 184  		defaults = "-g -O2"
 185  	}
 186  
 187  	cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
 188  	cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
 189  	cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
 190  	ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
 191  	return
 192  }
 193  
 194  // envList returns the value of the given environment variable broken
 195  // into fields, using the default value when the variable is empty.
 196  func envList(key, def string) []string {
 197  	v := os.Getenv(key)
 198  	if v == "" {
 199  		v = def
 200  	}
 201  	return strings.Fields(v)
 202  }
 203  
 204  // stringList's arguments should be a sequence of string or []string values.
 205  // stringList flattens them into a single []string.
 206  func stringList(args ...any) []string {
 207  	var x []string
 208  	for _, arg := range args {
 209  		switch arg := arg.(type) {
 210  		case []string:
 211  			x = append(x, arg...)
 212  		case string:
 213  			x = append(x, arg)
 214  		default:
 215  			panic("stringList: invalid argument")
 216  		}
 217  	}
 218  	return x
 219  }
 220