| // 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 ( |
| "bytes" |
| "go/build" |
| "go/scanner" |
| "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 |
| Target string `json:",omitempty"` // install path |
| 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 |
| Pos string // position of error |
| Err string // the error itself |
| } |
| |
| func (p *PackageError) Error() string { |
| if p.Pos != "" { |
| return strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Pos + ": " + p.Err |
| } |
| 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) |
| } |
| |
| // isGoTool is the list of directories for Go programs that are installed in |
| // $GOROOT/bin/tool. |
| var isGoTool = map[string]bool{ |
| "cmd/api": true, |
| "cmd/cgo": true, |
| "cmd/fix": true, |
| "cmd/vet": true, |
| "cmd/yacc": true, |
| "exp/gotype": true, |
| "exp/ebnflint": true, |
| } |
| |
| 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(), |
| } |
| // Look for parser errors. |
| if err, ok := err.(scanner.ErrorList); ok { |
| // Prepare error with \n before each message. |
| // When printed in something like context: %v |
| // this will put the leading file positions each on |
| // its own line. It will also show all the errors |
| // instead of just the first, as err.Error does. |
| var buf bytes.Buffer |
| for _, e := range err { |
| buf.WriteString("\n") |
| buf.WriteString(e.Error()) |
| } |
| p.Error.Err = buf.String() |
| } |
| 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) |
| if ctxt.GOOS != toolGOOS || ctxt.GOARCH != toolGOARCH { |
| // Install cross-compiled binaries to subdirectories of bin. |
| elem = ctxt.GOOS + "_" + ctxt.GOARCH + "/" + elem |
| } |
| if t.Goroot && isGoTool[p.ImportPath] { |
| p.target = filepath.Join(t.Path, "bin/tool", elem) |
| } else { |
| p.target = filepath.Join(t.BinDir(), elem) |
| } |
| if ctxt.GOOS == "windows" { |
| p.target += ".exe" |
| } |
| } else { |
| dir := t.PkgDir() |
| // For gccgo, rewrite p.target with the expected library name. |
| if _, ok := buildToolchain.(gccgoToolchain); ok { |
| dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir)) |
| } |
| p.target = buildToolchain.pkgpath(dir, p) |
| } |
| |
| 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) |
| if p1.Error != nil { |
| if info.ImportPos != nil && len(info.ImportPos[path]) > 0 { |
| pos := info.ImportPos[path][0] |
| p1.Error.Pos = pos.String() |
| } |
| } |
| 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 = "" |
| } |
| |
| p.Target = 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("can't load package: %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("can't load package: %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 |
| } |