| // Copyright 2013 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 loader | |
| // See doc.go for package documentation and implementation notes. | |
| import ( | |
| "errors" | |
| "fmt" | |
| "go/ast" | |
| "go/build" | |
| "go/parser" | |
| "go/token" | |
| "go/types" | |
| "os" | |
| "path/filepath" | |
| "sort" | |
| "strings" | |
| "sync" | |
| "time" | |
| "golang.org/x/tools/go/ast/astutil" | |
| "golang.org/x/tools/go/internal/cgo" | |
| ) | |
| var ignoreVendor build.ImportMode | |
| const trace = false // show timing info for type-checking | |
| // Config specifies the configuration for loading a whole program from | |
| // Go source code. | |
| // The zero value for Config is a ready-to-use default configuration. | |
| type Config struct { | |
| // Fset is the file set for the parser to use when loading the | |
| // program. If nil, it may be lazily initialized by any | |
| // method of Config. | |
| Fset *token.FileSet | |
| // ParserMode specifies the mode to be used by the parser when | |
| // loading source packages. | |
| ParserMode parser.Mode | |
| // TypeChecker contains options relating to the type checker. | |
| // | |
| // The supplied IgnoreFuncBodies is not used; the effective | |
| // value comes from the TypeCheckFuncBodies func below. | |
| // The supplied Import function is not used either. | |
| TypeChecker types.Config | |
| // TypeCheckFuncBodies is a predicate over package paths. | |
| // A package for which the predicate is false will | |
| // have its package-level declarations type checked, but not | |
| // its function bodies; this can be used to quickly load | |
| // dependencies from source. If nil, all func bodies are type | |
| // checked. | |
| TypeCheckFuncBodies func(path string) bool | |
| // If Build is non-nil, it is used to locate source packages. | |
| // Otherwise &build.Default is used. | |
| // | |
| // By default, cgo is invoked to preprocess Go files that | |
| // import the fake package "C". This behaviour can be | |
| // disabled by setting CGO_ENABLED=0 in the environment prior | |
| // to startup, or by setting Build.CgoEnabled=false. | |
| Build *build.Context | |
| // The current directory, used for resolving relative package | |
| // references such as "./go/loader". If empty, os.Getwd will be | |
| // used instead. | |
| Cwd string | |
| // If DisplayPath is non-nil, it is used to transform each | |
| // file name obtained from Build.Import(). This can be used | |
| // to prevent a virtualized build.Config's file names from | |
| // leaking into the user interface. | |
| DisplayPath func(path string) string | |
| // If AllowErrors is true, Load will return a Program even | |
| // if some of the its packages contained I/O, parser or type | |
| // errors; such errors are accessible via PackageInfo.Errors. If | |
| // false, Load will fail if any package had an error. | |
| AllowErrors bool | |
| // CreatePkgs specifies a list of non-importable initial | |
| // packages to create. The resulting packages will appear in | |
| // the corresponding elements of the Program.Created slice. | |
| CreatePkgs []PkgSpec | |
| // ImportPkgs specifies a set of initial packages to load. | |
| // The map keys are package paths. | |
| // | |
| // The map value indicates whether to load tests. If true, Load | |
| // will add and type-check two lists of files to the package: | |
| // non-test files followed by in-package *_test.go files. In | |
| // addition, it will append the external test package (if any) | |
| // to Program.Created. | |
| ImportPkgs map[string]bool | |
| // FindPackage is called during Load to create the build.Package | |
| // for a given import path from a given directory. | |
| // If FindPackage is nil, (*build.Context).Import is used. | |
| // A client may use this hook to adapt to a proprietary build | |
| // system that does not follow the "go build" layout | |
| // conventions, for example. | |
| // | |
| // It must be safe to call concurrently from multiple goroutines. | |
| FindPackage func(ctxt *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error) | |
| // AfterTypeCheck is called immediately after a list of files | |
| // has been type-checked and appended to info.Files. | |
| // | |
| // This optional hook function is the earliest opportunity for | |
| // the client to observe the output of the type checker, | |
| // which may be useful to reduce analysis latency when loading | |
| // a large program. | |
| // | |
| // The function is permitted to modify info.Info, for instance | |
| // to clear data structures that are no longer needed, which can | |
| // dramatically reduce peak memory consumption. | |
| // | |
| // The function may be called twice for the same PackageInfo: | |
| // once for the files of the package and again for the | |
| // in-package test files. | |
| // | |
| // It must be safe to call concurrently from multiple goroutines. | |
| AfterTypeCheck func(info *PackageInfo, files []*ast.File) | |
| } | |
| // A PkgSpec specifies a non-importable package to be created by Load. | |
| // Files are processed first, but typically only one of Files and | |
| // Filenames is provided. The path needn't be globally unique. | |
| // | |
| // For vendoring purposes, the package's directory is the one that | |
| // contains the first file. | |
| type PkgSpec struct { | |
| Path string // package path ("" => use package declaration) | |
| Files []*ast.File // ASTs of already-parsed files | |
| Filenames []string // names of files to be parsed | |
| } | |
| // A Program is a Go program loaded from source as specified by a Config. | |
| type Program struct { | |
| Fset *token.FileSet // the file set for this program | |
| // Created[i] contains the initial package whose ASTs or | |
| // filenames were supplied by Config.CreatePkgs[i], followed by | |
| // the external test package, if any, of each package in | |
| // Config.ImportPkgs ordered by ImportPath. | |
| // | |
| // NOTE: these files must not import "C". Cgo preprocessing is | |
| // only performed on imported packages, not ad hoc packages. | |
| // | |
| // TODO(adonovan): we need to copy and adapt the logic of | |
| // goFilesPackage (from $GOROOT/src/cmd/go/build.go) and make | |
| // Config.Import and Config.Create methods return the same kind | |
| // of entity, essentially a build.Package. | |
| // Perhaps we can even reuse that type directly. | |
| Created []*PackageInfo | |
| // Imported contains the initially imported packages, | |
| // as specified by Config.ImportPkgs. | |
| Imported map[string]*PackageInfo | |
| // AllPackages contains the PackageInfo of every package | |
| // encountered by Load: all initial packages and all | |
| // dependencies, including incomplete ones. | |
| AllPackages map[*types.Package]*PackageInfo | |
| // importMap is the canonical mapping of package paths to | |
| // packages. It contains all Imported initial packages, but not | |
| // Created ones, and all imported dependencies. | |
| importMap map[string]*types.Package | |
| } | |
| // PackageInfo holds the ASTs and facts derived by the type-checker | |
| // for a single package. | |
| // | |
| // Not mutated once exposed via the API. | |
| type PackageInfo struct { | |
| Pkg *types.Package | |
| Importable bool // true if 'import "Pkg.Path()"' would resolve to this | |
| TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors | |
| Files []*ast.File // syntax trees for the package's files | |
| Errors []error // non-nil if the package had errors | |
| types.Info // type-checker deductions. | |
| dir string // package directory | |
| checker *types.Checker // transient type-checker state | |
| errorFunc func(error) | |
| } | |
| func (info *PackageInfo) String() string { return info.Pkg.Path() } | |
| func (info *PackageInfo) appendError(err error) { | |
| if info.errorFunc != nil { | |
| info.errorFunc(err) | |
| } else { | |
| fmt.Fprintln(os.Stderr, err) | |
| } | |
| info.Errors = append(info.Errors, err) | |
| } | |
| func (conf *Config) fset() *token.FileSet { | |
| if conf.Fset == nil { | |
| conf.Fset = token.NewFileSet() | |
| } | |
| return conf.Fset | |
| } | |
| // ParseFile is a convenience function (intended for testing) that invokes | |
| // the parser using the Config's FileSet, which is initialized if nil. | |
| // | |
| // src specifies the parser input as a string, []byte, or io.Reader, and | |
| // filename is its apparent name. If src is nil, the contents of | |
| // filename are read from the file system. | |
| func (conf *Config) ParseFile(filename string, src any) (*ast.File, error) { | |
| // TODO(adonovan): use conf.build() etc like parseFiles does. | |
| return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode) | |
| } | |
| // FromArgsUsage is a partial usage message that applications calling | |
| // FromArgs may wish to include in their -help output. | |
| const FromArgsUsage = ` | |
| <args> is a list of arguments denoting a set of initial packages. | |
| It may take one of two forms: | |
| 1. A list of *.go source files. | |
| All of the specified files are loaded, parsed and type-checked | |
| as a single package. All the files must belong to the same directory. | |
| 2. A list of import paths, each denoting a package. | |
| The package's directory is found relative to the $GOROOT and | |
| $GOPATH using similar logic to 'go build', and the *.go files in | |
| that directory are loaded, parsed and type-checked as a single | |
| package. | |
| In addition, all *_test.go files in the directory are then loaded | |
| and parsed. Those files whose package declaration equals that of | |
| the non-*_test.go files are included in the primary package. Test | |
| files whose package declaration ends with "_test" are type-checked | |
| as another package, the 'external' test package, so that a single | |
| import path may denote two packages. (Whether this behaviour is | |
| enabled is tool-specific, and may depend on additional flags.) | |
| A '--' argument terminates the list of packages. | |
| ` | |
| // FromArgs interprets args as a set of initial packages to load from | |
| // source and updates the configuration. It returns the list of | |
| // unconsumed arguments. | |
| // | |
| // It is intended for use in command-line interfaces that require a | |
| // set of initial packages to be specified; see FromArgsUsage message | |
| // for details. | |
| // | |
| // Only superficial errors are reported at this stage; errors dependent | |
| // on I/O are detected during Load. | |
| func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) { | |
| var rest []string | |
| for i, arg := range args { | |
| if arg == "--" { | |
| rest = args[i+1:] | |
| args = args[:i] | |
| break // consume "--" and return the remaining args | |
| } | |
| } | |
| if len(args) > 0 && strings.HasSuffix(args[0], ".go") { | |
| // Assume args is a list of a *.go files | |
| // denoting a single ad hoc package. | |
| for _, arg := range args { | |
| if !strings.HasSuffix(arg, ".go") { | |
| return nil, fmt.Errorf("named files must be .go files: %s", arg) | |
| } | |
| } | |
| conf.CreateFromFilenames("", args...) | |
| } else { | |
| // Assume args are directories each denoting a | |
| // package and (perhaps) an external test, iff xtest. | |
| for _, arg := range args { | |
| if xtest { | |
| conf.ImportWithTests(arg) | |
| } else { | |
| conf.Import(arg) | |
| } | |
| } | |
| } | |
| return rest, nil | |
| } | |
| // CreateFromFilenames is a convenience function that adds | |
| // a conf.CreatePkgs entry to create a package of the specified *.go | |
| // files. | |
| func (conf *Config) CreateFromFilenames(path string, filenames ...string) { | |
| conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames}) | |
| } | |
| // CreateFromFiles is a convenience function that adds a conf.CreatePkgs | |
| // entry to create package of the specified path and parsed files. | |
| func (conf *Config) CreateFromFiles(path string, files ...*ast.File) { | |
| conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files}) | |
| } | |
| // ImportWithTests is a convenience function that adds path to | |
| // ImportPkgs, the set of initial source packages located relative to | |
| // $GOPATH. The package will be augmented by any *_test.go files in | |
| // its directory that contain a "package x" (not "package x_test") | |
| // declaration. | |
| // | |
| // In addition, if any *_test.go files contain a "package x_test" | |
| // declaration, an additional package comprising just those files will | |
| // be added to CreatePkgs. | |
| func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) } | |
| // Import is a convenience function that adds path to ImportPkgs, the | |
| // set of initial packages that will be imported from source. | |
| func (conf *Config) Import(path string) { conf.addImport(path, false) } | |
| func (conf *Config) addImport(path string, tests bool) { | |
| if path == "C" { | |
| return // ignore; not a real package | |
| } | |
| if conf.ImportPkgs == nil { | |
| conf.ImportPkgs = make(map[string]bool) | |
| } | |
| conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests | |
| } | |
| // PathEnclosingInterval returns the PackageInfo and ast.Node that | |
| // contain source interval [start, end), and all the node's ancestors | |
| // up to the AST root. It searches all ast.Files of all packages in prog. | |
| // exact is defined as for astutil.PathEnclosingInterval. | |
| // | |
| // The zero value is returned if not found. | |
| func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { | |
| for _, info := range prog.AllPackages { | |
| for _, f := range info.Files { | |
| if !tokenFileContainsPos(prog.Fset.File(f.FileStart), start) { | |
| continue | |
| } | |
| if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { | |
| return info, path, exact | |
| } | |
| } | |
| } | |
| return nil, nil, false | |
| } | |
| // InitialPackages returns a new slice containing the set of initial | |
| // packages (Created + Imported) in unspecified order. | |
| func (prog *Program) InitialPackages() []*PackageInfo { | |
| infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported)) | |
| infos = append(infos, prog.Created...) | |
| for _, info := range prog.Imported { | |
| infos = append(infos, info) | |
| } | |
| return infos | |
| } | |
| // Package returns the ASTs and results of type checking for the | |
| // specified package. | |
| func (prog *Program) Package(path string) *PackageInfo { | |
| if info, ok := prog.AllPackages[prog.importMap[path]]; ok { | |
| return info | |
| } | |
| for _, info := range prog.Created { | |
| if path == info.Pkg.Path() { | |
| return info | |
| } | |
| } | |
| return nil | |
| } | |
| // ---------- Implementation ---------- | |
| // importer holds the working state of the algorithm. | |
| type importer struct { | |
| conf *Config // the client configuration | |
| start time.Time // for logging | |
| progMu sync.Mutex // guards prog | |
| prog *Program // the resulting program | |
| // findpkg is a memoization of FindPackage. | |
| findpkgMu sync.Mutex // guards findpkg | |
| findpkg map[findpkgKey]*findpkgValue | |
| importedMu sync.Mutex // guards imported | |
| imported map[string]*importInfo // all imported packages (incl. failures) by import path | |
| // import dependency graph: graph[x][y] => x imports y | |
| // | |
| // Since non-importable packages cannot be cyclic, we ignore | |
| // their imports, thus we only need the subgraph over importable | |
| // packages. Nodes are identified by their import paths. | |
| graphMu sync.Mutex | |
| graph map[string]map[string]bool | |
| } | |
| type findpkgKey struct { | |
| importPath string | |
| fromDir string | |
| mode build.ImportMode | |
| } | |
| type findpkgValue struct { | |
| ready chan struct{} // closed to broadcast readiness | |
| bp *build.Package | |
| err error | |
| } | |
| // importInfo tracks the success or failure of a single import. | |
| // | |
| // Upon completion, exactly one of info and err is non-nil: | |
| // info on successful creation of a package, err otherwise. | |
| // A successful package may still contain type errors. | |
| type importInfo struct { | |
| path string // import path | |
| info *PackageInfo // results of typechecking (including errors) | |
| complete chan struct{} // closed to broadcast that info is set. | |
| } | |
| // awaitCompletion blocks until ii is complete, | |
| // i.e. the info field is safe to inspect. | |
| func (ii *importInfo) awaitCompletion() { | |
| <-ii.complete // wait for close | |
| } | |
| // Complete marks ii as complete. | |
| // Its info and err fields will not be subsequently updated. | |
| func (ii *importInfo) Complete(info *PackageInfo) { | |
| if info == nil { | |
| panic("info == nil") | |
| } | |
| ii.info = info | |
| close(ii.complete) | |
| } | |
| type importError struct { | |
| path string // import path | |
| err error // reason for failure to create a package | |
| } | |
| // Load creates the initial packages specified by conf.{Create,Import}Pkgs, | |
| // loading their dependencies packages as needed. | |
| // | |
| // On success, Load returns a Program containing a PackageInfo for | |
| // each package. On failure, it returns an error. | |
| // | |
| // If AllowErrors is true, Load will return a Program even if some | |
| // packages contained I/O, parser or type errors, or if dependencies | |
| // were missing. (Such errors are accessible via PackageInfo.Errors. If | |
| // false, Load will fail if any package had an error. | |
| // | |
| // It is an error if no packages were loaded. | |
| func (conf *Config) Load() (*Program, error) { | |
| // Create a simple default error handler for parse/type errors. | |
| if conf.TypeChecker.Error == nil { | |
| conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) } | |
| } | |
| // Set default working directory for relative package references. | |
| if conf.Cwd == "" { | |
| var err error | |
| conf.Cwd, err = os.Getwd() | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| // Install default FindPackage hook using go/build logic. | |
| if conf.FindPackage == nil { | |
| conf.FindPackage = (*build.Context).Import | |
| } | |
| prog := &Program{ | |
| Fset: conf.fset(), | |
| Imported: make(map[string]*PackageInfo), | |
| importMap: make(map[string]*types.Package), | |
| AllPackages: make(map[*types.Package]*PackageInfo), | |
| } | |
| imp := importer{ | |
| conf: conf, | |
| prog: prog, | |
| findpkg: make(map[findpkgKey]*findpkgValue), | |
| imported: make(map[string]*importInfo), | |
| start: time.Now(), | |
| graph: make(map[string]map[string]bool), | |
| } | |
| // -- loading proper (concurrent phase) -------------------------------- | |
| var errpkgs []string // packages that contained errors | |
| // Load the initially imported packages and their dependencies, | |
| // in parallel. | |
| // No vendor check on packages imported from the command line. | |
| infos, importErrors := imp.importAll("", conf.Cwd, conf.ImportPkgs, ignoreVendor) | |
| for _, ie := range importErrors { | |
| conf.TypeChecker.Error(ie.err) // failed to create package | |
| errpkgs = append(errpkgs, ie.path) | |
| } | |
| for _, info := range infos { | |
| prog.Imported[info.Pkg.Path()] = info | |
| } | |
| // Augment the designated initial packages by their tests. | |
| // Dependencies are loaded in parallel. | |
| var xtestPkgs []*build.Package | |
| for importPath, augment := range conf.ImportPkgs { | |
| if !augment { | |
| continue | |
| } | |
| // No vendor check on packages imported from command line. | |
| bp, err := imp.findPackage(importPath, conf.Cwd, ignoreVendor) | |
| if err != nil { | |
| // Package not found, or can't even parse package declaration. | |
| // Already reported by previous loop; ignore it. | |
| continue | |
| } | |
| // Needs external test package? | |
| if len(bp.XTestGoFiles) > 0 { | |
| xtestPkgs = append(xtestPkgs, bp) | |
| } | |
| // Consult the cache using the canonical package path. | |
| path := bp.ImportPath | |
| imp.importedMu.Lock() // (unnecessary, we're sequential here) | |
| ii, ok := imp.imported[path] | |
| // Paranoid checks added due to issue #11012. | |
| if !ok { | |
| // Unreachable. | |
| // The previous loop called importAll and thus | |
| // startLoad for each path in ImportPkgs, which | |
| // populates imp.imported[path] with a non-zero value. | |
| panic(fmt.Sprintf("imported[%q] not found", path)) | |
| } | |
| if ii == nil { | |
| // Unreachable. | |
| // The ii values in this loop are the same as in | |
| // the previous loop, which enforced the invariant | |
| // that at least one of ii.err and ii.info is non-nil. | |
| panic(fmt.Sprintf("imported[%q] == nil", path)) | |
| } | |
| if ii.info == nil { | |
| // Unreachable. | |
| // awaitCompletion has the postcondition | |
| // ii.info != nil. | |
| panic(fmt.Sprintf("imported[%q].info = nil", path)) | |
| } | |
| info := ii.info | |
| imp.importedMu.Unlock() | |
| // Parse the in-package test files. | |
| files, errs := imp.conf.parsePackageFiles(bp, 't') | |
| for _, err := range errs { | |
| info.appendError(err) | |
| } | |
| // The test files augmenting package P cannot be imported, | |
| // but may import packages that import P, | |
| // so we must disable the cycle check. | |
| imp.addFiles(info, files, false) | |
| } | |
| createPkg := func(path, dir string, files []*ast.File, errs []error) { | |
| info := imp.newPackageInfo(path, dir) | |
| for _, err := range errs { | |
| info.appendError(err) | |
| } | |
| // Ad hoc packages are non-importable, | |
| // so no cycle check is needed. | |
| // addFiles loads dependencies in parallel. | |
| imp.addFiles(info, files, false) | |
| prog.Created = append(prog.Created, info) | |
| } | |
| // Create packages specified by conf.CreatePkgs. | |
| for _, cp := range conf.CreatePkgs { | |
| files, errs := parseFiles(conf.fset(), conf.build(), nil, conf.Cwd, cp.Filenames, conf.ParserMode) | |
| files = append(files, cp.Files...) | |
| path := cp.Path | |
| if path == "" { | |
| if len(files) > 0 { | |
| path = files[0].Name.Name | |
| } else { | |
| path = "(unnamed)" | |
| } | |
| } | |
| dir := conf.Cwd | |
| if len(files) > 0 && files[0].Pos().IsValid() { | |
| dir = filepath.Dir(conf.fset().File(files[0].Pos()).Name()) | |
| } | |
| createPkg(path, dir, files, errs) | |
| } | |
| // Create external test packages. | |
| sort.Sort(byImportPath(xtestPkgs)) | |
| for _, bp := range xtestPkgs { | |
| files, errs := imp.conf.parsePackageFiles(bp, 'x') | |
| createPkg(bp.ImportPath+"_test", bp.Dir, files, errs) | |
| } | |
| // -- finishing up (sequential) ---------------------------------------- | |
| if len(prog.Imported)+len(prog.Created) == 0 { | |
| return nil, errors.New("no initial packages were loaded") | |
| } | |
| // Create infos for indirectly imported packages. | |
| // e.g. incomplete packages without syntax, loaded from export data. | |
| for _, obj := range prog.importMap { | |
| info := prog.AllPackages[obj] | |
| if info == nil { | |
| prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true} | |
| } else { | |
| // finished | |
| info.checker = nil | |
| info.errorFunc = nil | |
| } | |
| } | |
| if !conf.AllowErrors { | |
| // Report errors in indirectly imported packages. | |
| for _, info := range prog.AllPackages { | |
| if len(info.Errors) > 0 { | |
| errpkgs = append(errpkgs, info.Pkg.Path()) | |
| } | |
| } | |
| if errpkgs != nil { | |
| var more string | |
| if len(errpkgs) > 3 { | |
| more = fmt.Sprintf(" and %d more", len(errpkgs)-3) | |
| errpkgs = errpkgs[:3] | |
| } | |
| return nil, fmt.Errorf("couldn't load packages due to errors: %s%s", | |
| strings.Join(errpkgs, ", "), more) | |
| } | |
| } | |
| markErrorFreePackages(prog.AllPackages) | |
| return prog, nil | |
| } | |
| type byImportPath []*build.Package | |
| func (b byImportPath) Len() int { return len(b) } | |
| func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath } | |
| func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] } | |
| // markErrorFreePackages sets the TransitivelyErrorFree flag on all | |
| // applicable packages. | |
| func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) { | |
| // Build the transpose of the import graph. | |
| importedBy := make(map[*types.Package]map[*types.Package]bool) | |
| for P := range allPackages { | |
| for _, Q := range P.Imports() { | |
| clients, ok := importedBy[Q] | |
| if !ok { | |
| clients = make(map[*types.Package]bool) | |
| importedBy[Q] = clients | |
| } | |
| clients[P] = true | |
| } | |
| } | |
| // Find all packages reachable from some error package. | |
| reachable := make(map[*types.Package]bool) | |
| var visit func(*types.Package) | |
| visit = func(p *types.Package) { | |
| if !reachable[p] { | |
| reachable[p] = true | |
| for q := range importedBy[p] { | |
| visit(q) | |
| } | |
| } | |
| } | |
| for _, info := range allPackages { | |
| if len(info.Errors) > 0 { | |
| visit(info.Pkg) | |
| } | |
| } | |
| // Mark the others as "transitively error-free". | |
| for _, info := range allPackages { | |
| if !reachable[info.Pkg] { | |
| info.TransitivelyErrorFree = true | |
| } | |
| } | |
| } | |
| // build returns the effective build context. | |
| func (conf *Config) build() *build.Context { | |
| if conf.Build != nil { | |
| return conf.Build | |
| } | |
| return &build.Default | |
| } | |
| // parsePackageFiles enumerates the files belonging to package path, | |
| // then loads, parses and returns them, plus a list of I/O or parse | |
| // errors that were encountered. | |
| // | |
| // 'which' indicates which files to include: | |
| // | |
| // 'g': include non-test *.go source files (GoFiles + processed CgoFiles) | |
| // 't': include in-package *_test.go source files (TestGoFiles) | |
| // 'x': include external *_test.go source files. (XTestGoFiles) | |
| func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) { | |
| if bp.ImportPath == "unsafe" { | |
| return nil, nil | |
| } | |
| var filenames []string | |
| switch which { | |
| case 'g': | |
| filenames = bp.GoFiles | |
| case 't': | |
| filenames = bp.TestGoFiles | |
| case 'x': | |
| filenames = bp.XTestGoFiles | |
| default: | |
| panic(which) | |
| } | |
| files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode) | |
| // Preprocess CgoFiles and parse the outputs (sequentially). | |
| if which == 'g' && bp.CgoFiles != nil { | |
| ioLimit <- true | |
| cgofiles, err := cgo.ProcessFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode) | |
| <-ioLimit | |
| if err != nil { | |
| errs = append(errs, err) | |
| } else { | |
| files = append(files, cgofiles...) | |
| } | |
| } | |
| return files, errs | |
| } | |
| // doImport imports the package denoted by path. | |
| // It implements the types.Importer signature. | |
| // | |
| // It returns an error if a package could not be created | |
| // (e.g. go/build or parse error), but type errors are reported via | |
| // the types.Config.Error callback (the first of which is also saved | |
| // in the package's PackageInfo). | |
| // | |
| // Idempotent. | |
| func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) { | |
| if to == "C" { | |
| // This should be unreachable, but ad hoc packages are | |
| // not currently subject to cgo preprocessing. | |
| // See https://golang.org/issue/11627. | |
| return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`, | |
| from.Pkg.Path()) | |
| } | |
| bp, err := imp.findPackage(to, from.dir, 0) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // The standard unsafe package is handled specially, | |
| // and has no PackageInfo. | |
| if bp.ImportPath == "unsafe" { | |
| return types.Unsafe, nil | |
| } | |
| // Look for the package in the cache using its canonical path. | |
| path := bp.ImportPath | |
| imp.importedMu.Lock() | |
| ii := imp.imported[path] | |
| imp.importedMu.Unlock() | |
| if ii == nil { | |
| panic("internal error: unexpected import: " + path) | |
| } | |
| if ii.info != nil { | |
| return ii.info.Pkg, nil | |
| } | |
| // Import of incomplete package: this indicates a cycle. | |
| fromPath := from.Pkg.Path() | |
| if cycle := imp.findPath(path, fromPath); cycle != nil { | |
| // Normalize cycle: start from alphabetically largest node. | |
| pos, start := -1, "" | |
| for i, s := range cycle { | |
| if pos < 0 || s > start { | |
| pos, start = i, s | |
| } | |
| } | |
| cycle = append(cycle, cycle[:pos]...)[pos:] // rotate cycle to start from largest | |
| cycle = append(cycle, cycle[0]) // add start node to end to show cycliness | |
| return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> ")) | |
| } | |
| panic("internal error: import of incomplete (yet acyclic) package: " + fromPath) | |
| } | |
| // findPackage locates the package denoted by the importPath in the | |
| // specified directory. | |
| func (imp *importer) findPackage(importPath, fromDir string, mode build.ImportMode) (*build.Package, error) { | |
| // We use a non-blocking duplicate-suppressing cache (gopl.io §9.7) | |
| // to avoid holding the lock around FindPackage. | |
| key := findpkgKey{importPath, fromDir, mode} | |
| imp.findpkgMu.Lock() | |
| v, ok := imp.findpkg[key] | |
| if ok { | |
| // cache hit | |
| imp.findpkgMu.Unlock() | |
| <-v.ready // wait for entry to become ready | |
| } else { | |
| // Cache miss: this goroutine becomes responsible for | |
| // populating the map entry and broadcasting its readiness. | |
| v = &findpkgValue{ready: make(chan struct{})} | |
| imp.findpkg[key] = v | |
| imp.findpkgMu.Unlock() | |
| ioLimit <- true | |
| v.bp, v.err = imp.conf.FindPackage(imp.conf.build(), importPath, fromDir, mode) | |
| <-ioLimit | |
| if _, ok := v.err.(*build.NoGoError); ok { | |
| v.err = nil // empty directory is not an error | |
| } | |
| close(v.ready) // broadcast ready condition | |
| } | |
| return v.bp, v.err | |
| } | |
| // importAll loads, parses, and type-checks the specified packages in | |
| // parallel and returns their completed importInfos in unspecified order. | |
| // | |
| // fromPath is the package path of the importing package, if it is | |
| // importable, "" otherwise. It is used for cycle detection. | |
| // | |
| // fromDir is the directory containing the import declaration that | |
| // caused these imports. | |
| func (imp *importer) importAll(fromPath, fromDir string, imports map[string]bool, mode build.ImportMode) (infos []*PackageInfo, errors []importError) { | |
| if fromPath != "" { | |
| // We're loading a set of imports. | |
| // | |
| // We must record graph edges from the importing package | |
| // to its dependencies, and check for cycles. | |
| imp.graphMu.Lock() | |
| deps, ok := imp.graph[fromPath] | |
| if !ok { | |
| deps = make(map[string]bool) | |
| imp.graph[fromPath] = deps | |
| } | |
| for importPath := range imports { | |
| deps[importPath] = true | |
| } | |
| imp.graphMu.Unlock() | |
| } | |
| var pending []*importInfo | |
| for importPath := range imports { | |
| if fromPath != "" { | |
| if cycle := imp.findPath(importPath, fromPath); cycle != nil { | |
| // Cycle-forming import: we must not check it | |
| // since it would deadlock. | |
| if trace { | |
| fmt.Fprintf(os.Stderr, "import cycle: %q\n", cycle) | |
| } | |
| continue | |
| } | |
| } | |
| bp, err := imp.findPackage(importPath, fromDir, mode) | |
| if err != nil { | |
| errors = append(errors, importError{ | |
| path: importPath, | |
| err: err, | |
| }) | |
| continue | |
| } | |
| pending = append(pending, imp.startLoad(bp)) | |
| } | |
| for _, ii := range pending { | |
| ii.awaitCompletion() | |
| infos = append(infos, ii.info) | |
| } | |
| return infos, errors | |
| } | |
| // findPath returns an arbitrary path from 'from' to 'to' in the import | |
| // graph, or nil if there was none. | |
| func (imp *importer) findPath(from, to string) []string { | |
| imp.graphMu.Lock() | |
| defer imp.graphMu.Unlock() | |
| seen := make(map[string]bool) | |
| var search func(stack []string, importPath string) []string | |
| search = func(stack []string, importPath string) []string { | |
| if !seen[importPath] { | |
| seen[importPath] = true | |
| stack = append(stack, importPath) | |
| if importPath == to { | |
| return stack | |
| } | |
| for x := range imp.graph[importPath] { | |
| if p := search(stack, x); p != nil { | |
| return p | |
| } | |
| } | |
| } | |
| return nil | |
| } | |
| return search(make([]string, 0, 20), from) | |
| } | |
| // startLoad initiates the loading, parsing and type-checking of the | |
| // specified package and its dependencies, if it has not already begun. | |
| // | |
| // It returns an importInfo, not necessarily in a completed state. The | |
| // caller must call awaitCompletion() before accessing its info field. | |
| // | |
| // startLoad is concurrency-safe and idempotent. | |
| func (imp *importer) startLoad(bp *build.Package) *importInfo { | |
| path := bp.ImportPath | |
| imp.importedMu.Lock() | |
| ii, ok := imp.imported[path] | |
| if !ok { | |
| ii = &importInfo{path: path, complete: make(chan struct{})} | |
| imp.imported[path] = ii | |
| go func() { | |
| info := imp.load(bp) | |
| ii.Complete(info) | |
| }() | |
| } | |
| imp.importedMu.Unlock() | |
| return ii | |
| } | |
| // load implements package loading by parsing Go source files | |
| // located by go/build. | |
| func (imp *importer) load(bp *build.Package) *PackageInfo { | |
| info := imp.newPackageInfo(bp.ImportPath, bp.Dir) | |
| info.Importable = true | |
| files, errs := imp.conf.parsePackageFiles(bp, 'g') | |
| for _, err := range errs { | |
| info.appendError(err) | |
| } | |
| imp.addFiles(info, files, true) | |
| imp.progMu.Lock() | |
| imp.prog.importMap[bp.ImportPath] = info.Pkg | |
| imp.progMu.Unlock() | |
| return info | |
| } | |
| // addFiles adds and type-checks the specified files to info, loading | |
| // their dependencies if needed. The order of files determines the | |
| // package initialization order. It may be called multiple times on the | |
| // same package. Errors are appended to the info.Errors field. | |
| // | |
| // cycleCheck determines whether the imports within files create | |
| // dependency edges that should be checked for potential cycles. | |
| func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) { | |
| // Ensure the dependencies are loaded, in parallel. | |
| var fromPath string | |
| if cycleCheck { | |
| fromPath = info.Pkg.Path() | |
| } | |
| // TODO(adonovan): opt: make the caller do scanImports. | |
| // Callers with a build.Package can skip it. | |
| imp.importAll(fromPath, info.dir, scanImports(files), 0) | |
| if trace { | |
| fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n", | |
| time.Since(imp.start), info.Pkg.Path(), len(files)) | |
| } | |
| // Don't call checker.Files on Unsafe, even with zero files, | |
| // because it would mutate the package, which is a global. | |
| if info.Pkg == types.Unsafe { | |
| if len(files) > 0 { | |
| panic(`"unsafe" package contains unexpected files`) | |
| } | |
| } else { | |
| // Ignore the returned (first) error since we | |
| // already collect them all in the PackageInfo. | |
| info.checker.Files(files) | |
| info.Files = append(info.Files, files...) | |
| } | |
| if imp.conf.AfterTypeCheck != nil { | |
| imp.conf.AfterTypeCheck(info, files) | |
| } | |
| if trace { | |
| fmt.Fprintf(os.Stderr, "%s: stop %q\n", | |
| time.Since(imp.start), info.Pkg.Path()) | |
| } | |
| } | |
| func (imp *importer) newPackageInfo(path, dir string) *PackageInfo { | |
| var pkg *types.Package | |
| if path == "unsafe" { | |
| pkg = types.Unsafe | |
| } else { | |
| pkg = types.NewPackage(path, "") | |
| } | |
| info := &PackageInfo{ | |
| Pkg: pkg, | |
| Info: types.Info{ | |
| Types: make(map[ast.Expr]types.TypeAndValue), | |
| Defs: make(map[*ast.Ident]types.Object), | |
| Uses: make(map[*ast.Ident]types.Object), | |
| Implicits: make(map[ast.Node]types.Object), | |
| Instances: make(map[*ast.Ident]types.Instance), | |
| Scopes: make(map[ast.Node]*types.Scope), | |
| Selections: make(map[*ast.SelectorExpr]*types.Selection), | |
| FileVersions: make(map[*ast.File]string), | |
| }, | |
| errorFunc: imp.conf.TypeChecker.Error, | |
| dir: dir, | |
| } | |
| // Copy the types.Config so we can vary it across PackageInfos. | |
| tc := imp.conf.TypeChecker | |
| tc.IgnoreFuncBodies = false | |
| if f := imp.conf.TypeCheckFuncBodies; f != nil { | |
| tc.IgnoreFuncBodies = !f(path) | |
| } | |
| tc.Importer = closure{imp, info} | |
| tc.Error = info.appendError // appendError wraps the user's Error function | |
| info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) | |
| imp.progMu.Lock() | |
| imp.prog.AllPackages[pkg] = info | |
| imp.progMu.Unlock() | |
| return info | |
| } | |
| type closure struct { | |
| imp *importer | |
| info *PackageInfo | |
| } | |
| func (c closure) Import(to string) (*types.Package, error) { return c.imp.doImport(c.info, to) } |