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.
21 // ParseFile behaves like parser.ParseFile,
22 // but uses the build context's file system interface, if any.
24 // If file is not absolute (as defined by IsAbsPath), the (dir, file)
25 // components are joined using JoinPath; dir must be absolute.
27 // The displayPath function, if provided, is used to transform the
28 // filename that will be attached to the ASTs.
30 // TODO(adonovan): call this from go/loader.parseFiles when the tree thaws.
32 func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) {
33 if !IsAbsPath(ctxt, file) {
34 file = JoinPath(ctxt, dir, file)
36 rd, err := OpenFile(ctxt, file)
40 defer rd.Close() // ignore error
41 if displayPath != nil {
42 file = displayPath(file)
44 return parser.ParseFile(fset, file, rd, mode)
47 // ContainingPackage returns the package containing filename.
49 // If filename is not absolute, it is interpreted relative to working directory dir.
50 // All I/O is via the build context's file system interface, if any.
52 // The '...Files []string' fields of the resulting build.Package are not
53 // populated (build.FindOnly mode).
55 func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
56 if !IsAbsPath(ctxt, filename) {
57 filename = JoinPath(ctxt, dir, filename)
60 // We must not assume the file tree uses
63 // or os.PathSeparator (which varies by platform),
64 // but to make any progress, we are forced to assume that
65 // paths will not use `\` unless the PathSeparator
66 // is also `\`, thus we can rely on filepath.ToSlash for some sanity.
68 dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
70 // We assume that no source root (GOPATH[i] or GOROOT) contains any other.
71 for _, srcdir := range ctxt.SrcDirs() {
72 srcdirSlash := filepath.ToSlash(srcdir) + "/"
73 if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok {
74 return ctxt.Import(importPath, dir, build.FindOnly)
78 return nil, fmt.Errorf("can't find package containing %s", filename)
81 // -- Effective methods of file system interface -------------------------
83 // (go/build.Context defines these as methods, but does not export them.)
85 // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
86 // the local file system to answer the question.
87 func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) {
88 if f := ctxt.HasSubdir; f != nil {
92 // Try using paths we received.
93 if rel, ok = hasSubdir(root, dir); ok {
97 // Try expanding symlinks and comparing
98 // expanded against unexpanded and
99 // expanded against expanded.
100 rootSym, _ := filepath.EvalSymlinks(root)
101 dirSym, _ := filepath.EvalSymlinks(dir)
103 if rel, ok = hasSubdir(rootSym, dir); ok {
106 if rel, ok = hasSubdir(root, dirSym); ok {
109 return hasSubdir(rootSym, dirSym)
112 func hasSubdir(root, dir string) (rel string, ok bool) {
113 const sep = string(filepath.Separator)
114 root = filepath.Clean(root)
115 if !strings.HasSuffix(root, sep) {
119 dir = filepath.Clean(dir)
120 if !strings.HasPrefix(dir, root) {
124 return filepath.ToSlash(dir[len(root):]), true
127 // FileExists returns true if the specified file exists,
128 // using the build context's file system interface.
129 func FileExists(ctxt *build.Context, path string) bool {
130 if ctxt.OpenFile != nil {
131 r, err := ctxt.OpenFile(path)
135 r.Close() // ignore error
138 _, err := os.Stat(path)
142 // OpenFile behaves like os.Open,
143 // but uses the build context's file system interface, if any.
144 func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
145 if ctxt.OpenFile != nil {
146 return ctxt.OpenFile(path)
151 // IsAbsPath behaves like filepath.IsAbs,
152 // but uses the build context's file system interface, if any.
153 func IsAbsPath(ctxt *build.Context, path string) bool {
154 if ctxt.IsAbsPath != nil {
155 return ctxt.IsAbsPath(path)
157 return filepath.IsAbs(path)
160 // JoinPath behaves like filepath.Join,
161 // but uses the build context's file system interface, if any.
162 func JoinPath(ctxt *build.Context, path ...string) string {
163 if ctxt.JoinPath != nil {
164 return ctxt.JoinPath(path...)
166 return filepath.Join(path...)
169 // IsDir behaves like os.Stat plus IsDir,
170 // but uses the build context's file system interface, if any.
171 func IsDir(ctxt *build.Context, path string) bool {
172 if ctxt.IsDir != nil {
173 return ctxt.IsDir(path)
175 fi, err := os.Stat(path)
176 return err == nil && fi.IsDir()
179 // ReadDir behaves like ioutil.ReadDir,
180 // but uses the build context's file system interface, if any.
181 func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
182 if ctxt.ReadDir != nil {
183 return ctxt.ReadDir(path)
185 return ioutil.ReadDir(path)
188 // SplitPathList behaves like filepath.SplitList,
189 // but uses the build context's file system interface, if any.
190 func SplitPathList(ctxt *build.Context, s string) []string {
191 if ctxt.SplitPathList != nil {
192 return ctxt.SplitPathList(s)
194 return filepath.SplitList(s)
197 // sameFile returns true if x and y have the same basename and denote
200 func sameFile(x, y string) bool {
201 if path.Clean(x) == path.Clean(y) {
204 if filepath.Base(x) == filepath.Base(y) { // (optimisation)
205 if xi, err := os.Stat(x); err == nil {
206 if yi, err := os.Stat(y); err == nil {
207 return os.SameFile(xi, yi)