blob: e9fe845c56d7e41836d2796634852773b5b0466b [file] [log] [blame]
// 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
}