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 }