blob: f3f79b6a7db207e05271e6b6cee7194ebc6c6e44 [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 (
"fmt"
"go/build"
"go/doc"
"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
Name string // package name
Doc string `json:",omitempty"` // package documentation string
ImportPath string // import path of package in dir
Dir string // 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?
// Source files
GoFiles []string // .go source files (excluding CgoFiles)
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
// Unexported fields are not part of the public API.
t *build.Tree
pkgdir string
info *build.DirInfo
imports []*Package
gofiles []string // GoFiles+CgoFiles, absolute paths
target string // installed file for this package (may be executable)
fake bool // synthesized package
}
// 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{}
// 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) (*Package, error) {
// Check package cache.
if p := packageCache[arg]; p != nil {
// 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 {
return nil, fmt.Errorf("import loop at %s", arg)
}
return p, nil
}
// Find basic information about package path.
t, importPath, err := build.FindTree(arg)
dir := ""
// Maybe it is a standard command.
if err != nil && !filepath.IsAbs(arg) && 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
}
}
if err != nil {
return nil, err
}
if dir == "" {
dir = filepath.Join(t.SrcDir(), filepath.FromSlash(importPath))
}
// Maybe we know the package by its directory.
if p := packageCache[dir]; p != nil {
if p.imports == nil {
return nil, fmt.Errorf("import loop at %s", arg)
}
return p, nil
}
return scanPackage(&build.DefaultContext, t, arg, importPath, dir)
}
func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string) (*Package, error) {
// Read the files in the directory to learn the structure
// of the package.
info, err := ctxt.ScanDir(dir)
if err != nil {
return nil, err
}
var target string
if info.Package == "main" {
_, elem := filepath.Split(importPath)
target = filepath.Join(t.BinDir(), elem)
if ctxt.GOOS == "windows" {
target += ".exe"
}
} else {
target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
p := &Package{
Name: info.Package,
Doc: doc.CommentText(info.PackageComment),
ImportPath: importPath,
Dir: dir,
Imports: info.Imports,
GoFiles: info.GoFiles,
CFiles: info.CFiles,
HFiles: info.HFiles,
SFiles: info.SFiles,
CgoFiles: info.CgoFiles,
CgoCFLAGS: info.CgoCFLAGS,
CgoLDFLAGS: info.CgoLDFLAGS,
Standard: t.Goroot && !strings.Contains(importPath, "."),
target: target,
t: t,
info: info,
}
var built time.Time
if fi, err := os.Stat(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))
}
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 {
deps[path] = true
if path == "C" {
continue
}
p1, err := loadPackage(path)
if err != nil {
delete(packageCache, dir)
delete(packageCache, importPath)
// Add extra error detail to show full import chain.
// Always useful, but especially useful in import loops.
return nil, fmt.Errorf("%s: import %s\n\t%v", arg, path, err)
}
imports = append(imports, p1)
for _, dep := range p1.Deps {
deps[dep] = true
}
if p1.Stale {
p.Stale = 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)
// unsafe is a fake package and is never out-of-date.
if p.Standard && p.ImportPath == "unsafe" {
p.Stale = false
p.target = ""
}
return p, nil
}
// packages returns the packages named by the
// command line arguments 'args'.
func packages(args []string) []*Package {
args = importPaths(args)
var pkgs []*Package
for _, arg := range args {
pkg, err := loadPackage(arg)
if err != nil {
errorf("%s", err)
continue
}
pkgs = append(pkgs, pkg)
}
return pkgs
}