1
0
mirror of https://github.com/golang/go synced 2024-11-22 04:34:39 -07:00

go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package

This is an API change, but one I have been promising would
happen when it was clear what the go command needed.

This is basically a complete replacement of what used to be here.

build.Tree is gone.

build.DirInfo is expanded and now called build.Package.

build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.

build.ScanDir is now build.ImportDir.

build.FindTree+build.ScanDir is now build.Import.

The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH.  They will come back
with less information in the Package, but they will still work.

The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used.  Path is gone, so it can no longer be misused.  (Fixes issue 2749.)

This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.

R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
This commit is contained in:
Russ Cox 2012-03-01 12:12:09 -05:00
parent a72b87efa9
commit ebe1664d27
24 changed files with 501 additions and 513 deletions

View File

@ -74,16 +74,10 @@ func main() {
pkgs = strings.Fields(string(stds))
}
tree, _, err := build.FindTree("os") // some known package
if err != nil {
log.Fatalf("failed to find tree: %v", err)
}
var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
for _, context := range contexts {
w := NewWalker()
w.context = context
w.tree = tree
for _, pkg := range pkgs {
w.wantedPkg[pkg] = true
@ -95,7 +89,7 @@ func main() {
strings.HasPrefix(pkg, "old/") {
continue
}
if !tree.HasSrc(pkg) {
if fi, err := os.Stat(filepath.Join(w.root, pkg)); err != nil || !fi.IsDir() {
log.Fatalf("no source in tree for package %q", pkg)
}
w.WalkPackage(pkg)
@ -165,7 +159,7 @@ type pkgSymbol struct {
type Walker struct {
context *build.Context
tree *build.Tree
root string
fset *token.FileSet
scope []string
features map[string]bool // set
@ -191,6 +185,7 @@ func NewWalker() *Walker {
selectorFullPkg: make(map[string]string),
wantedPkg: make(map[string]bool),
prevConstType: make(map[pkgSymbol]string),
root: filepath.Join(build.Default.GOROOT, "src/pkg"),
}
}
@ -252,15 +247,13 @@ func (w *Walker) WalkPackage(name string) {
defer func() {
w.packageState[name] = loaded
}()
dir := filepath.Join(w.tree.SrcDir(), filepath.FromSlash(name))
dir := filepath.Join(w.root, filepath.FromSlash(name))
var info *build.DirInfo
var err error
if ctx := w.context; ctx != nil {
info, err = ctx.ScanDir(dir)
} else {
info, err = build.ScanDir(dir)
ctxt := w.context
if ctxt == nil {
ctxt = &build.Default
}
info, err := ctxt.ImportDir(dir, 0)
if err != nil {
if strings.Contains(err.Error(), "no Go source files") {
return

View File

@ -7,7 +7,6 @@ package main
import (
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"path/filepath"
@ -36,7 +35,7 @@ func TestGolden(t *testing.T) {
w := NewWalker()
w.wantedPkg[fi.Name()] = true
w.tree = &build.Tree{Path: "testdata", Goroot: true}
w.root = "testdata/src/pkg"
goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt")
w.WalkPackage(fi.Name())

View File

@ -15,6 +15,7 @@ import (
"go/printer"
"go/token"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
@ -90,11 +91,11 @@ var (
func initHandlers() {
paths := filepath.SplitList(*pkgPath)
for _, t := range build.Path {
if t.Goroot {
continue
gorootSrc := filepath.Join(build.Default.GOROOT, "src", "pkg")
for _, p := range build.Default.SrcDirs() {
if p != gorootSrc {
paths = append(paths, p)
}
paths = append(paths, t.SrcDir())
}
fsMap.Init(paths)
@ -1002,11 +1003,13 @@ func fsReadDir(dir string) ([]os.FileInfo, error) {
return fs.ReadDir(dir)
}
// fsReadFile implements ReadFile for the go/build package.
func fsReadFile(dir, name string) (path string, data []byte, err error) {
path = filepath.Join(dir, name)
data, err = ReadFile(fs, path)
return
// fsOpenFile implements OpenFile for the go/build package.
func fsOpenFile(name string) (r io.ReadCloser, err error) {
data, err := ReadFile(fs, name)
if err != nil {
return nil, err
}
return ioutil.NopCloser(bytes.NewReader(data)), nil
}
func inList(name string, list []string) bool {
@ -1039,10 +1042,11 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
// To use different pair, such as if we allowed the user
// to choose, set ctxt.GOOS and ctxt.GOARCH before
// calling ctxt.ScanDir.
ctxt := build.DefaultContext
ctxt := build.Default
ctxt.IsAbsPath = path.IsAbs
ctxt.ReadDir = fsReadDir
ctxt.ReadFile = fsReadFile
dir, err := ctxt.ScanDir(abspath)
ctxt.OpenFile = fsOpenFile
dir, err := ctxt.ImportDir(abspath, 0)
if err == nil {
pkgFiles = append(dir.GoFiles, dir.CgoFiles...)
}

View File

@ -388,9 +388,9 @@ func main() {
}
relpath := path
abspath := path
if t, pkg, err := build.FindTree(path); err == nil {
relpath = pkg
abspath = filepath.Join(t.SrcDir(), pkg)
if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
relpath = bp.ImportPath
abspath = bp.Dir
} else if !filepath.IsAbs(path) {
abspath = absolutePath(path, pkgHandler.fsRoot)
} else {

View File

@ -18,6 +18,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"text/scanner"
)
@ -39,11 +40,14 @@ func findPkg(path string) (filename, id string) {
switch path[0] {
default:
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
tree, pkg, err := build.FindTree(path)
if err != nil {
bp, _ := build.Import(path, "", build.FindOnly)
if bp.PkgObj == "" {
return
}
noext = filepath.Join(tree.PkgDir(), pkg)
noext = bp.PkgObj
if strings.HasSuffix(noext, ".a") {
noext = noext[:len(noext)-2]
}
case '.':
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
@ -742,7 +746,7 @@ func (p *gcParser) parseVarDecl() {
}
// FuncBody = "{" ... "}" .
//
//
func (p *gcParser) parseFuncBody() {
p.expect('{')
for i := 1; i > 0; p.next() {

View File

@ -48,7 +48,7 @@
// To build a file only when using cgo, and only on Linux and OS X:
//
// // +build linux,cgo darwin,cgo
//
//
// Such a file is usually paired with another file implementing the
// default functionality for other systems, which in this case would
// carry the constraint:
@ -66,12 +66,14 @@ import (
"errors"
"fmt"
"go/ast"
"go/doc"
"go/parser"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"path"
pathpkg "path"
"path/filepath"
"runtime"
"sort"
@ -84,56 +86,186 @@ import (
type Context struct {
GOARCH string // target architecture
GOOS string // target operating system
GOROOT string // Go root
GOPATH string // Go path
CgoEnabled bool // whether cgo can be used
BuildTags []string // additional tags to recognize in +build lines
UseAllFiles bool // use files regardless of +build lines, file names
Gccgo bool // assume use of gccgo when computing object paths
// By default, ScanDir uses the operating system's
// file system calls to read directories and files.
// Callers can override those calls to provide other
// ways to read data by setting ReadDir and ReadFile.
// ScanDir does not make any assumptions about the
// format of the strings dir and file: they can be
// slash-separated, backslash-separated, even URLs.
// By default, Import uses the operating system's file system calls
// to read directories and files. To read from other sources,
// callers can set the following functions. They all have default
// behaviors that use the local file system, so clients need only set
// the functions whose behaviors they wish to change.
// JoinPath joins the sequence of path fragments into a single path.
// If JoinPath is nil, Import uses filepath.Join.
JoinPath func(elem ...string) string
// SplitPathList splits the path list into a slice of individual paths.
// If SplitPathList is nil, Import uses filepath.SplitList.
SplitPathList func(list string) []string
// IsAbsPath reports whether path is an absolute path.
// If IsAbsPath is nil, Import uses filepath.IsAbs.
IsAbsPath func(path string) bool
// IsDir reports whether the path names a directory.
// If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
IsDir func(path string) bool
// HasSubdir reports whether dir is a subdirectory of
// (perhaps multiple levels below) root.
// If so, HasSubdir sets rel to a slash-separated path that
// can be joined to root to produce a path equivalent to dir.
// If HasSubdir is nil, Import uses an implementation built on
// filepath.EvalSymlinks.
HasSubdir func(root, dir string) (rel string, ok bool)
// ReadDir returns a slice of os.FileInfo, sorted by Name,
// describing the content of the named directory.
// The dir argument is the argument to ScanDir.
// If ReadDir is nil, ScanDir uses io.ReadDir.
// If ReadDir is nil, Import uses io.ReadDir.
ReadDir func(dir string) (fi []os.FileInfo, err error)
// ReadFile returns the content of the file named file
// in the directory named dir. The dir argument is the
// argument to ScanDir, and the file argument is the
// Name field from an os.FileInfo returned by ReadDir.
// The returned path is the full name of the file, to be
// used in error messages.
//
// If ReadFile is nil, ScanDir uses filepath.Join(dir, file)
// as the path and ioutil.ReadFile to read the data.
ReadFile func(dir, file string) (path string, content []byte, err error)
// OpenFile opens a file (not a directory) for reading.
// If OpenFile is nil, Import uses os.Open.
OpenFile func(path string) (r io.ReadCloser, err error)
}
func (ctxt *Context) readDir(dir string) ([]os.FileInfo, error) {
// joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join.
func (ctxt *Context) joinPath(elem ...string) string {
if f := ctxt.JoinPath; f != nil {
return f(elem...)
}
return filepath.Join(elem...)
}
// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList.
func (ctxt *Context) splitPathList(s string) []string {
if f := ctxt.SplitPathList; f != nil {
return f(s)
}
return filepath.SplitList(s)
}
// isAbsPath calls ctxt.IsAbsSPath (if not nil) or else filepath.IsAbs.
func (ctxt *Context) isAbsPath(path string) bool {
if f := ctxt.IsAbsPath; f != nil {
return f(path)
}
return filepath.IsAbs(path)
}
// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat.
func (ctxt *Context) isDir(path string) bool {
if f := ctxt.IsDir; f != nil {
return f(path)
}
fi, err := os.Stat(path)
return err == nil && fi.IsDir()
}
// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
// the local file system to answer the question.
func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) {
if f := ctxt.HasSubdir; f != nil {
return f(root, dir)
}
if p, err := filepath.EvalSymlinks(root); err == nil {
root = p
}
if p, err := filepath.EvalSymlinks(dir); err == nil {
dir = p
}
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
}
// readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir.
func (ctxt *Context) readDir(path string) ([]os.FileInfo, error) {
if f := ctxt.ReadDir; f != nil {
return f(dir)
return f(path)
}
return ioutil.ReadDir(dir)
return ioutil.ReadDir(path)
}
func (ctxt *Context) readFile(dir, file string) (string, []byte, error) {
if f := ctxt.ReadFile; f != nil {
return f(dir, file)
// openFile calls ctxt.OpenFile (if not nil) or else os.Open.
func (ctxt *Context) openFile(path string) (io.ReadCloser, error) {
if fn := ctxt.OpenFile; fn != nil {
return fn(path)
}
p := filepath.Join(dir, file)
content, err := ioutil.ReadFile(p)
return p, content, err
f, err := os.Open(path)
if err != nil {
return nil, err // nil interface
}
return f, nil
}
// The DefaultContext is the default Context for builds.
// It uses the GOARCH and GOOS environment variables
// if set, or else the compiled code's GOARCH and GOOS.
var DefaultContext Context = defaultContext()
// isFile determines whether path is a file by trying to open it.
// It reuses openFile instead of adding another function to the
// list in Context.
func (ctxt *Context) isFile(path string) bool {
f, err := ctxt.openFile(path)
if err != nil {
return false
}
f.Close()
return true
}
// gopath returns the list of Go path directories.
func (ctxt *Context) gopath() []string {
var all []string
for _, p := range ctxt.splitPathList(ctxt.GOPATH) {
if p == "" || p == ctxt.GOROOT {
// Empty paths are uninteresting.
// If the path is the GOROOT, ignore it.
// People sometimes set GOPATH=$GOROOT, which is useless
// but would cause us to find packages with import paths
// like "pkg/math".
// Do not get confused by this common mistake.
continue
}
all = append(all, p)
}
return all
}
// SrcDirs returns a list of package source root directories.
// It draws from the current Go root and Go path but omits directories
// that do not exist.
func (ctxt *Context) SrcDirs() []string {
var all []string
if ctxt.GOROOT != "" {
dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg")
if ctxt.isDir(dir) {
all = append(all, dir)
}
}
for _, p := range ctxt.gopath() {
dir := ctxt.joinPath(p, "src")
if ctxt.isDir(dir) {
all = append(all, dir)
}
}
return all
}
// Default is the default Context for builds.
// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables
// if set, or else the compiled code's GOARCH, GOOS, and GOROOT.
var Default Context = defaultContext()
var cgoEnabled = map[string]bool{
"darwin/386": true,
@ -151,9 +283,10 @@ func defaultContext() Context {
c.GOARCH = envOr("GOARCH", runtime.GOARCH)
c.GOOS = envOr("GOOS", runtime.GOOS)
c.GOROOT = runtime.GOROOT()
c.GOPATH = envOr("GOPATH", "")
s := os.Getenv("CGO_ENABLED")
switch s {
switch os.Getenv("CGO_ENABLED") {
case "1":
c.CgoEnabled = true
case "0":
@ -173,66 +306,203 @@ func envOr(name, def string) string {
return s
}
type DirInfo struct {
Package string // Name of package in dir
PackageComment *ast.CommentGroup // Package comments from GoFiles
ImportPath string // Import path of package in dir
Imports []string // All packages imported by GoFiles
ImportPos map[string][]token.Position // Source code location of imports
// An ImportMode controls the behavior of the Import method.
type ImportMode uint
const (
// If FindOnly is set, Import stops after locating the directory
// that should contain the sources for a package. It does not
// read any files in the directory.
FindOnly ImportMode = 1 << iota
// If AllowBinary is set, Import can be satisfied by a compiled
// package object without corresponding sources.
AllowBinary
)
// A Package describes the Go package found in a directory.
type Package struct {
Dir string // directory containing package sources
Name string // package name
Doc string // documentation synopsis
ImportPath string // import path of package ("" if unknown)
Root string // root of Go tree where this package lives
SrcRoot string // package source root directory ("" if unknown)
PkgRoot string // package install root directory ("" if unknown)
BinDir string // command install directory ("" if unknown)
Goroot bool // package found in Go root
PkgObj string // installed .a file
// Source files
GoFiles []string // .go files in dir (excluding CgoFiles, TestGoFiles, XTestGoFiles)
HFiles []string // .h files in dir
CFiles []string // .c files in dir
SFiles []string // .s (and, when using cgo, .S files in dir)
CgoFiles []string // .go files that import "C"
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string // .go source files that import "C"
CFiles []string // .c source files
HFiles []string // .h source files
SFiles []string // .s source files
// Cgo directives
CgoPkgConfig []string // Cgo pkg-config directives
CgoCFLAGS []string // Cgo CFLAGS directives
CgoLDFLAGS []string // Cgo LDFLAGS directives
// Dependency information
Imports []string // imports from GoFiles, CgoFiles
ImportPos map[string][]token.Position // line information for Imports
// Test information
TestGoFiles []string // _test.go files in package
XTestGoFiles []string // _test.go files outside package
TestImports []string // All packages imported by (X)TestGoFiles
TestImportPos map[string][]token.Position
TestGoFiles []string // _test.go files in package
TestImports []string // imports from TestGoFiles
TestImportPos map[string][]token.Position // line information for TestImports
XTestGoFiles []string // _test.go files outside package
XTestImports []string // imports from XTestGoFiles
XTestImportPos map[string][]token.Position // line information for XTestImports
}
func (d *DirInfo) IsCommand() bool {
// TODO(rsc): This is at least a little bogus.
return d.Package == "main"
// IsCommand reports whether the package is considered a
// command to be installed (not just a library).
// Packages named "main" are treated as commands.
func (p *Package) IsCommand() bool {
return p.Name == "main"
}
// ScanDir calls DefaultContext.ScanDir.
func ScanDir(dir string) (info *DirInfo, err error) {
return DefaultContext.ScanDir(dir)
// ImportDir is like Import but processes the Go package found in
// the named directory.
func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) {
return ctxt.Import(".", dir, mode)
}
// TODO(rsc): Move this comment to a more appropriate place.
// ScanDir returns a structure with details about the Go package
// found in the given directory.
// Import returns details about the Go package named by the import path,
// interpreting local import paths relative to the src directory. If the path
// is a local import path naming a package that can be imported using a
// standard import path, the returned package will set p.ImportPath to
// that path.
//
// Most .go, .c, .h, and .s files in the directory are considered part
// of the package. The exceptions are:
// In the directory containing the package, .go, .c, .h, and .s files are
// considered part of the package except for:
//
// - .go files in package main (unless no other package is found)
// - .go files in package documentation
// - files starting with _ or .
// - files with build constraints not satisfied by the context
//
func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
dirs, err := ctxt.readDir(dir)
// If an error occurs, Import returns a non-nil error also returns a non-nil
// *Package containing partial information.
//
func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error) {
p := &Package{
ImportPath: path,
}
var pkga string
if ctxt.Gccgo {
dir, elem := pathpkg.Split(p.ImportPath)
pkga = "pkg/gccgo/" + dir + "lib" + elem + ".a"
} else {
pkga = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + "/" + p.ImportPath + ".a"
}
binaryOnly := false
if IsLocalImport(path) {
if src == "" {
return p, fmt.Errorf("import %q: import relative to unknown directory", path)
}
if !ctxt.isAbsPath(path) {
p.Dir = ctxt.joinPath(src, path)
}
// Determine canonical import path, if any.
if ctxt.GOROOT != "" {
root := ctxt.joinPath(ctxt.GOROOT, "src", "pkg")
if sub, ok := ctxt.hasSubdir(root, p.Dir); ok {
p.Goroot = true
p.ImportPath = sub
p.Root = ctxt.GOROOT
goto Found
}
}
all := ctxt.gopath()
for i, root := range all {
rootsrc := ctxt.joinPath(root, "src")
if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok {
// We found a potential import path for dir,
// but check that using it wouldn't find something
// else first.
if ctxt.GOROOT != "" {
if dir := ctxt.joinPath(ctxt.GOROOT, "src", sub); ctxt.isDir(dir) {
goto Found
}
}
for _, earlyRoot := range all[:i] {
if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) {
goto Found
}
}
// sub would not name some other directory instead of this one.
// Record it.
p.ImportPath = sub
p.Root = root
goto Found
}
}
// It's okay that we didn't find a root containing dir.
// Keep going with the information we have.
} else {
if strings.HasPrefix(path, "/") {
return p, fmt.Errorf("import %q: cannot import absolute path", path)
}
// Determine directory from import path.
if ctxt.GOROOT != "" {
dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", path)
isDir := ctxt.isDir(dir)
binaryOnly = !isDir && mode&AllowBinary != 0 && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga))
if isDir || binaryOnly {
p.Dir = dir
p.Goroot = true
p.Root = ctxt.GOROOT
goto Found
}
}
for _, root := range ctxt.gopath() {
dir := ctxt.joinPath(root, "src", path)
isDir := ctxt.isDir(dir)
binaryOnly = !isDir && mode&AllowBinary != 0 && ctxt.isFile(ctxt.joinPath(root, pkga))
if isDir || binaryOnly {
p.Dir = dir
p.Root = root
goto Found
}
}
return p, fmt.Errorf("import %q: cannot find package", path)
}
Found:
if p.Root != "" {
if p.Goroot {
p.SrcRoot = ctxt.joinPath(p.Root, "src", "pkg")
} else {
p.SrcRoot = ctxt.joinPath(p.Root, "src")
}
p.PkgRoot = ctxt.joinPath(p.Root, "pkg")
p.BinDir = ctxt.joinPath(p.Root, "bin")
p.PkgObj = ctxt.joinPath(p.Root, pkga)
}
if mode&FindOnly != 0 {
return p, nil
}
if binaryOnly && (mode&AllowBinary) != 0 {
return p, nil
}
dirs, err := ctxt.readDir(p.Dir)
if err != nil {
return nil, err
return p, err
}
var Sfiles []string // files with ".S" (capital S)
var di DirInfo
var firstFile string
imported := make(map[string][]token.Position)
testImported := make(map[string][]token.Position)
xTestImported := make(map[string][]token.Position)
fset := token.NewFileSet()
for _, d := range dirs {
if d.IsDir() {
@ -247,7 +517,11 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
continue
}
ext := path.Ext(name)
i := strings.LastIndex(name, ".")
if i < 0 {
i = len(name)
}
ext := name[i:]
switch ext {
case ".go", ".c", ".s", ".h", ".S":
// tentatively okay
@ -256,9 +530,15 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
continue
}
filename, data, err := ctxt.readFile(dir, name)
filename := ctxt.joinPath(p.Dir, name)
f, err := ctxt.openFile(filename)
if err != nil {
return nil, err
return p, err
}
data, err := ioutil.ReadAll(f)
f.Close()
if err != nil {
return p, fmt.Errorf("read %s: %v", filename, err)
}
// Look for +build comments to accept or reject the file.
@ -269,13 +549,13 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
// Going to save the file. For non-Go files, can stop here.
switch ext {
case ".c":
di.CFiles = append(di.CFiles, name)
p.CFiles = append(p.CFiles, name)
continue
case ".h":
di.HFiles = append(di.HFiles, name)
p.HFiles = append(p.HFiles, name)
continue
case ".s":
di.SFiles = append(di.SFiles, name)
p.SFiles = append(p.SFiles, name)
continue
case ".S":
Sfiles = append(Sfiles, name)
@ -284,7 +564,7 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments)
if err != nil {
return nil, err
return p, err
}
pkg := string(pf.Name.Name)
@ -293,22 +573,20 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
}
isTest := strings.HasSuffix(name, "_test.go")
isXTest := false
if isTest && strings.HasSuffix(pkg, "_test") {
isXTest = true
pkg = pkg[:len(pkg)-len("_test")]
}
if di.Package == "" {
di.Package = pkg
if p.Name == "" {
p.Name = pkg
firstFile = name
} else if pkg != di.Package {
return nil, fmt.Errorf("%s: found packages %s (%s) and %s (%s)", dir, di.Package, firstFile, pkg, name)
} else if pkg != p.Name {
return p, fmt.Errorf("found packages %s (%s) and %s (%s) in %s", p.Name, firstFile, pkg, name, p.Dir)
}
if pf.Doc != nil {
if di.PackageComment != nil {
di.PackageComment.List = append(di.PackageComment.List, pf.Doc.List...)
} else {
di.PackageComment = pf.Doc
}
if pf.Doc != nil && p.Doc == "" {
p.Doc = doc.Synopsis(pf.Doc.Text())
}
// Record imports and information about cgo.
@ -328,22 +606,24 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
if err != nil {
log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted)
}
if isTest {
if isXTest {
xTestImported[path] = append(xTestImported[path], fset.Position(spec.Pos()))
} else if isTest {
testImported[path] = append(testImported[path], fset.Position(spec.Pos()))
} else {
imported[path] = append(imported[path], fset.Position(spec.Pos()))
}
if path == "C" {
if isTest {
return nil, fmt.Errorf("%s: use of cgo in test not supported", filename)
return p, fmt.Errorf("use of cgo in test %s not supported", filename)
}
cg := spec.Doc
if cg == nil && len(d.Specs) == 1 {
cg = d.Doc
}
if cg != nil {
if err := ctxt.saveCgo(filename, &di, cg); err != nil {
return nil, err
if err := ctxt.saveCgo(filename, p, cg); err != nil {
return p, err
}
}
isCgo = true
@ -352,48 +632,52 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) {
}
if isCgo {
if ctxt.CgoEnabled {
di.CgoFiles = append(di.CgoFiles, name)
p.CgoFiles = append(p.CgoFiles, name)
}
} else if isXTest {
p.XTestGoFiles = append(p.XTestGoFiles, name)
} else if isTest {
if pkg == string(pf.Name.Name) {
di.TestGoFiles = append(di.TestGoFiles, name)
} else {
di.XTestGoFiles = append(di.XTestGoFiles, name)
}
p.TestGoFiles = append(p.TestGoFiles, name)
} else {
di.GoFiles = append(di.GoFiles, name)
p.GoFiles = append(p.GoFiles, name)
}
}
if di.Package == "" {
return nil, fmt.Errorf("%s: no Go source files", dir)
}
di.Imports = make([]string, len(imported))
di.ImportPos = imported
i := 0
for p := range imported {
di.Imports[i] = p
i++
}
di.TestImports = make([]string, len(testImported))
di.TestImportPos = testImported
i = 0
for p := range testImported {
di.TestImports[i] = p
i++
if p.Name == "" {
return p, fmt.Errorf("no Go source files in %s", p.Dir)
}
p.Imports, p.ImportPos = cleanImports(imported)
p.TestImports, p.TestImportPos = cleanImports(testImported)
p.XTestImports, p.XTestImportPos = cleanImports(xTestImported)
// add the .S files only if we are using cgo
// (which means gcc will compile them).
// The standard assemblers expect .s files.
if len(di.CgoFiles) > 0 {
di.SFiles = append(di.SFiles, Sfiles...)
sort.Strings(di.SFiles)
if len(p.CgoFiles) > 0 {
p.SFiles = append(p.SFiles, Sfiles...)
sort.Strings(p.SFiles)
}
// File name lists are sorted because ReadDir sorts.
sort.Strings(di.Imports)
sort.Strings(di.TestImports)
return &di, nil
return p, nil
}
func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) {
all := make([]string, 0, len(m))
for path := range m {
all = append(all, path)
}
sort.Strings(all)
return all, m
}
// Import is shorthand for Default.Import.
func Import(path, src string, mode ImportMode) (*Package, error) {
return Default.Import(path, src, mode)
}
// ImportDir is shorthand for Default.ImportDir.
func ImportDir(dir string, mode ImportMode) (*Package, error) {
return Default.ImportDir(dir, mode)
}
var slashslash = []byte("//")
@ -473,7 +757,7 @@ func (ctxt *Context) shouldBuild(content []byte) bool {
//
// TODO(rsc): This duplicates code in cgo.
// Once the dust settles, remove this code from cgo.
func (ctxt *Context) saveCgo(filename string, di *DirInfo, cg *ast.CommentGroup) error {
func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error {
text := cg.Text()
for _, line := range strings.Split(text, "\n") {
orig := line
@ -711,14 +995,11 @@ func init() {
// ToolDir is the directory containing build tools.
var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH)
// isLocalPath returns whether the given path is local (/foo ./foo ../foo . ..)
// Windows paths that starts with drive letter (c:\foo c:foo) are considered local.
func isLocalPath(s string) bool {
const sep = string(filepath.Separator)
return s == "." || s == ".." ||
filepath.HasPrefix(s, sep) ||
filepath.HasPrefix(s, "."+sep) || filepath.HasPrefix(s, ".."+sep) ||
filepath.VolumeName(s) != ""
// IsLocalImport reports whether the import path is
// a local import path, like ".", "..", "./foo", or "../foo".
func IsLocalImport(path string) bool {
return path == "." || path == ".." ||
strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
}
// ArchChar returns the architecture character for the given goarch.

View File

@ -5,83 +5,14 @@
package build
import (
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"testing"
)
func sortstr(x []string) []string {
sort.Strings(x)
return x
}
var buildPkgs = []struct {
dir string
info *DirInfo
}{
{
"go/build/pkgtest",
&DirInfo{
GoFiles: []string{"pkgtest.go"},
SFiles: []string{"sqrt_" + runtime.GOARCH + ".s"},
Package: "pkgtest",
Imports: []string{"bytes"},
TestImports: []string{"fmt", "pkgtest"},
TestGoFiles: sortstr([]string{"sqrt_test.go", "sqrt_" + runtime.GOARCH + "_test.go"}),
XTestGoFiles: []string{"xsqrt_test.go"},
},
},
{
"go/build/cmdtest",
&DirInfo{
GoFiles: []string{"main.go"},
Package: "main",
Imports: []string{"go/build/pkgtest"},
TestImports: []string{},
},
},
{
"go/build/cgotest",
&DirInfo{
CgoFiles: ifCgo([]string{"cgotest.go"}),
CFiles: []string{"cgotest.c"},
HFiles: []string{"cgotest.h"},
Imports: []string{"C", "unsafe"},
TestImports: []string{},
Package: "cgotest",
},
},
}
func ifCgo(x []string) []string {
if DefaultContext.CgoEnabled {
return x
}
return nil
}
func TestBuild(t *testing.T) {
for _, tt := range buildPkgs {
tree := Path[0] // Goroot
dir := filepath.Join(tree.SrcDir(), tt.dir)
info, err := ScanDir(dir)
if err != nil {
t.Errorf("ScanDir(%#q): %v", tt.dir, err)
continue
}
// Don't bother testing import positions.
tt.info.ImportPos, tt.info.TestImportPos = info.ImportPos, info.TestImportPos
if !reflect.DeepEqual(info, tt.info) {
t.Errorf("ScanDir(%#q) = %#v, want %#v\n", tt.dir, info, tt.info)
continue
}
}
}
func TestMatch(t *testing.T) {
ctxt := DefaultContext
ctxt := Default
what := "default"
match := func(tag string) {
if !ctxt.match(tag) {
@ -106,3 +37,40 @@ func TestMatch(t *testing.T) {
match(runtime.GOOS + "," + runtime.GOARCH + ",!bar")
nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar")
}
func TestDotSlashImport(t *testing.T) {
p, err := ImportDir("testdata/other", 0)
if err != nil {
t.Fatal(err)
}
if len(p.Imports) != 1 || p.Imports[0] != "./file" {
t.Fatalf("testdata/other: Imports=%v, want [./file]", p.Imports)
}
p1, err := Import("./file", "testdata/other", 0)
if err != nil {
t.Fatal(err)
}
if p1.Name != "file" {
t.Fatalf("./file: Name=%q, want %q", p1.Name, "file")
}
dir := filepath.Clean("testdata/other/file") // Clean to use \ on Windows
if p1.Dir != dir {
t.Fatalf("./file: Dir=%q, want %q", p1.Name, dir)
}
}
func TestLocalDirectory(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
p, err := ImportDir(cwd, 0)
if err != nil {
t.Fatal(err)
}
if p.ImportPath != "go/build" {
t.Fatalf("ImportPath=%q, want %q", p.ImportPath, "go/build")
}
}

View File

@ -1,9 +0,0 @@
// Copyright 2011 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.
int
Add(int x, int y, int *sum)
{
sum = x+y;
}

View File

@ -1,19 +0,0 @@
// Copyright 2011 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 cgotest
/*
char* greeting = "hello, world";
*/
// #include "cgotest.h"
import "C"
import "unsafe"
var Greeting = C.GoString(C.greeting)
func DoAdd(x, y int) (sum int) {
C.Add(C.int(x), C.int(y), (*C.int)(unsafe.Pointer(&sum)))
return
}

View File

@ -1,5 +0,0 @@
// Copyright 2011 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.
extern int Add(int, int, int *);

View File

@ -1,12 +0,0 @@
// Copyright 2011 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 main
import "go/build/pkgtest"
func main() {
pkgtest.Foo()
print(int(pkgtest.Sqrt(9)))
}

View File

@ -1,169 +0,0 @@
// Copyright 2011 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 build
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
)
// Path is a validated list of Trees derived from $GOROOT and $GOPATH at init.
var Path []*Tree
// Tree describes a Go source tree, either $GOROOT or one from $GOPATH.
type Tree struct {
Path string
Goroot bool
}
func newTree(p string) (*Tree, error) {
if !filepath.IsAbs(p) {
return nil, errors.New("must be absolute")
}
ep, err := filepath.EvalSymlinks(p)
if err != nil {
return nil, err
}
return &Tree{Path: ep}, nil
}
// SrcDir returns the tree's package source directory.
func (t *Tree) SrcDir() string {
if t.Goroot {
return filepath.Join(t.Path, "src", "pkg")
}
return filepath.Join(t.Path, "src")
}
// PkgDir returns the tree's package object directory.
func (t *Tree) PkgDir() string {
goos, goarch := runtime.GOOS, runtime.GOARCH
if e := os.Getenv("GOOS"); e != "" {
goos = e
}
if e := os.Getenv("GOARCH"); e != "" {
goarch = e
}
return filepath.Join(t.Path, "pkg", goos+"_"+goarch)
}
// BinDir returns the tree's binary executable directory.
func (t *Tree) BinDir() string {
if t.Goroot {
if gobin := os.Getenv("GOBIN"); gobin != "" {
return filepath.Clean(gobin)
}
}
return filepath.Join(t.Path, "bin")
}
// HasSrc returns whether the given package's
// source can be found inside this Tree.
func (t *Tree) HasSrc(pkg string) bool {
fi, err := os.Stat(filepath.Join(t.SrcDir(), pkg))
if err != nil {
return false
}
return fi.IsDir()
}
// HasPkg returns whether the given package's
// object file can be found inside this Tree.
func (t *Tree) HasPkg(pkg string) bool {
fi, err := os.Stat(filepath.Join(t.PkgDir(), pkg+".a"))
if err != nil {
return false
}
return !fi.IsDir()
}
var (
ErrNotFound = errors.New("package could not be found locally")
ErrTreeNotFound = errors.New("no valid GOROOT or GOPATH could be found")
)
// FindTree takes an import or filesystem path and returns the
// tree where the package source should be and the package import path.
func FindTree(path string) (tree *Tree, pkg string, err error) {
if isLocalPath(path) {
if path, err = filepath.Abs(path); err != nil {
return
}
if path, err = filepath.EvalSymlinks(path); err != nil {
return
}
for _, t := range Path {
tpath := t.SrcDir() + string(filepath.Separator)
if !filepath.HasPrefix(path, tpath) {
continue
}
tree = t
pkg = filepath.ToSlash(path[len(tpath):])
return
}
err = fmt.Errorf("path %q not inside a GOPATH", path)
return
}
tree = defaultTree
pkg = filepath.ToSlash(path)
for _, t := range Path {
if t.HasSrc(pkg) {
tree = t
return
}
}
if tree == nil {
err = ErrTreeNotFound
} else {
err = ErrNotFound
}
return
}
var (
// argument lists used by the build's gc and ld methods
gcImportArgs []string
ldImportArgs []string
// default tree for remote packages
defaultTree *Tree
)
// set up Path: parse and validate GOROOT and GOPATH variables
func init() {
root := runtime.GOROOT()
t, err := newTree(root)
if err == nil {
t.Goroot = true
Path = []*Tree{t}
}
for _, p := range filepath.SplitList(os.Getenv("GOPATH")) {
if p == "" {
continue
}
t, err := newTree(p)
if err != nil {
continue
}
Path = append(Path, t)
gcImportArgs = append(gcImportArgs, "-I", t.PkgDir())
ldImportArgs = append(ldImportArgs, "-L", t.PkgDir())
// select first GOPATH entry as default
if defaultTree == nil {
defaultTree = t
}
}
// use GOROOT if no valid GOPATH specified
if defaultTree == nil && len(Path) > 0 {
defaultTree = Path[0]
}
}

View File

@ -1,13 +0,0 @@
// Copyright 2011 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 pkgtest
import "bytes"
func Foo() *bytes.Buffer {
return nil
}
func Sqrt(x float64) float64

View File

@ -1,10 +0,0 @@
// Copyright 2009 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.
// func Sqrt(x float64) float64
TEXT ·Sqrt(SB),7,$0
FMOVD x+0(FP),F0
FSQRT
FMOVDP F0,r+8(FP)
RET

View File

@ -1 +0,0 @@
package pkgtest

View File

@ -1,9 +0,0 @@
// Copyright 2009 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.
// func Sqrt(x float64) float64
TEXT ·Sqrt(SB),7,$0
SQRTSD x+0(FP), X0
MOVSD X0, r+8(FP)
RET

View File

@ -1 +0,0 @@
package pkgtest

View File

@ -1,10 +0,0 @@
// Copyright 2011 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.
// func Sqrt(x float64) float64
TEXT ·Sqrt(SB),7,$0
MOVD x+0(FP),F0
SQRTD F0,F0
MOVD F0,r+8(FP)
RET

View File

@ -1 +0,0 @@
package pkgtest

View File

@ -1,9 +0,0 @@
// Copyright 2011 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 pkgtest
import "fmt"
var _ = fmt.Printf

View File

@ -1,9 +0,0 @@
// Copyright 2011 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 pkgtest_test
import "pkgtest"
var _ = pkgtest.Foo

View File

@ -55,7 +55,7 @@ var tests = []GoodFileTest{
func TestGoodOSArch(t *testing.T) {
for _, test := range tests {
if DefaultContext.goodOSArchFile(test.name) != test.result {
if Default.goodOSArchFile(test.name) != test.result {
t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result)
}
}

View File

@ -0,0 +1,5 @@
// Test data - not compiled.
package file
func F() {}

11
src/pkg/go/build/testdata/other/main.go vendored Normal file
View File

@ -0,0 +1,11 @@
// Test data - not compiled.
package main
import (
"./file"
)
func main() {
file.F()
}