| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package buildutil |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/build" |
| "go/parser" |
| "go/token" |
| "io" |
| "io/ioutil" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
| ) |
| |
| // ParseFile behaves like parser.ParseFile, |
| // but uses the build context's file system interface, if any. |
| // |
| // If file is not absolute (as defined by IsAbsPath), the (dir, file) |
| // components are joined using JoinPath; dir must be absolute. |
| // |
| // The displayPath function, if provided, is used to transform the |
| // filename that will be attached to the ASTs. |
| // |
| // TODO(adonovan): call this from go/loader.parseFiles when the tree thaws. |
| // |
| func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) { |
| if !IsAbsPath(ctxt, file) { |
| file = JoinPath(ctxt, dir, file) |
| } |
| rd, err := OpenFile(ctxt, file) |
| if err != nil { |
| return nil, err |
| } |
| defer rd.Close() // ignore error |
| if displayPath != nil { |
| file = displayPath(file) |
| } |
| return parser.ParseFile(fset, file, rd, mode) |
| } |
| |
| // ContainingPackage returns the package containing filename. |
| // |
| // If filename is not absolute, it is interpreted relative to working directory dir. |
| // All I/O is via the build context's file system interface, if any. |
| // |
| // The '...Files []string' fields of the resulting build.Package are not |
| // populated (build.FindOnly mode). |
| // |
| func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) { |
| if !IsAbsPath(ctxt, filename) { |
| filename = JoinPath(ctxt, dir, filename) |
| } |
| |
| // We must not assume the file tree uses |
| // "/" always, |
| // `\` always, |
| // or os.PathSeparator (which varies by platform), |
| // but to make any progress, we are forced to assume that |
| // paths will not use `\` unless the PathSeparator |
| // is also `\`, thus we can rely on filepath.ToSlash for some sanity. |
| |
| dirSlash := path.Dir(filepath.ToSlash(filename)) + "/" |
| |
| // We assume that no source root (GOPATH[i] or GOROOT) contains any other. |
| for _, srcdir := range ctxt.SrcDirs() { |
| srcdirSlash := filepath.ToSlash(srcdir) + "/" |
| if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok { |
| return ctxt.Import(importPath, dir, build.FindOnly) |
| } |
| } |
| |
| return nil, fmt.Errorf("can't find package containing %s", filename) |
| } |
| |
| // -- Effective methods of file system interface ------------------------- |
| |
| // (go/build.Context defines these as methods, but does not export them.) |
| |
| // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses |
| // the local file system to answer the question. |
| func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) { |
| if f := ctxt.HasSubdir; f != nil { |
| return f(root, dir) |
| } |
| |
| // Try using paths we received. |
| if rel, ok = hasSubdir(root, dir); ok { |
| return |
| } |
| |
| // Try expanding symlinks and comparing |
| // expanded against unexpanded and |
| // expanded against expanded. |
| rootSym, _ := filepath.EvalSymlinks(root) |
| dirSym, _ := filepath.EvalSymlinks(dir) |
| |
| if rel, ok = hasSubdir(rootSym, dir); ok { |
| return |
| } |
| if rel, ok = hasSubdir(root, dirSym); ok { |
| return |
| } |
| return hasSubdir(rootSym, dirSym) |
| } |
| |
| func hasSubdir(root, dir string) (rel string, ok bool) { |
| const sep = string(filepath.Separator) |
| root = filepath.Clean(root) |
| if !strings.HasSuffix(root, sep) { |
| root += sep |
| } |
| |
| dir = filepath.Clean(dir) |
| if !strings.HasPrefix(dir, root) { |
| return "", false |
| } |
| |
| return filepath.ToSlash(dir[len(root):]), true |
| } |
| |
| // FileExists returns true if the specified file exists, |
| // using the build context's file system interface. |
| func FileExists(ctxt *build.Context, path string) bool { |
| if ctxt.OpenFile != nil { |
| r, err := ctxt.OpenFile(path) |
| if err != nil { |
| return false |
| } |
| r.Close() // ignore error |
| return true |
| } |
| _, err := os.Stat(path) |
| return err == nil |
| } |
| |
| // OpenFile behaves like os.Open, |
| // but uses the build context's file system interface, if any. |
| func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) { |
| if ctxt.OpenFile != nil { |
| return ctxt.OpenFile(path) |
| } |
| return os.Open(path) |
| } |
| |
| // IsAbsPath behaves like filepath.IsAbs, |
| // but uses the build context's file system interface, if any. |
| func IsAbsPath(ctxt *build.Context, path string) bool { |
| if ctxt.IsAbsPath != nil { |
| return ctxt.IsAbsPath(path) |
| } |
| return filepath.IsAbs(path) |
| } |
| |
| // JoinPath behaves like filepath.Join, |
| // but uses the build context's file system interface, if any. |
| func JoinPath(ctxt *build.Context, path ...string) string { |
| if ctxt.JoinPath != nil { |
| return ctxt.JoinPath(path...) |
| } |
| return filepath.Join(path...) |
| } |
| |
| // IsDir behaves like os.Stat plus IsDir, |
| // but uses the build context's file system interface, if any. |
| func IsDir(ctxt *build.Context, path string) bool { |
| if ctxt.IsDir != nil { |
| return ctxt.IsDir(path) |
| } |
| fi, err := os.Stat(path) |
| return err == nil && fi.IsDir() |
| } |
| |
| // ReadDir behaves like ioutil.ReadDir, |
| // but uses the build context's file system interface, if any. |
| func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) { |
| if ctxt.ReadDir != nil { |
| return ctxt.ReadDir(path) |
| } |
| return ioutil.ReadDir(path) |
| } |
| |
| // SplitPathList behaves like filepath.SplitList, |
| // but uses the build context's file system interface, if any. |
| func SplitPathList(ctxt *build.Context, s string) []string { |
| if ctxt.SplitPathList != nil { |
| return ctxt.SplitPathList(s) |
| } |
| return filepath.SplitList(s) |
| } |
| |
| // sameFile returns true if x and y have the same basename and denote |
| // the same file. |
| // |
| func sameFile(x, y string) bool { |
| if path.Clean(x) == path.Clean(y) { |
| return true |
| } |
| if filepath.Base(x) == filepath.Base(y) { // (optimisation) |
| if xi, err := os.Stat(x); err == nil { |
| if yi, err := os.Stat(y); err == nil { |
| return os.SameFile(xi, yi) |
| } |
| } |
| } |
| return false |
| } |