// 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" "errors" "fmt" "go/build" "go/scanner" "go/token" "os" pathpkg "path" "path/filepath" "sort" "strings" "time" "unicode" ) // 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 Dir string `json:",omitempty"` // directory containing package sources ImportPath string `json:",omitempty"` // import path of package in dir Name string `json:",omitempty"` // package name Doc string `json:",omitempty"` // package documentation string Target string `json:",omitempty"` // install path Goroot bool `json:",omitempty"` // is this package found in the Go root? 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? Root string `json:",omitempty"` // Go root or Go path dir containing this package // Source files GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) CgoFiles []string `json:",omitempty"` // .go sources files that import "C" CFiles []string `json:",omitempty"` // .c source files HFiles []string `json:",omitempty"` // .h source files SFiles []string `json:",omitempty"` // .s source files SysoFiles []string `json:",omitempty"` // .syso system object files added to package // Cgo directives CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler CgoLDFLAGS []string `json:",omitempty"` // cgo: flags for linker CgoPkgConfig []string `json:",omitempty"` // cgo: pkg-config names // Dependency information Imports []string `json:",omitempty"` // import paths used by this package Deps []string `json:",omitempty"` // all (recursively) imported dependencies // Error information Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies? Error *PackageError `json:",omitempty"` // error loading this package (not dependencies) DepsErrors []*PackageError `json:",omitempty"` // errors loading dependencies // Test information TestGoFiles []string `json:",omitempty"` // _test.go files in package TestImports []string `json:",omitempty"` // imports from TestGoFiles XTestGoFiles []string `json:",omitempty"` // _test.go files outside package XTestImports []string `json:",omitempty"` // imports from XTestGoFiles // Unexported fields are not part of the public API. build *build.Package pkgdir string // overrides build.PkgDir 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 forceBuild bool // this package must be rebuilt forceLibrary bool // this package is a library (even if named "main") local bool // imported via local path (./ or ../) localPrefix string // interpret ./ and ../ imports relative to this prefix } func (p *Package) copyBuild(pp *build.Package) { p.build = pp p.Dir = pp.Dir p.ImportPath = pp.ImportPath p.Name = pp.Name p.Doc = pp.Doc p.Root = pp.Root // TODO? Target p.Goroot = pp.Goroot p.Standard = p.Goroot && p.ImportPath != "" && !strings.Contains(p.ImportPath, ".") p.GoFiles = pp.GoFiles p.CgoFiles = pp.CgoFiles p.CFiles = pp.CFiles p.HFiles = pp.HFiles p.SFiles = pp.SFiles p.SysoFiles = pp.SysoFiles p.CgoCFLAGS = pp.CgoCFLAGS p.CgoLDFLAGS = pp.CgoLDFLAGS p.CgoPkgConfig = pp.CgoPkgConfig p.Imports = pp.Imports p.TestGoFiles = pp.TestGoFiles p.TestImports = pp.TestImports p.XTestGoFiles = pp.XTestGoFiles p.XTestImports = pp.XTestImports } // 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 != "" { // Omit import stack. The full path to the file where the error // is the most important thing. return p.Pos + ": " + p.Err } return "package " + 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) } // dirToImportPath returns the pseudo-import path we use for a package // outside the Go path. It begins with _/ and then contains the full path // to the directory. If the package lives in c:\home\gopher\my\pkg then // the pseudo-import path is _/c_/home/gopher/my/pkg. // Using a pseudo-import path like this makes the ./ imports no longer // a special case, so that all the code to deal with ordinary imports works // automatically. func dirToImportPath(dir string) string { return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir))) } func makeImportValid(r rune) rune { // Should match Go spec, compilers, and ../../pkg/go/parser/parser.go:/isValidImport. const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { return '_' } return r } // loadImport scans the directory named by path, which must be an import path, // but possibly a local import path (an absolute file system path or one beginning // with ./ or ../). A local relative path is interpreted relative to srcDir. // It returns a *Package describing the package found in that directory. func loadImport(path string, srcDir string, stk *importStack, importPos []token.Position) *Package { stk.push(path) defer stk.pop() // Determine canonical identifier for this package. // For a local import the identifier is the pseudo-import path // we create from the full directory to the package. // Otherwise it is the usual import path. importPath := path isLocal := build.IsLocalImport(path) if isLocal { importPath = dirToImportPath(filepath.Join(srcDir, path)) } if p := packageCache[importPath]; p != nil { return reusePackage(p, stk) } p := new(Package) p.local = isLocal p.ImportPath = importPath packageCache[importPath] = p // Load package. // Import always returns bp != nil, even if an error occurs, // in order to return partial information. // // TODO: After Go 1, decide when to pass build.AllowBinary here. // See issue 3268 for mistakes to avoid. bp, err := buildContext.Import(path, srcDir, 0) bp.ImportPath = importPath if gobin != "" { bp.BinDir = gobin } p.load(stk, bp, err) if p.Error != nil && len(importPos) > 0 { pos := importPos[0] pos.Filename = shortPath(pos.Filename) p.Error.Pos = pos.String() } return p } // reusePackage reuses package p to satisfy the import at the top // of the import stack stk. If this use causes an import loop, // reusePackage updates p's error information to record the loop. 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 } // isGoTool is the list of directories for Go programs that are installed in // $GOROOT/pkg/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, } // expandScanner expands a scanner.List error into all the errors in the list. // The default Error method only shows the first error. func expandScanner(err error) 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 { e.Pos.Filename = shortPath(e.Pos.Filename) buf.WriteString("\n") buf.WriteString(e.Error()) } return errors.New(buf.String()) } return err } // load populates p using information from bp, err, which should // be the result of calling build.Context.Import. func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package { p.copyBuild(bp) // The localPrefix is the path we interpret ./ imports relative to. // Synthesized main packages sometimes override this. p.localPrefix = dirToImportPath(p.Dir) if err != nil { p.Incomplete = true err = expandScanner(err) p.Error = &PackageError{ ImportStack: stk.copy(), Err: err.Error(), } return p } if p.Name == "main" { _, elem := filepath.Split(p.Dir) full := buildContext.GOOS + "_" + buildContext.GOARCH + "/" + elem if buildContext.GOOS != toolGOOS || buildContext.GOARCH != toolGOARCH { // Install cross-compiled binaries to subdirectories of bin. elem = full } p.target = filepath.Join(p.build.BinDir, elem) if p.Goroot && isGoTool[p.ImportPath] { p.target = filepath.Join(gorootPkg, "tool", full) } if buildContext.GOOS == "windows" { p.target += ".exe" } } else if p.local { // Local import turned into absolute path. // No permanent install target. p.target = "" } else { p.target = p.build.PkgObj } importPaths := p.Imports // Packages that use cgo import runtime/cgo implicitly, // except runtime/cgo itself. if len(p.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") } // Build list of full paths to all Go files in the package, // for use by commands like go fmt. p.gofiles = stringList(p.GoFiles, p.CgoFiles, p.TestGoFiles, p.XTestGoFiles) for i := range p.gofiles { p.gofiles[i] = filepath.Join(p.Dir, p.gofiles[i]) } sort.Strings(p.gofiles) // Build list of imported packages and full dependency list. imports := make([]*Package, 0, len(p.Imports)) deps := make(map[string]bool) for i, path := range importPaths { if path == "C" { continue } p1 := loadImport(path, p.Dir, stk, p.build.ImportPos[path]) if p1.local { if !p.local && p.Error == nil { p.Error = &PackageError{ ImportStack: stk.copy(), Err: fmt.Sprintf("local import %q in non-local package", path), } pos := p.build.ImportPos[path] if len(pos) > 0 { p.Error.Pos = pos[0].String() } } path = p1.ImportPath importPaths[i] = path } deps[path] = true imports = append(imports, p1) for _, dep := range p1.Deps { deps[dep] = true } if p1.Incomplete { p.Incomplete = 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. if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") { p.target = "" } p.Target = p.target return p } // packageList returns the list of packages in the dag rooted at roots // as visited in a depth-first post-order traversal. func packageList(roots []*Package) []*Package { seen := map[*Package]bool{} all := []*Package{} var walk func(*Package) walk = func(p *Package) { if seen[p] { return } seen[p] = true for _, p1 := range p.imports { walk(p1) } all = append(all, p) } for _, root := range roots { walk(root) } return all } // computeStale computes the Stale flag in the package dag that starts // at the named pkgs (command-line arguments). func computeStale(pkgs ...*Package) { topRoot := map[string]bool{} for _, p := range pkgs { topRoot[p.Root] = true } for _, p := range packageList(pkgs) { p.Stale = isStale(p, topRoot) } } // isStale reports whether package p needs to be rebuilt. func isStale(p *Package, topRoot map[string]bool) bool { if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") { // fake, builtin package return false } if p.Error != nil { return true } // A package without Go sources means we only found // the installed .a file. Since we don't know how to rebuild // it, it can't be stale, even if -a is set. This enables binary-only // distributions of Go packages, although such binaries are // only useful with the specific version of the toolchain that // created them. if len(p.gofiles) == 0 { return false } if buildA || p.target == "" || p.Stale { return true } // Package is stale if completely unbuilt. var built time.Time if fi, err := os.Stat(p.target); err == nil { built = fi.ModTime() } if built.IsZero() { return true } olderThan := func(file string) bool { fi, err := os.Stat(file) return err != nil || fi.ModTime().After(built) } // Package is stale if a dependency is, or if a dependency is newer. for _, p1 := range p.deps { if p1.Stale || p1.target != "" && olderThan(p1.target) { return true } } // As a courtesy to developers installing new versions of the compiler // frequently, define that packages are stale if they are // older than the compiler, and commands if they are older than // the linker. This heuristic will not work if the binaries are back-dated, // as some binary distributions may do, but it does handle a very // common case. See issue 3036. if olderThan(buildToolchain.compiler()) { return true } if p.build.IsCommand() && olderThan(buildToolchain.linker()) { return true } // Have installed copy, probably built using current compilers, // and built after its imported packages. The only reason now // that we'd have to rebuild it is if the sources were newer than // the package. If a package p is not in the same tree as any // package named on the command-line, assume it is up-to-date // no matter what the modification times on the source files indicate. // This avoids rebuilding $GOROOT packages when people are // working outside the Go root, and it effectively makes each tree // listed in $GOPATH a separate compilation world. // See issue 3149. if p.Root != "" && !topRoot[p.Root] { return false } srcs := stringList(p.GoFiles, p.CFiles, p.HFiles, p.SFiles, p.CgoFiles, p.SysoFiles) for _, src := range srcs { if olderThan(filepath.Join(p.Dir, src)) { return true } } return false } var cwd, _ = os.Getwd() var cmdCache = map[string]*Package{} // loadPackage is like loadImport but is used for command-line arguments, // not for paths found in import statements. In addition to ordinary import paths, // loadPackage accepts pseudo-paths beginning with cmd/ to denote commands // in the Go command directory, as well as paths to those directories. func loadPackage(arg string, stk *importStack) *Package { if build.IsLocalImport(arg) { dir := arg if !filepath.IsAbs(dir) { if abs, err := filepath.Abs(dir); err == nil { // interpret relative to current directory dir = abs } } if sub, ok := hasSubdir(gorootSrc, dir); ok && strings.HasPrefix(sub, "cmd/") && !strings.Contains(sub[4:], "/") { arg = sub } } if strings.HasPrefix(arg, "cmd/") && !strings.Contains(arg[4:], "/") { if p := cmdCache[arg]; p != nil { return p } stk.push(arg) defer stk.pop() bp, err := build.ImportDir(filepath.Join(gorootSrc, arg), 0) bp.ImportPath = arg bp.Goroot = true bp.BinDir = gorootBin if gobin != "" { bp.BinDir = gobin } bp.Root = goroot bp.SrcRoot = gorootSrc p := new(Package) cmdCache[arg] = p p.load(stk, bp, err) if p.Error == nil && p.Name != "main" { p.Error = &PackageError{ ImportStack: stk.copy(), Err: fmt.Sprintf("expected package main but found package %s in %s", p.Name, p.Dir), } } return p } // Wasn't a command; must be a package. // If it is a local import path but names a standard package, // we treat it as if the user specified the standard package. // This lets you run go test ./ioutil in package io and be // referring to io/ioutil rather than a hypothetical import of // "./ioutil". if build.IsLocalImport(arg) { bp, _ := build.ImportDir(filepath.Join(cwd, arg), build.FindOnly) if bp.ImportPath != "" && bp.ImportPath != "." { arg = bp.ImportPath } } return loadImport(arg, cwd, stk, nil) } // 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 { var pkgs []*Package for _, pkg := range packagesAndErrors(args) { 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 { if len(args) > 0 && strings.HasSuffix(args[0], ".go") { return []*Package{goFilesPackage(args)} } args = importPaths(args) var pkgs []*Package var stk importStack for _, arg := range args { pkgs = append(pkgs, loadPackage(arg, &stk)) } computeStale(pkgs...) 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 } // hasSubdir reports whether dir is a subdirectory of // (possibly multiple levels below) root. // If so, it sets rel to the path fragment that must be // appended to root to reach dir. func hasSubdir(root, dir string) (rel string, ok bool) { if p, err := filepath.EvalSymlinks(root); err == nil { root = p } if p, err := filepath.EvalSymlinks(dir); err == nil { dir = p } const sep = string(filepath.Separator) root = filepath.Clean(root) if !strings.HasSuffix(root, sep) { root += sep } dir = filepath.Clean(dir) if !strings.HasPrefix(dir, root) { return "", false } return filepath.ToSlash(dir[len(root):]), true }