github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/buildutil/util.go (about) 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 }