|  | // 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 | 
|  | } |