You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

207 lines
6.7 KiB

  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. package loader
  5. // This file 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. import (
  52. "fmt"
  53. "go/ast"
  54. "go/build"
  55. "go/parser"
  56. "go/token"
  57. "io/ioutil"
  58. "log"
  59. "os"
  60. "os/exec"
  61. "path/filepath"
  62. "regexp"
  63. "strings"
  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. pkgdir := bp.Dir
  75. if DisplayPath != nil {
  76. pkgdir = DisplayPath(pkgdir)
  77. }
  78. cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir)
  79. if err != nil {
  80. return nil, err
  81. }
  82. var files []*ast.File
  83. for i := range cgoFiles {
  84. rd, err := os.Open(cgoFiles[i])
  85. if err != nil {
  86. return nil, err
  87. }
  88. display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
  89. f, err := parser.ParseFile(fset, display, rd, mode)
  90. rd.Close()
  91. if err != nil {
  92. return nil, err
  93. }
  94. files = append(files, f)
  95. }
  96. return files, nil
  97. }
  98. var cgoRe = regexp.MustCompile(`[/\\:]`)
  99. // runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
  100. // lists of files: the resulting processed files (in temporary
  101. // directory tmpdir) and the corresponding names of the unprocessed files.
  102. //
  103. // runCgo is adapted from (*builder).cgo in
  104. // $GOROOT/src/cmd/go/build.go, but these features are unsupported:
  105. // Objective C, CGOPKGPATH, CGO_FLAGS.
  106. //
  107. func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) {
  108. cgoCPPFLAGS, _, _, _ := cflags(bp, true)
  109. _, cgoexeCFLAGS, _, _ := cflags(bp, false)
  110. if len(bp.CgoPkgConfig) > 0 {
  111. pcCFLAGS, err := pkgConfigFlags(bp)
  112. if err != nil {
  113. return nil, nil, err
  114. }
  115. cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
  116. }
  117. // Allows including _cgo_export.h from .[ch] files in the package.
  118. cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
  119. // _cgo_gotypes.go (displayed "C") contains the type definitions.
  120. files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
  121. displayFiles = append(displayFiles, "C")
  122. for _, fn := range bp.CgoFiles {
  123. // "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
  124. f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
  125. files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
  126. displayFiles = append(displayFiles, fn)
  127. }
  128. var cgoflags []string
  129. if bp.Goroot && bp.ImportPath == "runtime/cgo" {
  130. cgoflags = append(cgoflags, "-import_runtime_cgo=false")
  131. }
  132. if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
  133. cgoflags = append(cgoflags, "-import_syscall=false")
  134. }
  135. args := stringList(
  136. "go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
  137. cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles,
  138. )
  139. if false {
  140. log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
  141. }
  142. cmd := exec.Command(args[0], args[1:]...)
  143. cmd.Dir = pkgdir
  144. cmd.Stdout = os.Stderr
  145. cmd.Stderr = os.Stderr
  146. if err := cmd.Run(); err != nil {
  147. return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
  148. }
  149. return files, displayFiles, nil
  150. }
  151. // -- unmodified from 'go build' ---------------------------------------
  152. // Return the flags to use when invoking the C or C++ compilers, or cgo.
  153. func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
  154. var defaults string
  155. if def {
  156. defaults = "-g -O2"
  157. }
  158. cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
  159. cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
  160. cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
  161. ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
  162. return
  163. }
  164. // envList returns the value of the given environment variable broken
  165. // into fields, using the default value when the variable is empty.
  166. func envList(key, def string) []string {
  167. v := os.Getenv(key)
  168. if v == "" {
  169. v = def
  170. }
  171. return strings.Fields(v)
  172. }
  173. // stringList's arguments should be a sequence of string or []string values.
  174. // stringList flattens them into a single []string.
  175. func stringList(args ...interface{}) []string {
  176. var x []string
  177. for _, arg := range args {
  178. switch arg := arg.(type) {
  179. case []string:
  180. x = append(x, arg...)
  181. case string:
  182. x = append(x, arg)
  183. default:
  184. panic("stringList: invalid argument")
  185. }
  186. }
  187. return x
  188. }