This makes it work even if dependency packages are not installed yet.master
parent
5b3e75f776
commit
837142166a
@ -0,0 +1,198 @@ |
||||
// 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 provides utilities related to the go/build
|
||||
// package in the standard library.
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface, which must
|
||||
// be concurrency-safe.
|
||||
package buildutil // import "golang.org/x/tools/go/buildutil"
|
||||
|
||||
import ( |
||||
"go/build" |
||||
"os" |
||||
"path/filepath" |
||||
"sort" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
// AllPackages returns the package path of each Go package in any source
|
||||
// directory of the specified build context (e.g. $GOROOT or an element
|
||||
// of $GOPATH). Errors are ignored. The results are sorted.
|
||||
// All package paths are canonical, and thus may contain "/vendor/".
|
||||
//
|
||||
// The result may include import paths for directories that contain no
|
||||
// *.go files, such as "archive" (in $GOROOT/src).
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface,
|
||||
// which must be concurrency-safe.
|
||||
//
|
||||
func AllPackages(ctxt *build.Context) []string { |
||||
var list []string |
||||
ForEachPackage(ctxt, func(pkg string, _ error) { |
||||
list = append(list, pkg) |
||||
}) |
||||
sort.Strings(list) |
||||
return list |
||||
} |
||||
|
||||
// ForEachPackage calls the found function with the package path of
|
||||
// each Go package it finds in any source directory of the specified
|
||||
// build context (e.g. $GOROOT or an element of $GOPATH).
|
||||
// All package paths are canonical, and thus may contain "/vendor/".
|
||||
//
|
||||
// If the package directory exists but could not be read, the second
|
||||
// argument to the found function provides the error.
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface,
|
||||
// which must be concurrency-safe.
|
||||
//
|
||||
func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) { |
||||
ch := make(chan item) |
||||
|
||||
var wg sync.WaitGroup |
||||
for _, root := range ctxt.SrcDirs() { |
||||
root := root |
||||
wg.Add(1) |
||||
go func() { |
||||
allPackages(ctxt, root, ch) |
||||
wg.Done() |
||||
}() |
||||
} |
||||
go func() { |
||||
wg.Wait() |
||||
close(ch) |
||||
}() |
||||
|
||||
// All calls to found occur in the caller's goroutine.
|
||||
for i := range ch { |
||||
found(i.importPath, i.err) |
||||
} |
||||
} |
||||
|
||||
type item struct { |
||||
importPath string |
||||
err error // (optional)
|
||||
} |
||||
|
||||
// We use a process-wide counting semaphore to limit
|
||||
// the number of parallel calls to ReadDir.
|
||||
var ioLimit = make(chan bool, 20) |
||||
|
||||
func allPackages(ctxt *build.Context, root string, ch chan<- item) { |
||||
root = filepath.Clean(root) + string(os.PathSeparator) |
||||
|
||||
var wg sync.WaitGroup |
||||
|
||||
var walkDir func(dir string) |
||||
walkDir = func(dir string) { |
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
base := filepath.Base(dir) |
||||
if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" { |
||||
return |
||||
} |
||||
|
||||
pkg := filepath.ToSlash(strings.TrimPrefix(dir, root)) |
||||
|
||||
// Prune search if we encounter any of these import paths.
|
||||
switch pkg { |
||||
case "builtin": |
||||
return |
||||
} |
||||
|
||||
ioLimit <- true |
||||
files, err := ReadDir(ctxt, dir) |
||||
<-ioLimit |
||||
if pkg != "" || err != nil { |
||||
ch <- item{pkg, err} |
||||
} |
||||
for _, fi := range files { |
||||
fi := fi |
||||
if fi.IsDir() { |
||||
wg.Add(1) |
||||
go func() { |
||||
walkDir(filepath.Join(dir, fi.Name())) |
||||
wg.Done() |
||||
}() |
||||
} |
||||
} |
||||
} |
||||
|
||||
walkDir(root) |
||||
wg.Wait() |
||||
} |
||||
|
||||
// ExpandPatterns returns the set of packages matched by patterns,
|
||||
// which may have the following forms:
|
||||
//
|
||||
// golang.org/x/tools/cmd/guru # a single package
|
||||
// golang.org/x/tools/... # all packages beneath dir
|
||||
// ... # the entire workspace.
|
||||
//
|
||||
// Order is significant: a pattern preceded by '-' removes matching
|
||||
// packages from the set. For example, these patterns match all encoding
|
||||
// packages except encoding/xml:
|
||||
//
|
||||
// encoding/... -encoding/xml
|
||||
//
|
||||
// A trailing slash in a pattern is ignored. (Path components of Go
|
||||
// package names are separated by slash, not the platform's path separator.)
|
||||
//
|
||||
func ExpandPatterns(ctxt *build.Context, patterns []string) map[string]bool { |
||||
// TODO(adonovan): support other features of 'go list':
|
||||
// - "std"/"cmd"/"all" meta-packages
|
||||
// - "..." not at the end of a pattern
|
||||
// - relative patterns using "./" or "../" prefix
|
||||
|
||||
pkgs := make(map[string]bool) |
||||
doPkg := func(pkg string, neg bool) { |
||||
if neg { |
||||
delete(pkgs, pkg) |
||||
} else { |
||||
pkgs[pkg] = true |
||||
} |
||||
} |
||||
|
||||
// Scan entire workspace if wildcards are present.
|
||||
// TODO(adonovan): opt: scan only the necessary subtrees of the workspace.
|
||||
var all []string |
||||
for _, arg := range patterns { |
||||
if strings.HasSuffix(arg, "...") { |
||||
all = AllPackages(ctxt) |
||||
break |
||||
} |
||||
} |
||||
|
||||
for _, arg := range patterns { |
||||
if arg == "" { |
||||
continue |
||||
} |
||||
|
||||
neg := arg[0] == '-' |
||||
if neg { |
||||
arg = arg[1:] |
||||
} |
||||
|
||||
if arg == "..." { |
||||
// ... matches all packages
|
||||
for _, pkg := range all { |
||||
doPkg(pkg, neg) |
||||
} |
||||
} else if dir := strings.TrimSuffix(arg, "/..."); dir != arg { |
||||
// dir/... matches all packages beneath dir
|
||||
for _, pkg := range all { |
||||
if strings.HasPrefix(pkg, dir) && |
||||
(len(pkg) == len(dir) || pkg[len(dir)] == '/') { |
||||
doPkg(pkg, neg) |
||||
} |
||||
} |
||||
} else { |
||||
// single package
|
||||
doPkg(strings.TrimSuffix(arg, "/"), neg) |
||||
} |
||||
} |
||||
|
||||
return pkgs |
||||
} |
@ -0,0 +1,108 @@ |
||||
package buildutil |
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/build" |
||||
"io" |
||||
"io/ioutil" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"sort" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// FakeContext returns a build.Context for the fake file tree specified
|
||||
// by pkgs, which maps package import paths to a mapping from file base
|
||||
// names to contents.
|
||||
//
|
||||
// The fake Context has a GOROOT of "/go" and no GOPATH, and overrides
|
||||
// the necessary file access methods to read from memory instead of the
|
||||
// real file system.
|
||||
//
|
||||
// Unlike a real file tree, the fake one has only two levels---packages
|
||||
// and files---so ReadDir("/go/src/") returns all packages under
|
||||
// /go/src/ including, for instance, "math" and "math/big".
|
||||
// ReadDir("/go/src/math/big") would return all the files in the
|
||||
// "math/big" package.
|
||||
//
|
||||
func FakeContext(pkgs map[string]map[string]string) *build.Context { |
||||
clean := func(filename string) string { |
||||
f := path.Clean(filepath.ToSlash(filename)) |
||||
// Removing "/go/src" while respecting segment
|
||||
// boundaries has this unfortunate corner case:
|
||||
if f == "/go/src" { |
||||
return "" |
||||
} |
||||
return strings.TrimPrefix(f, "/go/src/") |
||||
} |
||||
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOROOT = "/go" |
||||
ctxt.GOPATH = "" |
||||
ctxt.IsDir = func(dir string) bool { |
||||
dir = clean(dir) |
||||
if dir == "" { |
||||
return true // needed by (*build.Context).SrcDirs
|
||||
} |
||||
return pkgs[dir] != nil |
||||
} |
||||
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) { |
||||
dir = clean(dir) |
||||
var fis []os.FileInfo |
||||
if dir == "" { |
||||
// enumerate packages
|
||||
for importPath := range pkgs { |
||||
fis = append(fis, fakeDirInfo(importPath)) |
||||
} |
||||
} else { |
||||
// enumerate files of package
|
||||
for basename := range pkgs[dir] { |
||||
fis = append(fis, fakeFileInfo(basename)) |
||||
} |
||||
} |
||||
sort.Sort(byName(fis)) |
||||
return fis, nil |
||||
} |
||||
ctxt.OpenFile = func(filename string) (io.ReadCloser, error) { |
||||
filename = clean(filename) |
||||
dir, base := path.Split(filename) |
||||
content, ok := pkgs[path.Clean(dir)][base] |
||||
if !ok { |
||||
return nil, fmt.Errorf("file not found: %s", filename) |
||||
} |
||||
return ioutil.NopCloser(strings.NewReader(content)), nil |
||||
} |
||||
ctxt.IsAbsPath = func(path string) bool { |
||||
path = filepath.ToSlash(path) |
||||
// Don't rely on the default (filepath.Path) since on
|
||||
// Windows, it reports virtual paths as non-absolute.
|
||||
return strings.HasPrefix(path, "/") |
||||
} |
||||
return &ctxt |
||||
} |
||||
|
||||
type byName []os.FileInfo |
||||
|
||||
func (s byName) Len() int { return len(s) } |
||||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
||||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } |
||||
|
||||
type fakeFileInfo string |
||||
|
||||
func (fi fakeFileInfo) Name() string { return string(fi) } |
||||
func (fakeFileInfo) Sys() interface{} { return nil } |
||||
func (fakeFileInfo) ModTime() time.Time { return time.Time{} } |
||||
func (fakeFileInfo) IsDir() bool { return false } |
||||
func (fakeFileInfo) Size() int64 { return 0 } |
||||
func (fakeFileInfo) Mode() os.FileMode { return 0644 } |
||||
|
||||
type fakeDirInfo string |
||||
|
||||
func (fd fakeDirInfo) Name() string { return string(fd) } |
||||
func (fakeDirInfo) Sys() interface{} { return nil } |
||||
func (fakeDirInfo) ModTime() time.Time { return time.Time{} } |
||||
func (fakeDirInfo) IsDir() bool { return true } |
||||
func (fakeDirInfo) Size() int64 { return 0 } |
||||
func (fakeDirInfo) Mode() os.FileMode { return 0755 } |
@ -0,0 +1,103 @@ |
||||
// Copyright 2016 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 ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"go/build" |
||||
"io" |
||||
"io/ioutil" |
||||
"path/filepath" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// OverlayContext overlays a build.Context with additional files from
|
||||
// a map. Files in the map take precedence over other files.
|
||||
//
|
||||
// In addition to plain string comparison, two file names are
|
||||
// considered equal if their base names match and their directory
|
||||
// components point at the same directory on the file system. That is,
|
||||
// symbolic links are followed for directories, but not files.
|
||||
//
|
||||
// A common use case for OverlayContext is to allow editors to pass in
|
||||
// a set of unsaved, modified files.
|
||||
//
|
||||
// Currently, only the Context.OpenFile function will respect the
|
||||
// overlay. This may change in the future.
|
||||
func OverlayContext(orig *build.Context, overlay map[string][]byte) *build.Context { |
||||
// TODO(dominikh): Implement IsDir, HasSubdir and ReadDir
|
||||
|
||||
rc := func(data []byte) (io.ReadCloser, error) { |
||||
return ioutil.NopCloser(bytes.NewBuffer(data)), nil |
||||
} |
||||
|
||||
copy := *orig // make a copy
|
||||
ctxt := © |
||||
ctxt.OpenFile = func(path string) (io.ReadCloser, error) { |
||||
// Fast path: names match exactly.
|
||||
if content, ok := overlay[path]; ok { |
||||
return rc(content) |
||||
} |
||||
|
||||
// Slow path: check for same file under a different
|
||||
// alias, perhaps due to a symbolic link.
|
||||
for filename, content := range overlay { |
||||
if sameFile(path, filename) { |
||||
return rc(content) |
||||
} |
||||
} |
||||
|
||||
return OpenFile(orig, path) |
||||
} |
||||
return ctxt |
||||
} |
||||
|
||||
// ParseOverlayArchive parses an archive containing Go files and their
|
||||
// contents. The result is intended to be used with OverlayContext.
|
||||
//
|
||||
//
|
||||
// Archive format
|
||||
//
|
||||
// The archive consists of a series of files. Each file consists of a
|
||||
// name, a decimal file size and the file contents, separated by
|
||||
// newlinews. No newline follows after the file contents.
|
||||
func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) { |
||||
overlay := make(map[string][]byte) |
||||
r := bufio.NewReader(archive) |
||||
for { |
||||
// Read file name.
|
||||
filename, err := r.ReadString('\n') |
||||
if err != nil { |
||||
if err == io.EOF { |
||||
break // OK
|
||||
} |
||||
return nil, fmt.Errorf("reading archive file name: %v", err) |
||||
} |
||||
filename = filepath.Clean(strings.TrimSpace(filename)) |
||||
|
||||
// Read file size.
|
||||
sz, err := r.ReadString('\n') |
||||
if err != nil { |
||||
return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err) |
||||
} |
||||
sz = strings.TrimSpace(sz) |
||||
size, err := strconv.ParseUint(sz, 10, 32) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err) |
||||
} |
||||
|
||||
// Read file content.
|
||||
content := make([]byte, size) |
||||
if _, err := io.ReadFull(r, content); err != nil { |
||||
return nil, fmt.Errorf("reading archive file %s: %v", filename, err) |
||||
} |
||||
overlay[filename] = content |
||||
} |
||||
|
||||
return overlay, nil |
||||
} |
@ -0,0 +1,75 @@ |
||||
package buildutil |
||||
|
||||
// This logic was copied from stringsFlag from $GOROOT/src/cmd/go/build.go.
|
||||
|
||||
import "fmt" |
||||
|
||||
const TagsFlagDoc = "a list of `build tags` to consider satisfied during the build. " + |
||||
"For more information about build tags, see the description of " + |
||||
"build constraints in the documentation for the go/build package" |
||||
|
||||
// TagsFlag is an implementation of the flag.Value and flag.Getter interfaces that parses
|
||||
// a flag value in the same manner as go build's -tags flag and
|
||||
// populates a []string slice.
|
||||
//
|
||||
// See $GOROOT/src/go/build/doc.go for description of build tags.
|
||||
// See $GOROOT/src/cmd/go/doc.go for description of 'go build -tags' flag.
|
||||
//
|
||||
// Example:
|
||||
// flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||
type TagsFlag []string |
||||
|
||||
func (v *TagsFlag) Set(s string) error { |
||||
var err error |
||||
*v, err = splitQuotedFields(s) |
||||
if *v == nil { |
||||
*v = []string{} |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func (v *TagsFlag) Get() interface{} { return *v } |
||||
|
||||
func splitQuotedFields(s string) ([]string, error) { |
||||
// Split fields allowing '' or "" around elements.
|
||||
// Quotes further inside the string do not count.
|
||||
var f []string |
||||
for len(s) > 0 { |
||||
for len(s) > 0 && isSpaceByte(s[0]) { |
||||
s = s[1:] |
||||
} |
||||
if len(s) == 0 { |
||||
break |
||||
} |
||||
// Accepted quoted string. No unescaping inside.
|
||||
if s[0] == '"' || s[0] == '\'' { |
||||
quote := s[0] |
||||
s = s[1:] |
||||
i := 0 |
||||
for i < len(s) && s[i] != quote { |
||||
i++ |
||||
} |
||||
if i >= len(s) { |
||||
return nil, fmt.Errorf("unterminated %c string", quote) |
||||
} |
||||
f = append(f, s[:i]) |
||||
s = s[i+1:] |
||||
continue |
||||
} |
||||
i := 0 |
||||
for i < len(s) && !isSpaceByte(s[i]) { |
||||
i++ |
||||
} |
||||
f = append(f, s[:i]) |
||||
s = s[i:] |
||||
} |
||||
return f, nil |
||||
} |
||||
|
||||
func (v *TagsFlag) String() string { |
||||
return "<tagsFlag>" |
||||
} |
||||
|
||||
func isSpaceByte(c byte) bool { |
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' |
||||
} |
@ -0,0 +1,221 @@ |
||||
// 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" |
||||
"runtime" |
||||
"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).
|
||||
//
|
||||
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 importPath, ok := HasSubdir(ctxt, srcdirSlash, dirSlash); ok { |
||||
return ctxt.Import(importPath, dir, build.FindOnly) |
||||
} |
||||
} |
||||
|
||||
return nil, fmt.Errorf("can't find package containing %s", filename) |
||||
} |
||||
|
||||
// dirHasPrefix tests whether the directory dir begins with prefix.
|
||||
func dirHasPrefix(dir, prefix string) bool { |
||||
if runtime.GOOS != "windows" { |
||||
return strings.HasPrefix(dir, prefix) |
||||
} |
||||
return len(dir) >= len(prefix) && strings.EqualFold(dir[:len(prefix)], prefix) |
||||
} |
||||
|
||||
// -- Effective methods of file system interface -------------------------
|
||||
|
||||
// (go/build.Context defines these as methods, but does not export them.)
|
||||
|
||||
// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
|
||||
// the local file system to answer the question.
|
||||
func HasSubdir(ctxt *build.Context, root, dir string) (rel string, ok bool) { |
||||
if f := ctxt.HasSubdir; f != nil { |
||||
return f(root, dir) |
||||
} |
||||
|
||||
// Try using paths we received.
|
||||
if rel, ok = hasSubdir(root, dir); ok { |
||||
return |
||||
} |
||||
|
||||
// Try expanding symlinks and comparing
|
||||
// expanded against unexpanded and
|
||||
// expanded against expanded.
|
||||
rootSym, _ := filepath.EvalSymlinks(root) |
||||
dirSym, _ := filepath.EvalSymlinks(dir) |
||||
|
||||
if rel, ok = hasSubdir(rootSym, dir); ok { |
||||
return |
||||
} |
||||
if rel, ok = hasSubdir(root, dirSym); ok { |
||||
return |
||||
} |
||||
return hasSubdir(rootSym, dirSym) |
||||
} |
||||
|
||||
func hasSubdir(root, dir string) (rel string, ok bool) { |
||||
const sep = string(filepath.Separator) |
||||
root = filepath.Clean(root) |
||||
if !strings.HasSuffix(root, sep) { |
||||
root += sep |
||||
} |
||||
|
||||
dir = filepath.Clean(dir) |
||||
if !strings.HasPrefix(dir, root) { |
||||
return "", false |
||||
} |
||||
|
||||
return filepath.ToSlash(dir[len(root):]), true |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
// sameFile returns true if x and y have the same basename and denote
|
||||
// the same file.
|
||||
//
|
||||
func sameFile(x, y string) bool { |
||||
if path.Clean(x) == path.Clean(y) { |
||||
return true |
||||
} |
||||
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||
if xi, err := os.Stat(x); err == nil { |
||||
if yi, err := os.Stat(y); err == nil { |
||||
return os.SameFile(xi, yi) |
||||
} |
||||
} |
||||
} |
||||
return false |
||||
} |
@ -0,0 +1,207 @@ |
||||
// Copyright 2013 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 loader |
||||
|
||||
// This file handles cgo preprocessing of files containing `import "C"`.
|
||||
//
|
||||
// DESIGN
|
||||
//
|
||||
// The approach taken is to run the cgo processor on the package's
|
||||
// CgoFiles and parse the output, faking the filenames of the
|
||||
// resulting ASTs so that the synthetic file containing the C types is
|
||||
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
|
||||
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
|
||||
// not the names of the actual temporary files.
|
||||
//
|
||||
// The advantage of this approach is its fidelity to 'go build'. The
|
||||
// downside is that the token.Position.Offset for each AST node is
|
||||
// incorrect, being an offset within the temporary file. Line numbers
|
||||
// should still be correct because of the //line comments.
|
||||
//
|
||||
// The logic of this file is mostly plundered from the 'go build'
|
||||
// tool, which also invokes the cgo preprocessor.
|
||||
//
|
||||
//
|
||||
// REJECTED ALTERNATIVE
|
||||
//
|
||||
// An alternative approach that we explored is to extend go/types'
|
||||
// Importer mechanism to provide the identity of the importing package
|
||||
// so that each time `import "C"` appears it resolves to a different
|
||||
// synthetic package containing just the objects needed in that case.
|
||||
// The loader would invoke cgo but parse only the cgo_types.go file
|
||||
// defining the package-level objects, discarding the other files
|
||||
// resulting from preprocessing.
|
||||
//
|
||||
// The benefit of this approach would have been that source-level
|
||||
// syntax information would correspond exactly to the original cgo
|
||||
// file, with no preprocessing involved, making source tools like
|
||||
// godoc, guru, and eg happy. However, the approach was rejected
|
||||
// due to the additional complexity it would impose on go/types. (It
|
||||
// made for a beautiful demo, though.)
|
||||
//
|
||||
// cgo files, despite their *.go extension, are not legal Go source
|
||||
// files per the specification since they may refer to unexported
|
||||
// members of package "C" such as C.int. Also, a function such as
|
||||
// C.getpwent has in effect two types, one matching its C type and one
|
||||
// which additionally returns (errno C.int). The cgo preprocessor
|
||||
// uses name mangling to distinguish these two functions in the
|
||||
// processed code, but go/types would need to duplicate this logic in
|
||||
// its handling of function calls, analogous to the treatment of map
|
||||
// lookups in which y=m[k] and y,ok=m[k] are both legal.
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/build" |
||||
"go/parser" |
||||
"go/token" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses
|
||||
// the output and returns the resulting ASTs.
|
||||
//
|
||||
func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) { |
||||
tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer os.RemoveAll(tmpdir) |
||||
|
||||
pkgdir := bp.Dir |
||||
if DisplayPath != nil { |
||||
pkgdir = DisplayPath(pkgdir) |
||||
} |
||||
|
||||
cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var files []*ast.File |
||||
for i := range cgoFiles { |
||||
rd, err := os.Open(cgoFiles[i]) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
display := filepath.Join(bp.Dir, cgoDisplayFiles[i]) |
||||
f, err := parser.ParseFile(fset, display, rd, mode) |
||||
rd.Close() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
files = append(files, f) |
||||
} |
||||
return files, nil |
||||
} |
||||
|
||||
var cgoRe = regexp.MustCompile(`[/\\:]`) |
||||
|
||||
// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
|
||||
// lists of files: the resulting processed files (in temporary
|
||||
// directory tmpdir) and the corresponding names of the unprocessed files.
|
||||
//
|
||||
// runCgo is adapted from (*builder).cgo in
|
||||
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
|
||||
// Objective C, CGOPKGPATH, CGO_FLAGS.
|
||||
//
|
||||
func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) { |
||||
cgoCPPFLAGS, _, _, _ := cflags(bp, true) |
||||
_, cgoexeCFLAGS, _, _ := cflags(bp, false) |
||||
|
||||
if len(bp.CgoPkgConfig) > 0 { |
||||
pcCFLAGS, err := pkgConfigFlags(bp) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...) |
||||
} |
||||
|
||||
// Allows including _cgo_export.h from .[ch] files in the package.
|
||||
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir) |
||||
|
||||
// _cgo_gotypes.go (displayed "C") contains the type definitions.
|
||||
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go")) |
||||
displayFiles = append(displayFiles, "C") |
||||
for _, fn := range bp.CgoFiles { |
||||
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
|
||||
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_") |
||||
files = append(files, filepath.Join(tmpdir, f+"cgo1.go")) |
||||
displayFiles = append(displayFiles, fn) |
||||
} |
||||
|
||||
var cgoflags []string |
||||
if bp.Goroot && bp.ImportPath == "runtime/cgo" { |
||||
cgoflags = append(cgoflags, "-import_runtime_cgo=false") |
||||
} |
||||
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" { |
||||
cgoflags = append(cgoflags, "-import_syscall=false") |
||||
} |
||||
|
||||
args := stringList( |
||||
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--", |
||||
cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles, |
||||
) |
||||
if false { |
||||
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir) |
||||
} |
||||
cmd := exec.Command(args[0], args[1:]...) |
||||
cmd.Dir = pkgdir |
||||
cmd.Stdout = os.Stderr |
||||
cmd.Stderr = os.Stderr |
||||
if err := cmd.Run(); err != nil { |
||||
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err) |
||||
} |
||||
|
||||
return files, displayFiles, nil |
||||
} |
||||
|
||||
// -- unmodified from 'go build' ---------------------------------------
|
||||
|
||||
// Return the flags to use when invoking the C or C++ compilers, or cgo.
|
||||
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { |
||||
var defaults string |
||||
if def { |
||||
defaults = "-g -O2" |
||||
} |
||||
|
||||
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) |
||||
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) |
||||
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) |
||||
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) |
||||
return |
||||
} |
||||
|
||||
// envList returns the value of the given environment variable broken
|
||||
// into fields, using the default value when the variable is empty.
|
||||
func envList(key, def string) []string { |
||||
v := os.Getenv(key) |
||||
if v == "" { |
||||
v = def |
||||
} |
||||
return strings.Fields(v) |
||||
} |
||||
|
||||
// stringList's arguments should be a sequence of string or []string values.
|
||||
// stringList flattens them into a single []string.
|
||||
func stringList(args ...interface{}) []string { |
||||
var x []string |
||||
for _, arg := range args { |
||||
switch arg := arg.(type) { |
||||
case []string: |
||||
x = append(x, arg...) |
||||
case string: |
||||
x = append(x, arg) |
||||
default: |
||||
panic("stringList: invalid argument") |
||||
} |
||||
} |
||||
return x |
||||
} |
@ -0,0 +1,39 @@ |
||||
// Copyright 2013 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 loader |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"go/build" |
||||
"os/exec" |
||||
"strings" |
||||
) |
||||
|
||||
// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints.
|
||||
func pkgConfig(mode string, pkgs []string) (flags []string, err error) { |
||||
cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...) |
||||
out, err := cmd.CombinedOutput() |
||||
if err != nil { |
||||
s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err) |
||||
if len(out) > 0 { |
||||
s = fmt.Sprintf("%s: %s", s, out) |
||||
} |
||||
return nil, errors.New(s) |
||||
} |
||||
if len(out) > 0 { |
||||
flags = strings.Fields(string(out)) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// pkgConfigFlags calls pkg-config if needed and returns the cflags
|
||||
// needed to build the package.
|
||||
func pkgConfigFlags(p *build.Package) (cflags []string, err error) { |
||||
if len(p.CgoPkgConfig) == 0 { |
||||
return nil, nil |
||||
} |
||||
return pkgConfig("--cflags", p.CgoPkgConfig) |
||||
} |
@ -0,0 +1,205 @@ |
||||
// Copyright 2015 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 loader loads a complete Go program from source code, parsing
|
||||
// and type-checking the initial packages plus their transitive closure
|
||||
// of dependencies. The ASTs and the derived facts are retained for
|
||||
// later use.
|
||||
//
|
||||
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
|
||||
//
|
||||
// The package defines two primary types: Config, which specifies a
|
||||
// set of initial packages to load and various other options; and
|
||||
// Program, which is the result of successfully loading the packages
|
||||
// specified by a configuration.
|
||||
//
|
||||
// The configuration can be set directly, but *Config provides various
|
||||
// convenience methods to simplify the common cases, each of which can
|
||||
// be called any number of times. Finally, these are followed by a
|
||||
// call to Load() to actually load and type-check the program.
|
||||
//
|
||||
// var conf loader.Config
|
||||
//
|
||||
// // Use the command-line arguments to specify
|
||||
// // a set of initial packages to load from source.
|
||||
// // See FromArgsUsage for help.
|
||||
// rest, err := conf.FromArgs(os.Args[1:], wantTests)
|
||||
//
|
||||
// // Parse the specified files and create an ad hoc package with path "foo".
|
||||
// // All files must have the same 'package' declaration.
|
||||
// conf.CreateFromFilenames("foo", "foo.go", "bar.go")
|
||||
//
|
||||
// // Create an ad hoc package with path "foo" from
|
||||
// // the specified already-parsed files.
|
||||
// // All ASTs must have the same 'package' declaration.
|
||||
// conf.CreateFromFiles("foo", parsedFiles)
|
||||
//
|
||||
// // Add "runtime" to the set of packages to be loaded.
|
||||
// conf.Import("runtime")
|
||||
//
|
||||
// // Adds "fmt" and "fmt_test" to the set of packages
|
||||
// // to be loaded. "fmt" will include *_test.go files.
|
||||
// conf.ImportWithTests("fmt")
|
||||
//
|
||||
// // Finally, load all the packages specified by the configuration.
|
||||
// prog, err := conf.Load()
|
||||
//
|
||||
// See examples_test.go for examples of API usage.
|
||||
//
|
||||
//
|
||||
// CONCEPTS AND TERMINOLOGY
|
||||
//
|
||||
// The WORKSPACE is the set of packages accessible to the loader. The
|
||||
// workspace is defined by Config.Build, a *build.Context. The
|
||||
// default context treats subdirectories of $GOROOT and $GOPATH as
|
||||
// packages, but this behavior may be overridden.
|
||||
//
|
||||
// An AD HOC package is one specified as a set of source files on the
|
||||
// command line. In the simplest case, it may consist of a single file
|
||||
// such as $GOROOT/src/net/http/triv.go.
|
||||
//
|
||||
// EXTERNAL TEST packages are those comprised of a set of *_test.go
|
||||
// files all with the same 'package foo_test' declaration, all in the
|
||||
// same directory. (go/build.Package calls these files XTestFiles.)
|
||||
//
|
||||
// An IMPORTABLE package is one that can be referred to by some import
|
||||
// spec. Every importable package is uniquely identified by its
|
||||
// PACKAGE PATH or just PATH, a string such as "fmt", "encoding/json",
|
||||
// or "cmd/vendor/golang.org/x/arch/x86/x86asm". A package path
|
||||
// typically denotes a subdirectory of the workspace.
|
||||
//
|
||||
// An import declaration uses an IMPORT PATH to refer to a package.
|
||||
// Most import declarations use the package path as the import path.
|
||||
//
|
||||
// Due to VENDORING (https://golang.org/s/go15vendor), the
|
||||
// interpretation of an import path may depend on the directory in which
|
||||
// it appears. To resolve an import path to a package path, go/build
|
||||
// must search the enclosing directories for a subdirectory named
|
||||
// "vendor".
|
||||
//
|
||||
// ad hoc packages and external test packages are NON-IMPORTABLE. The
|
||||
// path of an ad hoc package is inferred from the package
|
||||
// declarations of its files and is therefore not a unique package key.
|
||||
// For example, Config.CreatePkgs may specify two initial ad hoc
|
||||
// packages, both with path "main".
|
||||
//
|
||||
// An AUGMENTED package is an importable package P plus all the
|
||||
// *_test.go files with same 'package foo' declaration as P.
|
||||
// (go/build.Package calls these files TestFiles.)
|
||||
//
|
||||
// The INITIAL packages are those specified in the configuration. A
|
||||
// DEPENDENCY is a package loaded to satisfy an import in an initial
|
||||
// package or another dependency.
|
||||
//
|
||||
package loader |
||||
|
||||
// IMPLEMENTATION NOTES
|
||||
//
|
||||
// 'go test', in-package test files, and import cycles
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// An external test package may depend upon members of the augmented
|
||||
// package that are not in the unaugmented package, such as functions
|
||||
// that expose internals. (See bufio/export_test.go for an example.)
|
||||
// So, the loader must ensure that for each external test package
|
||||
// it loads, it also augments the corresponding non-test package.
|
||||
//
|
||||
// The import graph over n unaugmented packages must be acyclic; the
|
||||
// import graph over n-1 unaugmented packages plus one augmented
|
||||
// package must also be acyclic. ('go test' relies on this.) But the
|
||||
// import graph over n augmented packages may contain cycles.
|
||||
//
|
||||
// First, all the (unaugmented) non-test packages and their
|
||||
// dependencies are imported in the usual way; the loader reports an
|
||||
// error if it detects an import cycle.
|
||||
//
|
||||
// Then, each package P for which testing is desired is augmented by
|
||||
// the list P' of its in-package test files, by calling
|
||||
// (*types.Checker).Files. This arrangement ensures that P' may
|
||||
// reference definitions within P, but P may not reference definitions
|
||||
// within P'. Furthermore, P' may import any other package, including
|
||||
// ones that depend upon P, without an import cycle error.
|
||||
//
|
||||
// Consider two packages A and B, both of which have lists of
|
||||
// in-package test files we'll call A' and B', and which have the
|
||||
// following import graph edges:
|
||||
// B imports A
|
||||
// B' imports A
|
||||
// A' imports B
|
||||
// This last edge would be expected to create an error were it not
|
||||
// for the special type-checking discipline above.
|
||||
// Cycles of size greater than two are possible. For example:
|
||||
// compress/bzip2/bzip2_test.go (package bzip2) imports "io/ioutil"
|
||||
// io/ioutil/tempfile_test.go (package ioutil) imports "regexp"
|
||||
// regexp/exec_test.go (package regexp) imports "compress/bzip2"
|
||||
//
|
||||
//
|
||||
// Concurrency
|
||||
// -----------
|
||||
//
|
||||
// Let us define the import dependency graph as follows. Each node is a
|
||||
// list of files passed to (Checker).Files at once. Many of these lists
|
||||
// are the production code of an importable Go package, so those nodes
|
||||
// are labelled by the package's path. The remaining nodes are
|
||||
// ad hoc packages and lists of in-package *_test.go files that augment
|
||||
// an importable package; those nodes have no label.
|
||||
//
|
||||
// The edges of the graph represent import statements appearing within a
|
||||
// file. An edge connects a node (a list of files) to the node it
|
||||
// imports, which is importable and thus always labelled.
|
||||
//
|
||||
// Loading is controlled by this dependency graph.
|
||||
//
|
||||
// To reduce I/O latency, we start loading a package's dependencies
|
||||
// asynchronously as soon as we've parsed its files and enumerated its
|
||||
// imports (scanImports). This performs a preorder traversal of the
|
||||
// import dependency graph.
|
||||
//
|
||||
// To exploit hardware parallelism, we type-check unrelated packages in
|
||||
// parallel, where "unrelated" means not ordered by the partial order of
|
||||
// the import dependency graph.
|
||||
//
|
||||
// We use a concurrency-safe non-blocking cache (importer.imported) to
|
||||
// record the results of type-checking, whether success or failure. An
|
||||
// entry is created in this cache by startLoad the first time the
|
||||
// package is imported. The first goroutine to request an entry becomes
|
||||
// responsible for completing the task and broadcasting completion to
|
||||
// subsequent requestors, which block until then.
|
||||
//
|
||||
// Type checking occurs in (parallel) postorder: we cannot type-check a
|
||||
// set of files until we have loaded and type-checked all of their
|
||||
// immediate dependencies (and thus all of their transitive
|
||||
// dependencies). If the input were guaranteed free of import cycles,
|
||||
// this would be trivial: we could simply wait for completion of the
|
||||
// dependencies and then invoke the typechecker.
|
||||
//
|
||||
// But as we saw in the 'go test' section above, some cycles in the
|
||||
// import graph over packages are actually legal, so long as the
|
||||
// cycle-forming edge originates in the in-package test files that
|
||||
// augment the package. This explains why the nodes of the import
|
||||
// dependency graph are not packages, but lists of files: the unlabelled
|
||||
// nodes avoid the cycles. Consider packages A and B where B imports A
|
||||
// and A's in-package tests AT import B. The naively constructed import
|
||||
// graph over packages would contain a cycle (A+AT) --> B --> (A+AT) but
|
||||
// the graph over lists of files is AT --> B --> A, where AT is an
|
||||
// unlabelled node.
|
||||
//
|
||||
// Awaiting completion of the dependencies in a cyclic graph would
|
||||
// deadlock, so we must materialize the import dependency graph (as
|
||||
// importer.graph) and check whether each import edge forms a cycle. If
|
||||
// x imports y, and the graph already contains a path from y to x, then
|
||||
// there is an import cycle, in which case the processing of x must not
|
||||
// wait for the completion of processing of y.
|
||||
//
|
||||
// When the type-checker makes a callback (doImport) to the loader for a
|
||||
// given import edge, there are two possible cases. In the normal case,
|
||||
// the dependency has already been completely type-checked; doImport
|
||||
// does a cache lookup and returns it. In the cyclic case, the entry in
|
||||
// the cache is still necessarily incomplete, indicating a cycle. We
|
||||
// perform the cycle check again to obtain the error message, and return
|
||||
// the error.
|
||||
//
|
||||
// The result of using concurrency is about a 2.5x speedup for stdlib_test.
|
||||
|
||||
// TODO(adonovan): overhaul the package documentation.
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,124 @@ |
||||
// Copyright 2013 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 loader |
||||
|
||||
import ( |
||||
"go/ast" |
||||
"go/build" |
||||
"go/parser" |
||||
"go/token" |
||||
"io" |
||||
"os" |
||||
"strconv" |
||||
"sync" |
||||
|
||||
"golang.org/x/tools/go/buildutil" |
||||
) |
||||
|
||||
// We use a counting semaphore to limit
|
||||
// the number of parallel I/O calls per process.
|
||||
var ioLimit = make(chan bool, 10) |
||||
|
||||
// parseFiles parses the Go source files within directory dir and
|
||||
// returns the ASTs of the ones that could be at least partially parsed,
|
||||
// along with a list of I/O and parse errors encountered.
|
||||
//
|
||||
// I/O is done via ctxt, which may specify a virtual file system.
|
||||
// displayPath is used to transform the filenames attached to the ASTs.
|
||||
//
|
||||
func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { |
||||
if displayPath == nil { |
||||
displayPath = func(path string) string { return path } |
||||
} |
||||
var wg sync.WaitGroup |
||||
n := len(files) |
||||
parsed := make([]*ast.File, n) |
||||
errors := make([]error, n) |
||||
for i, file := range files { |
||||
if !buildutil.IsAbsPath(ctxt, file) { |
||||
file = buildutil.JoinPath(ctxt, dir, file) |
||||
} |
||||
wg.Add(1) |
||||
go func(i int, file string) { |
||||
ioLimit <- true // wait
|
||||
defer func() { |
||||
wg.Done() |
||||
<-ioLimit // signal
|
||||
}() |
||||
var rd io.ReadCloser |
||||
var err error |
||||
if ctxt.OpenFile != nil { |
||||
rd, err = ctxt.OpenFile(file) |
||||
} else { |
||||
rd, err = os.Open(file) |
||||
} |
||||
if err != nil { |
||||
errors[i] = err // open failed
|
||||
return |
||||
} |
||||
|
||||
// ParseFile may return both an AST and an error.
|
||||
parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) |
||||
rd.Close() |
||||
}(i, file) |
||||
} |
||||
wg.Wait() |
||||
|
||||
// Eliminate nils, preserving order.
|
||||
var o int |
||||
for _, f := range parsed { |
||||
if f != nil { |
||||
parsed[o] = f |
||||
o++ |
||||
} |
||||
} |
||||
parsed = parsed[:o] |
||||
|
||||
o = 0 |
||||
for _, err := range errors { |
||||
if err != nil { |
||||
errors[o] = err |
||||
o++ |
||||
} |
||||
} |
||||
errors = errors[:o] |
||||
|
||||
return parsed, errors |
||||
} |
||||
|
||||
// scanImports returns the set of all import paths from all
|
||||
// import specs in the specified files.
|
||||
func scanImports(files []*ast.File) map[string]bool { |
||||
imports := make(map[string]bool) |
||||
for _, f := range files { |
||||
for _, decl := range f.Decls { |
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { |
||||
for _, spec := range decl.Specs { |
||||
spec := spec.(*ast.ImportSpec) |
||||
|
||||
// NB: do not assume the program is well-formed!
|
||||
path, err := strconv.Unquote(spec.Path.Value) |
||||
if err != nil { |
||||
continue // quietly ignore the error
|
||||
} |
||||
if path == "C" { |
||||
continue // skip pseudopackage
|
||||
} |
||||
imports[path] = true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return imports |
||||
} |
||||
|
||||
// ---------- Internal helpers ----------
|
||||
|
||||
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
|
||||
func tokenFileContainsPos(f *token.File, pos token.Pos) bool { |
||||
p := int(pos) |
||||
base := f.Base() |
||||
return base <= p && p < base+f.Size() |
||||
} |
Loading…
Reference in new issue