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