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