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