src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

util.go (5871B)


      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 }