diff options
Diffstat (limited to 'src/cmd/go/pkg.go')
| -rw-r--r-- | src/cmd/go/pkg.go | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go new file mode 100644 index 000000000..09fa67127 --- /dev/null +++ b/src/cmd/go/pkg.go @@ -0,0 +1,445 @@ +// 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" + "os" + "path/filepath" + "sort" + "strings" + "time" +) + +// A Package describes a single package found in a directory. +type Package struct { + // Note: These fields are part of the go command's public API. + // See list.go. It is okay to add fields, but not to change or + // remove existing ones. Keep in sync with list.go + ImportPath string // import path of package in dir + Name string `json:",omitempty"` // package name + Doc string `json:",omitempty"` // package documentation string + Dir string `json:",omitempty"` // directory containing package sources + Version string `json:",omitempty"` // version of installed package (TODO) + Standard bool `json:",omitempty"` // is this package part of the standard Go library? + Stale bool `json:",omitempty"` // would 'go install' do anything for this package? + Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies? + Error *PackageError `json:",omitempty"` // error loading this package (not dependencies) + + // Source files + GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles and XTestGoFiles) + TestGoFiles []string `json:",omitempty"` // _test.go source files internal to the package they are testing + XTestGoFiles []string `json:",omitempty"` //_test.go source files external to the package they are testing + CFiles []string `json:",omitempty"` // .c source files + HFiles []string `json:",omitempty"` // .h source files + SFiles []string `json:",omitempty"` // .s source files + CgoFiles []string `json:",omitempty"` // .go sources files that import "C" + CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler + CgoLDFLAGS []string `json:",omitempty"` // cgo: flags for linker + + // Dependency information + Imports []string `json:",omitempty"` // import paths used by this package + Deps []string `json:",omitempty"` // all (recursively) imported dependencies + DepsErrors []*PackageError `json:",omitempty"` // errors loading dependencies + + // Unexported fields are not part of the public API. + t *build.Tree + pkgdir string + info *build.DirInfo + imports []*Package + deps []*Package + gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths + target string // installed file for this package (may be executable) + fake bool // synthesized package +} + +// A PackageError describes an error loading information about a package. +type PackageError struct { + ImportStack []string // shortest path from package named on command line to this one + Err string // the error itself +} + +func (p *PackageError) Error() string { + return strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err +} + +// An importStack is a stack of import paths. +type importStack []string + +func (s *importStack) push(p string) { + *s = append(*s, p) +} + +func (s *importStack) pop() { + *s = (*s)[0 : len(*s)-1] +} + +func (s *importStack) copy() []string { + return append([]string{}, *s...) +} + +// shorterThan returns true if sp is shorter than t. +// We use this to record the shortest import sequence +// that leads to a particular package. +func (sp *importStack) shorterThan(t []string) bool { + s := *sp + if len(s) != len(t) { + return len(s) < len(t) + } + // If they are the same length, settle ties using string ordering. + for i := range s { + if s[i] != t[i] { + return s[i] < t[i] + } + } + return false // they are equal +} + +// packageCache is a lookup cache for loadPackage, +// so that if we look up a package multiple times +// we return the same pointer each time. +var packageCache = map[string]*Package{} + +// reloadPackage is like loadPackage but makes sure +// not to use the package cache. +func reloadPackage(arg string, stk *importStack) *Package { + p := packageCache[arg] + if p != nil { + delete(packageCache, p.Dir) + delete(packageCache, p.ImportPath) + } + return loadPackage(arg, stk) +} + +// loadPackage scans directory named by arg, +// which is either an import path or a file system path +// (if the latter, must be rooted or begin with . or ..), +// and returns a *Package describing the package +// found in that directory. +func loadPackage(arg string, stk *importStack) *Package { + stk.push(arg) + defer stk.pop() + + // Check package cache. + if p := packageCache[arg]; p != nil { + return reusePackage(p, stk) + } + + // Find basic information about package path. + t, importPath, err := build.FindTree(arg) + dir := "" + // Maybe it is a standard command. + if err != nil && strings.HasPrefix(arg, "cmd/") { + goroot := build.Path[0] + p := filepath.Join(goroot.Path, "src", arg) + if st, err1 := os.Stat(p); err1 == nil && st.IsDir() { + t = goroot + importPath = arg + dir = p + err = nil + } + } + // Maybe it is a path to a standard command. + if err != nil && (filepath.IsAbs(arg) || isLocalPath(arg)) { + arg, _ := filepath.Abs(arg) + goroot := build.Path[0] + cmd := filepath.Join(goroot.Path, "src", "cmd") + string(filepath.Separator) + if st, err1 := os.Stat(arg); err1 == nil && st.IsDir() && strings.HasPrefix(arg, cmd) { + t = goroot + importPath = filepath.FromSlash(arg[len(cmd):]) + dir = arg + err = nil + } + } + if err != nil { + p := &Package{ + ImportPath: arg, + Error: &PackageError{ + ImportStack: stk.copy(), + Err: err.Error(), + }, + Incomplete: true, + } + packageCache[arg] = p + return p + } + + if dir == "" { + dir = filepath.Join(t.SrcDir(), filepath.FromSlash(importPath)) + } + + // Maybe we know the package by its directory. + if p := packageCache[dir]; p != nil { + packageCache[importPath] = p + return reusePackage(p, stk) + } + + return scanPackage(&buildContext, t, arg, importPath, dir, stk) +} + +func reusePackage(p *Package, stk *importStack) *Package { + // We use p.imports==nil to detect a package that + // is in the midst of its own loadPackage call + // (all the recursion below happens before p.imports gets set). + if p.imports == nil { + if p.Error == nil { + p.Error = &PackageError{ + ImportStack: stk.copy(), + Err: "import loop", + } + } + p.Incomplete = true + } + if p.Error != nil && stk.shorterThan(p.Error.ImportStack) { + p.Error.ImportStack = stk.copy() + } + return p +} + +// firstSentence returns the first sentence of the document text. +// The sentence ends after the first period followed by a space. +// The returned sentence will have no \n \r or \t characters and +// will use only single spaces between words. +func firstSentence(text string) string { + var b []byte + space := true +Loop: + for i := 0; i < len(text); i++ { + switch c := text[i]; c { + case ' ', '\t', '\r', '\n': + if !space { + space = true + if len(b) > 0 && b[len(b)-1] == '.' { + break Loop + } + b = append(b, ' ') + } + default: + space = false + b = append(b, c) + } + } + return string(b) +} + +func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string, stk *importStack) *Package { + // Read the files in the directory to learn the structure + // of the package. + p := &Package{ + ImportPath: importPath, + Dir: dir, + Standard: t.Goroot && !strings.Contains(importPath, "."), + t: t, + } + packageCache[dir] = p + packageCache[importPath] = p + + info, err := ctxt.ScanDir(dir) + if err != nil { + p.Error = &PackageError{ + ImportStack: stk.copy(), + Err: err.Error(), + } + p.Incomplete = true + return p + } + + p.info = info + p.Name = info.Package + p.Doc = firstSentence(info.PackageComment.Text()) + p.Imports = info.Imports + p.GoFiles = info.GoFiles + p.TestGoFiles = info.TestGoFiles + p.XTestGoFiles = info.XTestGoFiles + p.CFiles = info.CFiles + p.HFiles = info.HFiles + p.SFiles = info.SFiles + p.CgoFiles = info.CgoFiles + p.CgoCFLAGS = info.CgoCFLAGS + p.CgoLDFLAGS = info.CgoLDFLAGS + + if info.Package == "main" { + _, elem := filepath.Split(importPath) + p.target = filepath.Join(t.BinDir(), elem) + if ctxt.GOOS == "windows" { + p.target += ".exe" + } + } else { + p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a") + } + + var built time.Time + if fi, err := os.Stat(p.target); err == nil { + built = fi.ModTime() + } + + // Build list of full paths to all Go files in the package, + // for use by commands like go fmt. + for _, f := range info.GoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + for _, f := range info.CgoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + for _, f := range info.TestGoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + for _, f := range info.XTestGoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + + sort.Strings(p.gofiles) + + srcss := [][]string{ + p.GoFiles, + p.CFiles, + p.HFiles, + p.SFiles, + p.CgoFiles, + } +Stale: + for _, srcs := range srcss { + for _, src := range srcs { + if fi, err := os.Stat(filepath.Join(p.Dir, src)); err != nil || fi.ModTime().After(built) { + //println("STALE", p.ImportPath, "needs", src, err) + p.Stale = true + break Stale + } + } + } + + importPaths := p.Imports + // Packages that use cgo import runtime/cgo implicitly, + // except runtime/cgo itself. + if len(info.CgoFiles) > 0 && (!p.Standard || p.ImportPath != "runtime/cgo") { + importPaths = append(importPaths, "runtime/cgo") + } + // Everything depends on runtime, except runtime and unsafe. + if !p.Standard || (p.ImportPath != "runtime" && p.ImportPath != "unsafe") { + importPaths = append(importPaths, "runtime") + } + + // Record package under both import path and full directory name. + packageCache[dir] = p + packageCache[importPath] = p + + // Build list of imported packages and full dependency list. + imports := make([]*Package, 0, len(p.Imports)) + deps := make(map[string]bool) + for _, path := range importPaths { + if path == "C" { + continue + } + deps[path] = true + p1 := loadPackage(path, stk) + imports = append(imports, p1) + for _, dep := range p1.Deps { + deps[dep] = true + } + if p1.Stale { + p.Stale = true + } + if p1.Incomplete { + p.Incomplete = true + } + // p1.target can be empty only if p1 is not a real package, + // such as package unsafe or the temporary packages + // created during go test. + if !p.Stale && p1.target != "" { + if fi, err := os.Stat(p1.target); err != nil || fi.ModTime().After(built) { + //println("STALE", p.ImportPath, "needs", p1.target, err) + //println("BUILT", built.String(), "VS", fi.ModTime().String()) + p.Stale = true + } + } + } + p.imports = imports + + p.Deps = make([]string, 0, len(deps)) + for dep := range deps { + p.Deps = append(p.Deps, dep) + } + sort.Strings(p.Deps) + for _, dep := range p.Deps { + p1 := packageCache[dep] + if p1 == nil { + panic("impossible: missing entry in package cache for " + dep + " imported by " + p.ImportPath) + } + p.deps = append(p.deps, p1) + if p1.Error != nil { + p.DepsErrors = append(p.DepsErrors, p1.Error) + } + } + + // unsafe is a fake package and is never out-of-date. + if p.Standard && p.ImportPath == "unsafe" { + p.Stale = false + p.target = "" + } + + return p +} + +// packages returns the packages named by the +// command line arguments 'args'. If a named package +// cannot be loaded at all (for example, if the directory does not exist), +// then packages prints an error and does not include that +// package in the results. However, if errors occur trying +// to load dependencies of a named package, the named +// package is still returned, with p.Incomplete = true +// and details in p.DepsErrors. +func packages(args []string) []*Package { + args = importPaths(args) + var pkgs []*Package + var stk importStack + for _, arg := range args { + pkg := loadPackage(arg, &stk) + if pkg.Error != nil { + errorf("%s", pkg.Error) + continue + } + pkgs = append(pkgs, pkg) + } + return pkgs +} + +// packagesAndErrors is like 'packages' but returns a +// *Package for every argument, even the ones that +// cannot be loaded at all. +// The packages that fail to load will have p.Error != nil. +func packagesAndErrors(args []string) []*Package { + args = importPaths(args) + var pkgs []*Package + var stk importStack + for _, arg := range args { + pkgs = append(pkgs, loadPackage(arg, &stk)) + } + return pkgs +} + +// packagesForBuild is like 'packages' but fails if any of +// the packages or their dependencies have errors +// (cannot be built). +func packagesForBuild(args []string) []*Package { + pkgs := packagesAndErrors(args) + printed := map[*PackageError]bool{} + for _, pkg := range pkgs { + if pkg.Error != nil { + errorf("%s", pkg.Error) + } + for _, err := range pkg.DepsErrors { + // Since these are errors in dependencies, + // the same error might show up multiple times, + // once in each package that depends on it. + // Only print each once. + if !printed[err] { + printed[err] = true + errorf("%s", err) + } + } + } + exitIfErrors() + return pkgs +} |
