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 |
Members