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