util.go raw

   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