src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

cgo.go (7387B)


      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 }