diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go index ee0f92328e..fe9c862f4f 100644 --- a/src/cmd/api/goapi.go +++ b/src/cmd/api/goapi.go @@ -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 diff --git a/src/cmd/api/goapi_test.go b/src/cmd/api/goapi_test.go index dbbec46b0d..c7cc601b1a 100644 --- a/src/cmd/api/goapi_test.go +++ b/src/cmd/api/goapi_test.go @@ -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()) diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index 1f212a0bcd..e5f7a73d4f 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -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...) } diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index ee905bb7a0..5f42105393 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -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 { diff --git a/src/pkg/exp/types/gcimporter.go b/src/pkg/exp/types/gcimporter.go index 8b28aede1e..cb996f2805 100644 --- a/src/pkg/exp/types/gcimporter.go +++ b/src/pkg/exp/types/gcimporter.go @@ -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() { diff --git a/src/pkg/go/build/build.go b/src/pkg/go/build/build.go index aeb9ad259b..f893ddd0ba 100644 --- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -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. diff --git a/src/pkg/go/build/build_test.go b/src/pkg/go/build/build_test.go index 3c706a46ed..06b8b0e94f 100644 --- a/src/pkg/go/build/build_test.go +++ b/src/pkg/go/build/build_test.go @@ -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") + } +} diff --git a/src/pkg/go/build/cgotest/cgotest.c b/src/pkg/go/build/cgotest/cgotest.c deleted file mode 100644 index b13acb2275..0000000000 --- a/src/pkg/go/build/cgotest/cgotest.c +++ /dev/null @@ -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; -} diff --git a/src/pkg/go/build/cgotest/cgotest.go b/src/pkg/go/build/cgotest/cgotest.go deleted file mode 100644 index 93bbf06883..0000000000 --- a/src/pkg/go/build/cgotest/cgotest.go +++ /dev/null @@ -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 -} diff --git a/src/pkg/go/build/cgotest/cgotest.h b/src/pkg/go/build/cgotest/cgotest.h deleted file mode 100644 index 9c73643b6f..0000000000 --- a/src/pkg/go/build/cgotest/cgotest.h +++ /dev/null @@ -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 *); diff --git a/src/pkg/go/build/cmdtest/main.go b/src/pkg/go/build/cmdtest/main.go deleted file mode 100644 index bed4f485a0..0000000000 --- a/src/pkg/go/build/cmdtest/main.go +++ /dev/null @@ -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))) -} diff --git a/src/pkg/go/build/path.go b/src/pkg/go/build/path.go deleted file mode 100644 index 6115adafeb..0000000000 --- a/src/pkg/go/build/path.go +++ /dev/null @@ -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] - } -} diff --git a/src/pkg/go/build/pkgtest/pkgtest.go b/src/pkg/go/build/pkgtest/pkgtest.go deleted file mode 100644 index 08eea1e2bc..0000000000 --- a/src/pkg/go/build/pkgtest/pkgtest.go +++ /dev/null @@ -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 diff --git a/src/pkg/go/build/pkgtest/sqrt_386.s b/src/pkg/go/build/pkgtest/sqrt_386.s deleted file mode 100644 index d0a428d52e..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_386.s +++ /dev/null @@ -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 diff --git a/src/pkg/go/build/pkgtest/sqrt_386_test.go b/src/pkg/go/build/pkgtest/sqrt_386_test.go deleted file mode 100644 index 26b483fa0b..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_386_test.go +++ /dev/null @@ -1 +0,0 @@ -package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_amd64.s b/src/pkg/go/build/pkgtest/sqrt_amd64.s deleted file mode 100644 index f5b329e70a..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_amd64.s +++ /dev/null @@ -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 diff --git a/src/pkg/go/build/pkgtest/sqrt_amd64_test.go b/src/pkg/go/build/pkgtest/sqrt_amd64_test.go deleted file mode 100644 index 26b483fa0b..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_amd64_test.go +++ /dev/null @@ -1 +0,0 @@ -package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_arm.s b/src/pkg/go/build/pkgtest/sqrt_arm.s deleted file mode 100644 index befbb8a898..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_arm.s +++ /dev/null @@ -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 diff --git a/src/pkg/go/build/pkgtest/sqrt_arm_test.go b/src/pkg/go/build/pkgtest/sqrt_arm_test.go deleted file mode 100644 index 26b483fa0b..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_arm_test.go +++ /dev/null @@ -1 +0,0 @@ -package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_test.go b/src/pkg/go/build/pkgtest/sqrt_test.go deleted file mode 100644 index ee9fd5de68..0000000000 --- a/src/pkg/go/build/pkgtest/sqrt_test.go +++ /dev/null @@ -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 diff --git a/src/pkg/go/build/pkgtest/xsqrt_test.go b/src/pkg/go/build/pkgtest/xsqrt_test.go deleted file mode 100644 index 3898d1dda5..0000000000 --- a/src/pkg/go/build/pkgtest/xsqrt_test.go +++ /dev/null @@ -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 diff --git a/src/pkg/go/build/syslist_test.go b/src/pkg/go/build/syslist_test.go index d27630d758..9157faf8cb 100644 --- a/src/pkg/go/build/syslist_test.go +++ b/src/pkg/go/build/syslist_test.go @@ -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) } } diff --git a/src/pkg/go/build/testdata/other/file/file.go b/src/pkg/go/build/testdata/other/file/file.go new file mode 100644 index 0000000000..bbfd3e9e59 --- /dev/null +++ b/src/pkg/go/build/testdata/other/file/file.go @@ -0,0 +1,5 @@ +// Test data - not compiled. + +package file + +func F() {} diff --git a/src/pkg/go/build/testdata/other/main.go b/src/pkg/go/build/testdata/other/main.go new file mode 100644 index 0000000000..e0904357c9 --- /dev/null +++ b/src/pkg/go/build/testdata/other/main.go @@ -0,0 +1,11 @@ +// Test data - not compiled. + +package main + +import ( + "./file" +) + +func main() { + file.F() +}