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.

212 lines
5.7 KiB

  1. // Copyright 2014 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 buildutil
  5. import (
  6. "fmt"
  7. "go/ast"
  8. "go/build"
  9. "go/parser"
  10. "go/token"
  11. "io"
  12. "io/ioutil"
  13. "os"
  14. "path"
  15. "path/filepath"
  16. "strings"
  17. )
  18. // ParseFile behaves like parser.ParseFile,
  19. // but uses the build context's file system interface, if any.
  20. //
  21. // If file is not absolute (as defined by IsAbsPath), the (dir, file)
  22. // components are joined using JoinPath; dir must be absolute.
  23. //
  24. // The displayPath function, if provided, is used to transform the
  25. // filename that will be attached to the ASTs.
  26. //
  27. // TODO(adonovan): call this from go/loader.parseFiles when the tree thaws.
  28. //
  29. func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) {
  30. if !IsAbsPath(ctxt, file) {
  31. file = JoinPath(ctxt, dir, file)
  32. }
  33. rd, err := OpenFile(ctxt, file)
  34. if err != nil {
  35. return nil, err
  36. }
  37. defer rd.Close() // ignore error
  38. if displayPath != nil {
  39. file = displayPath(file)
  40. }
  41. return parser.ParseFile(fset, file, rd, mode)
  42. }
  43. // ContainingPackage returns the package containing filename.
  44. //
  45. // If filename is not absolute, it is interpreted relative to working directory dir.
  46. // All I/O is via the build context's file system interface, if any.
  47. //
  48. // The '...Files []string' fields of the resulting build.Package are not
  49. // populated (build.FindOnly mode).
  50. //
  51. func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
  52. if !IsAbsPath(ctxt, filename) {
  53. filename = JoinPath(ctxt, dir, filename)
  54. }
  55. // We must not assume the file tree uses
  56. // "/" always,
  57. // `\` always,
  58. // or os.PathSeparator (which varies by platform),
  59. // but to make any progress, we are forced to assume that
  60. // paths will not use `\` unless the PathSeparator
  61. // is also `\`, thus we can rely on filepath.ToSlash for some sanity.
  62. dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
  63. // We assume that no source root (GOPATH[i] or GOROOT) contains any other.
  64. for _, srcdir := range ctxt.SrcDirs() {
  65. srcdirSlash := filepath.ToSlash(srcdir) + "/"
  66. if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok {
  67. return ctxt.Import(importPath, dir, build.FindOnly)
  68. }
  69. }
  70. return nil, fmt.Errorf("can't find package containing %s", filename)
  71. }
  72. // -- Effective methods of file system interface -------------------------
  73. // (go/build.Context defines these as methods, but does not export them.)
  74. // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
  75. // the local file system to answer the question.
  76. func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) {
  77. if f := ctxt.HasSubdir; f != nil {
  78. return f(root, dir)
  79. }
  80. // Try using paths we received.
  81. if rel, ok = hasSubdir(root, dir); ok {
  82. return
  83. }
  84. // Try expanding symlinks and comparing
  85. // expanded against unexpanded and
  86. // expanded against expanded.
  87. rootSym, _ := filepath.EvalSymlinks(root)
  88. dirSym, _ := filepath.EvalSymlinks(dir)
  89. if rel, ok = hasSubdir(rootSym, dir); ok {
  90. return
  91. }
  92. if rel, ok = hasSubdir(root, dirSym); ok {
  93. return
  94. }
  95. return hasSubdir(rootSym, dirSym)
  96. }
  97. func hasSubdir(root, dir string) (rel string, ok bool) {
  98. const sep = string(filepath.Separator)
  99. root = filepath.Clean(root)
  100. if !strings.HasSuffix(root, sep) {
  101. root += sep
  102. }
  103. dir = filepath.Clean(dir)
  104. if !strings.HasPrefix(dir, root) {
  105. return "", false
  106. }
  107. return filepath.ToSlash(dir[len(root):]), true
  108. }
  109. // FileExists returns true if the specified file exists,
  110. // using the build context's file system interface.
  111. func FileExists(ctxt *build.Context, path string) bool {
  112. if ctxt.OpenFile != nil {
  113. r, err := ctxt.OpenFile(path)
  114. if err != nil {
  115. return false
  116. }
  117. r.Close() // ignore error
  118. return true
  119. }
  120. _, err := os.Stat(path)
  121. return err == nil
  122. }
  123. // OpenFile behaves like os.Open,
  124. // but uses the build context's file system interface, if any.
  125. func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
  126. if ctxt.OpenFile != nil {
  127. return ctxt.OpenFile(path)
  128. }
  129. return os.Open(path)
  130. }
  131. // IsAbsPath behaves like filepath.IsAbs,
  132. // but uses the build context's file system interface, if any.
  133. func IsAbsPath(ctxt *build.Context, path string) bool {
  134. if ctxt.IsAbsPath != nil {
  135. return ctxt.IsAbsPath(path)
  136. }
  137. return filepath.IsAbs(path)
  138. }
  139. // JoinPath behaves like filepath.Join,
  140. // but uses the build context's file system interface, if any.
  141. func JoinPath(ctxt *build.Context, path ...string) string {
  142. if ctxt.JoinPath != nil {
  143. return ctxt.JoinPath(path...)
  144. }
  145. return filepath.Join(path...)
  146. }
  147. // IsDir behaves like os.Stat plus IsDir,
  148. // but uses the build context's file system interface, if any.
  149. func IsDir(ctxt *build.Context, path string) bool {
  150. if ctxt.IsDir != nil {
  151. return ctxt.IsDir(path)
  152. }
  153. fi, err := os.Stat(path)
  154. return err == nil && fi.IsDir()
  155. }
  156. // ReadDir behaves like ioutil.ReadDir,
  157. // but uses the build context's file system interface, if any.
  158. func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
  159. if ctxt.ReadDir != nil {
  160. return ctxt.ReadDir(path)
  161. }
  162. return ioutil.ReadDir(path)
  163. }
  164. // SplitPathList behaves like filepath.SplitList,
  165. // but uses the build context's file system interface, if any.
  166. func SplitPathList(ctxt *build.Context, s string) []string {
  167. if ctxt.SplitPathList != nil {
  168. return ctxt.SplitPathList(s)
  169. }
  170. return filepath.SplitList(s)
  171. }
  172. // sameFile returns true if x and y have the same basename and denote
  173. // the same file.
  174. //
  175. func sameFile(x, y string) bool {
  176. if path.Clean(x) == path.Clean(y) {
  177. return true
  178. }
  179. if filepath.Base(x) == filepath.Base(y) { // (optimisation)
  180. if xi, err := os.Stat(x); err == nil {
  181. if yi, err := os.Stat(y); err == nil {
  182. return os.SameFile(xi, yi)
  183. }
  184. }
  185. }
  186. return false
  187. }