GoPLS Viewer

Home|gopls/go/internal/cgo/cgo.go
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
52package cgo
53
54import (
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.
72func ProcessFiles(bp *build.Packagefset *token.FileSetDisplayPath func(path stringstringmode parser.Mode) ([]*ast.Fileerror) {
73    tmpdirerr := ioutil.TempDir(""strings.Replace(bp.ImportPath"/""_", -1)+"_C")
74    if err != nil {
75        return nilerr
76    }
77    defer os.RemoveAll(tmpdir)
78
79    pkgdir := bp.Dir
80    if DisplayPath != nil {
81        pkgdir = DisplayPath(pkgdir)
82    }
83
84    cgoFilescgoDisplayFileserr := Run(bppkgdirtmpdirfalse)
85    if err != nil {
86        return nilerr
87    }
88    var files []*ast.File
89    for i := range cgoFiles {
90        rderr := os.Open(cgoFiles[i])
91        if err != nil {
92            return nilerr
93        }
94        display := filepath.Join(bp.DircgoDisplayFiles[i])
95        ferr := parser.ParseFile(fsetdisplayrdmode)
96        rd.Close()
97        if err != nil {
98            return nilerr
99        }
100        files = append(filesf)
101    }
102    return filesnil
103}
104
105var 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.
120func Run(bp *build.Packagepkgdirtmpdir stringuseabs bool) (filesdisplayFiles []stringerr error) {
121    cgoCPPFLAGS___ := cflags(bptrue)
122    _cgoexeCFLAGS__ := cflags(bpfalse)
123
124    if len(bp.CgoPkgConfig) > 0 {
125        pcCFLAGSerr := pkgConfigFlags(bp)
126        if err != nil {
127            return nilnilerr
128        }
129        cgoCPPFLAGS = append(cgoCPPFLAGSpcCFLAGS...)
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(filesfilepath.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(filesfilepath.Join(tmpdirf+"cgo1.go"))
142        displayFiles = append(displayFilesfn)
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([]stringlen(bp.CgoFiles))
156        for i := range cgoFiles {
157            cgoFiles[i] = filepath.Join(pkgdirbp.CgoFiles[i])
158        }
159    }
160
161    args := stringList(
162        "go""tool""cgo""-objdir"tmpdircgoflags"--",
163        cgoCPPFLAGScgoexeCFLAGScgoFiles,
164    )
165    if false {
166        log.Printf("Running cgo for package %q: %s (dir=%s)"bp.ImportPathargspkgdir)
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 nilnilfmt.Errorf("cgo failed: %s: %s"argserr)
175    }
176
177    return filesdisplayFilesnil
178}
179
180// -- unmodified from 'go build' ---------------------------------------
181
182// Return the flags to use when invoking the C or C++ compilers, or cgo.
183func cflags(p *build.Packagedef bool) (cppflagscflagscxxflagsldflags []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.
198func envList(keydef 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.
208func stringList(args ...interface{}) []string {
209    var x []string
210    for _arg := range args {
211        switch arg := arg.(type) {
212        case []string:
213            x = append(xarg...)
214        case string:
215            x = append(xarg)
216        default:
217            panic("stringList: invalid argument")
218        }
219    }
220    return x
221}
222
MembersX
fmt
Run.cmd
ProcessFiles.DisplayPath
ProcessFiles.RangeStmt_3262.i
Run.displayFiles
Run._
Run
Run.cgoexeCFLAGS
Run.BlockStmt.err
stringList.x
Run.bp
Run.cgoFiles
cflags.p
Run.BlockStmt.pcCFLAGS
envList.def
os
regexp
ProcessFiles.tmpdir
Run.err
token
ProcessFiles.RangeStmt_3262.BlockStmt.rd
envList.v
cflags.def
envList
build
filepath
Run.pkgdir
Run.RangeStmt_4950.BlockStmt.f
log
Run.useabs
Run.cgoCPPFLAGS
ProcessFiles
ProcessFiles.RangeStmt_3262.BlockStmt.display
Run.files
ProcessFiles.bp
ProcessFiles.err
Run.RangeStmt_4950.fn
stringList
ast
ProcessFiles.cgoDisplayFiles
ProcessFiles.RangeStmt_3262.BlockStmt.err
cflags.ldflags
exec
cflags.cppflags
cflags.cflags
cflags.cxxflags
cflags.defaults
envList.key
stringList.args
strings
ProcessFiles.cgoFiles
Run.BlockStmt.RangeStmt_5593.i
Run.args
stringList.RangeStmt_7228.arg
parser
ProcessFiles.pkgdir
Run.tmpdir
cflags
ProcessFiles.RangeStmt_3262.BlockStmt.f
Run.cgoflags
ioutil
ProcessFiles.fset
ProcessFiles.mode
ProcessFiles.files
Members
X