| 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