util.go

  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
  5package buildutil
  6
  7import (
  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//
 32func 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//
 55func 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.
 87func 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
112func 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.
129func 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.
144func 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.
153func 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.
162func 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.
171func 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.
181func 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.
190func 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//
200func 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}