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