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