summaryrefslogtreecommitdiff
path: root/src/cmd/go/pkg.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/pkg.go')
-rw-r--r--src/cmd/go/pkg.go445
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
+}