gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/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  //
    32  func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) {
    33  	if !IsAbsPath(ctxt, file) {
    34  		file = JoinPath(ctxt, dir, file)
    35  	}
    36  	rd, err := OpenFile(ctxt, file)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	defer rd.Close() // ignore error
    41  	if displayPath != nil {
    42  		file = displayPath(file)
    43  	}
    44  	return parser.ParseFile(fset, file, rd, mode)
    45  }
    46  
    47  // ContainingPackage returns the package containing filename.
    48  //
    49  // If filename is not absolute, it is interpreted relative to working directory dir.
    50  // All I/O is via the build context's file system interface, if any.
    51  //
    52  // The '...Files []string' fields of the resulting build.Package are not
    53  // populated (build.FindOnly mode).
    54  //
    55  func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
    56  	if !IsAbsPath(ctxt, filename) {
    57  		filename = JoinPath(ctxt, dir, filename)
    58  	}
    59  
    60  	// We must not assume the file tree uses
    61  	// "/" always,
    62  	// `\` always,
    63  	// or os.PathSeparator (which varies by platform),
    64  	// but to make any progress, we are forced to assume that
    65  	// paths will not use `\` unless the PathSeparator
    66  	// is also `\`, thus we can rely on filepath.ToSlash for some sanity.
    67  
    68  	dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
    69  
    70  	// We assume that no source root (GOPATH[i] or GOROOT) contains any other.
    71  	for _, srcdir := range ctxt.SrcDirs() {
    72  		srcdirSlash := filepath.ToSlash(srcdir) + "/"
    73  		if importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok {
    74  			return ctxt.Import(importPath, dir, build.FindOnly)
    75  		}
    76  	}
    77  
    78  	return nil, fmt.Errorf("can't find package containing %s", filename)
    79  }
    80  
    81  // -- Effective methods of file system interface -------------------------
    82  
    83  // (go/build.Context defines these as methods, but does not export them.)
    84  
    85  // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
    86  // the local file system to answer the question.
    87  func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) {
    88  	if f := ctxt.HasSubdir; f != nil {
    89  		return f(root, dir)
    90  	}
    91  
    92  	// Try using paths we received.
    93  	if rel, ok = hasSubdir(root, dir); ok {
    94  		return
    95  	}
    96  
    97  	// Try expanding symlinks and comparing
    98  	// expanded against unexpanded and
    99  	// expanded against expanded.
   100  	rootSym, _ := filepath.EvalSymlinks(root)
   101  	dirSym, _ := filepath.EvalSymlinks(dir)
   102  
   103  	if rel, ok = hasSubdir(rootSym, dir); ok {
   104  		return
   105  	}
   106  	if rel, ok = hasSubdir(root, dirSym); ok {
   107  		return
   108  	}
   109  	return hasSubdir(rootSym, dirSym)
   110  }
   111  
   112  func hasSubdir(root, dir string) (rel string, ok bool) {
   113  	const sep = string(filepath.Separator)
   114  	root = filepath.Clean(root)
   115  	if !strings.HasSuffix(root, sep) {
   116  		root += sep
   117  	}
   118  
   119  	dir = filepath.Clean(dir)
   120  	if !strings.HasPrefix(dir, root) {
   121  		return "", false
   122  	}
   123  
   124  	return filepath.ToSlash(dir[len(root):]), true
   125  }
   126  
   127  // FileExists returns true if the specified file exists,
   128  // using the build context's file system interface.
   129  func FileExists(ctxt *build.Context, path string) bool {
   130  	if ctxt.OpenFile != nil {
   131  		r, err := ctxt.OpenFile(path)
   132  		if err != nil {
   133  			return false
   134  		}
   135  		r.Close() // ignore error
   136  		return true
   137  	}
   138  	_, err := os.Stat(path)
   139  	return err == nil
   140  }
   141  
   142  // OpenFile behaves like os.Open,
   143  // but uses the build context's file system interface, if any.
   144  func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
   145  	if ctxt.OpenFile != nil {
   146  		return ctxt.OpenFile(path)
   147  	}
   148  	return os.Open(path)
   149  }
   150  
   151  // IsAbsPath behaves like filepath.IsAbs,
   152  // but uses the build context's file system interface, if any.
   153  func IsAbsPath(ctxt *build.Context, path string) bool {
   154  	if ctxt.IsAbsPath != nil {
   155  		return ctxt.IsAbsPath(path)
   156  	}
   157  	return filepath.IsAbs(path)
   158  }
   159  
   160  // JoinPath behaves like filepath.Join,
   161  // but uses the build context's file system interface, if any.
   162  func JoinPath(ctxt *build.Context, path ...string) string {
   163  	if ctxt.JoinPath != nil {
   164  		return ctxt.JoinPath(path...)
   165  	}
   166  	return filepath.Join(path...)
   167  }
   168  
   169  // IsDir behaves like os.Stat plus IsDir,
   170  // but uses the build context's file system interface, if any.
   171  func IsDir(ctxt *build.Context, path string) bool {
   172  	if ctxt.IsDir != nil {
   173  		return ctxt.IsDir(path)
   174  	}
   175  	fi, err := os.Stat(path)
   176  	return err == nil && fi.IsDir()
   177  }
   178  
   179  // ReadDir behaves like ioutil.ReadDir,
   180  // but uses the build context's file system interface, if any.
   181  func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
   182  	if ctxt.ReadDir != nil {
   183  		return ctxt.ReadDir(path)
   184  	}
   185  	return ioutil.ReadDir(path)
   186  }
   187  
   188  // SplitPathList behaves like filepath.SplitList,
   189  // but uses the build context's file system interface, if any.
   190  func SplitPathList(ctxt *build.Context, s string) []string {
   191  	if ctxt.SplitPathList != nil {
   192  		return ctxt.SplitPathList(s)
   193  	}
   194  	return filepath.SplitList(s)
   195  }
   196  
   197  // sameFile returns true if x and y have the same basename and denote
   198  // the same file.
   199  //
   200  func sameFile(x, y string) bool {
   201  	if path.Clean(x) == path.Clean(y) {
   202  		return true
   203  	}
   204  	if filepath.Base(x) == filepath.Base(y) { // (optimisation)
   205  		if xi, err := os.Stat(x); err == nil {
   206  			if yi, err := os.Stat(y); err == nil {
   207  				return os.SameFile(xi, yi)
   208  			}
   209  		}
   210  	}
   211  	return false
   212  }