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
5package buildutil
6
7import (
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//
32func 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)
35 }
36 rd, err := OpenFile(ctxt, file)
37 if err != nil {
38 return nil, err
39 }
40 defer rd.Close() // ignore error
41 if displayPath != nil {
42 file = displayPath(file)
43 }
44 return parser.ParseFile(fset, file, rd, mode)
45}
46
47// ContainingPackage returns the package containing filename.
48//
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.
51//
52// The '...Files []string' fields of the resulting build.Package are not
53// populated (build.FindOnly mode).
54//
55func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
56 if !IsAbsPath(ctxt, filename) {
57 filename = JoinPath(ctxt, dir, filename)
58 }
59
60 // We must not assume the file tree uses
61 // "/" always,
62 // `\` always,
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.
67
68 dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
69
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)
75 }
76 }
77
78 return nil, fmt.Errorf("can't find package containing %s", filename)
79}
80
81// -- Effective methods of file system interface -------------------------
82
83// (go/build.Context defines these as methods, but does not export them.)
84
85// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
86// the local file system to answer the question.
87func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) {
88 if f := ctxt.HasSubdir; f != nil {
89 return f(root, dir)
90 }
91
92 // Try using paths we received.
93 if rel, ok = hasSubdir(root, dir); ok {
94 return
95 }
96
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)
102
103 if rel, ok = hasSubdir(rootSym, dir); ok {
104 return
105 }
106 if rel, ok = hasSubdir(root, dirSym); ok {
107 return
108 }
109 return hasSubdir(rootSym, dirSym)
110}
111
112func 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) {
116 root += sep
117 }
118
119 dir = filepath.Clean(dir)
120 if !strings.HasPrefix(dir, root) {
121 return "", false
122 }
123
124 return filepath.ToSlash(dir[len(root):]), true
125}
126
127// FileExists returns true if the specified file exists,
128// using the build context's file system interface.
129func FileExists(ctxt *build.Context, path string) bool {
130 if ctxt.OpenFile != nil {
131 r, err := ctxt.OpenFile(path)
132 if err != nil {
133 return false
134 }
135 r.Close() // ignore error
136 return true
137 }
138 _, err := os.Stat(path)
139 return err == nil
140}
141
142// OpenFile behaves like os.Open,
143// but uses the build context's file system interface, if any.
144func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
145 if ctxt.OpenFile != nil {
146 return ctxt.OpenFile(path)
147 }
148 return os.Open(path)
149}
150
151// IsAbsPath behaves like filepath.IsAbs,
152// but uses the build context's file system interface, if any.
153func IsAbsPath(ctxt *build.Context, path string) bool {
154 if ctxt.IsAbsPath != nil {
155 return ctxt.IsAbsPath(path)
156 }
157 return filepath.IsAbs(path)
158}
159
160// JoinPath behaves like filepath.Join,
161// but uses the build context's file system interface, if any.
162func JoinPath(ctxt *build.Context, path ...string) string {
163 if ctxt.JoinPath != nil {
164 return ctxt.JoinPath(path...)
165 }
166 return filepath.Join(path...)
167}
168
169// IsDir behaves like os.Stat plus IsDir,
170// but uses the build context's file system interface, if any.
171func IsDir(ctxt *build.Context, path string) bool {
172 if ctxt.IsDir != nil {
173 return ctxt.IsDir(path)
174 }
175 fi, err := os.Stat(path)
176 return err == nil && fi.IsDir()
177}
178
179// ReadDir behaves like ioutil.ReadDir,
180// but uses the build context's file system interface, if any.
181func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
182 if ctxt.ReadDir != nil {
183 return ctxt.ReadDir(path)
184 }
185 return ioutil.ReadDir(path)
186}
187
188// SplitPathList behaves like filepath.SplitList,
189// but uses the build context's file system interface, if any.
190func SplitPathList(ctxt *build.Context, s string) []string {
191 if ctxt.SplitPathList != nil {
192 return ctxt.SplitPathList(s)
193 }
194 return filepath.SplitList(s)
195}
196
197// sameFile returns true if x and y have the same basename and denote
198// the same file.
199//
200func sameFile(x, y string) bool {
201 if path.Clean(x) == path.Clean(y) {
202 return true
203 }
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)
208 }
209 }
210 }
211 return false
212}