159 lines
4.5 KiB
Go
159 lines
4.5 KiB
Go
|
// 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).
|
||
|
//
|
||
|
// TODO(adonovan): call this from oracle when the tree thaws.
|
||
|
//
|
||
|
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 strings.HasPrefix(dirSlash, srcdirSlash) {
|
||
|
importPath := dirSlash[len(srcdirSlash) : len(dirSlash)-len("/")]
|
||
|
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.)
|
||
|
|
||
|
// TODO(adonovan): HasSubdir?
|
||
|
|
||
|
// 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)
|
||
|
}
|