Alan Donovan | 713699d | 2013-08-27 18:49:13 -0400 | [diff] [blame] | 1 | // Copyright 2013 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 5 | package loader |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 6 | |
Alan Donovan | 1b6275a | 2015-04-16 15:42:28 -0400 | [diff] [blame] | 7 | // See doc.go for package documentation and implementation notes. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 8 | |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 9 | import ( |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 10 | "errors" |
Alan Donovan | 2a3a129 | 2013-07-30 14:28:14 -0400 | [diff] [blame] | 11 | "fmt" |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 12 | "go/ast" |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 13 | "go/build" |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 14 | "go/parser" |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 15 | "go/token" |
Alan Donovan | 542ffc7 | 2015-12-29 13:06:30 -0500 | [diff] [blame] | 16 | "go/types" |
Alan Donovan | 2a3a129 | 2013-07-30 14:28:14 -0400 | [diff] [blame] | 17 | "os" |
Alan Donovan | 6c803ab | 2016-11-28 16:48:17 -0500 | [diff] [blame] | 18 | "path/filepath" |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 19 | "sort" |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 20 | "strings" |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 21 | "sync" |
| 22 | "time" |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 23 | |
Peter Collingbourne | 4f8578d | 2015-01-08 18:32:51 -0800 | [diff] [blame] | 24 | "golang.org/x/tools/go/ast/astutil" |
Michael Matloob | 1f25352 | 2018-08-03 12:55:38 -0400 | [diff] [blame] | 25 | "golang.org/x/tools/go/internal/cgo" |
Tim King | 33002ea | 2022-02-14 21:42:10 +0000 | [diff] [blame] | 26 | "golang.org/x/tools/internal/typeparams" |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 27 | ) |
| 28 | |
Alan Donovan | 1071209 | 2016-01-08 13:18:57 -0500 | [diff] [blame] | 29 | var ignoreVendor build.ImportMode |
| 30 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 31 | const trace = false // show timing info for type-checking |
| 32 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 33 | // Config specifies the configuration for loading a whole program from |
| 34 | // Go source code. |
Alan Donovan | 3d82e7e | 2014-01-09 14:11:54 -0500 | [diff] [blame] | 35 | // The zero value for Config is a ready-to-use default configuration. |
Alan Donovan | 8a9eca1 | 2013-07-19 11:02:27 -0400 | [diff] [blame] | 36 | type Config struct { |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 37 | // Fset is the file set for the parser to use when loading the |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 38 | // program. If nil, it may be lazily initialized by any |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 39 | // method of Config. |
| 40 | Fset *token.FileSet |
| 41 | |
Alan Donovan | 2414677 | 2014-03-27 12:50:26 -0400 | [diff] [blame] | 42 | // ParserMode specifies the mode to be used by the parser when |
| 43 | // loading source packages. |
| 44 | ParserMode parser.Mode |
| 45 | |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 46 | // TypeChecker contains options relating to the type checker. |
Alan Donovan | f119874 | 2013-12-13 10:04:55 -0500 | [diff] [blame] | 47 | // |
| 48 | // The supplied IgnoreFuncBodies is not used; the effective |
| 49 | // value comes from the TypeCheckFuncBodies func below. |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 50 | // The supplied Import function is not used either. |
Robert Griesemer | 40a278e | 2013-07-18 17:07:44 -0700 | [diff] [blame] | 51 | TypeChecker types.Config |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 52 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 53 | // TypeCheckFuncBodies is a predicate over package paths. |
| 54 | // A package for which the predicate is false will |
Alan Donovan | f119874 | 2013-12-13 10:04:55 -0500 | [diff] [blame] | 55 | // have its package-level declarations type checked, but not |
| 56 | // its function bodies; this can be used to quickly load |
| 57 | // dependencies from source. If nil, all func bodies are type |
| 58 | // checked. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 59 | TypeCheckFuncBodies func(path string) bool |
Alan Donovan | f119874 | 2013-12-13 10:04:55 -0500 | [diff] [blame] | 60 | |
Alan Donovan | 3d82e7e | 2014-01-09 14:11:54 -0500 | [diff] [blame] | 61 | // If Build is non-nil, it is used to locate source packages. |
| 62 | // Otherwise &build.Default is used. |
Alan Donovan | 7746b67 | 2014-06-11 13:16:51 -0400 | [diff] [blame] | 63 | // |
| 64 | // By default, cgo is invoked to preprocess Go files that |
| 65 | // import the fake package "C". This behaviour can be |
| 66 | // disabled by setting CGO_ENABLED=0 in the environment prior |
| 67 | // to startup, or by setting Build.CgoEnabled=false. |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 68 | Build *build.Context |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 69 | |
Alan Donovan | 8cc1c75 | 2015-03-12 14:33:01 -0400 | [diff] [blame] | 70 | // The current directory, used for resolving relative package |
| 71 | // references such as "./go/loader". If empty, os.Getwd will be |
| 72 | // used instead. |
| 73 | Cwd string |
| 74 | |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 75 | // If DisplayPath is non-nil, it is used to transform each |
| 76 | // file name obtained from Build.Import(). This can be used |
| 77 | // to prevent a virtualized build.Config's file names from |
| 78 | // leaking into the user interface. |
| 79 | DisplayPath func(path string) string |
| 80 | |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 81 | // If AllowErrors is true, Load will return a Program even |
| 82 | // if some of the its packages contained I/O, parser or type |
| 83 | // errors; such errors are accessible via PackageInfo.Errors. If |
| 84 | // false, Load will fail if any package had an error. |
| 85 | AllowErrors bool |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 86 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 87 | // CreatePkgs specifies a list of non-importable initial |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 88 | // packages to create. The resulting packages will appear in |
| 89 | // the corresponding elements of the Program.Created slice. |
| 90 | CreatePkgs []PkgSpec |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 91 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 92 | // ImportPkgs specifies a set of initial packages to load. |
| 93 | // The map keys are package paths. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 94 | // |
| 95 | // The map value indicates whether to load tests. If true, Load |
| 96 | // will add and type-check two lists of files to the package: |
| 97 | // non-test files followed by in-package *_test.go files. In |
| 98 | // addition, it will append the external test package (if any) |
| 99 | // to Program.Created. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 100 | ImportPkgs map[string]bool |
Peter Collingbourne | d3e7567 | 2014-12-28 11:32:27 -0800 | [diff] [blame] | 101 | |
Alan Donovan | 51931f8 | 2015-02-20 11:23:27 -0500 | [diff] [blame] | 102 | // FindPackage is called during Load to create the build.Package |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 103 | // for a given import path from a given directory. |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 104 | // If FindPackage is nil, (*build.Context).Import is used. |
| 105 | // A client may use this hook to adapt to a proprietary build |
| 106 | // system that does not follow the "go build" layout |
| 107 | // conventions, for example. |
Alan Donovan | 51931f8 | 2015-02-20 11:23:27 -0500 | [diff] [blame] | 108 | // |
| 109 | // It must be safe to call concurrently from multiple goroutines. |
Kirill Smelkov | c2d03f4 | 2017-07-03 17:18:56 +0300 | [diff] [blame] | 110 | FindPackage func(ctxt *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error) |
Alan Donovan | 2336c53 | 2016-02-21 18:45:02 -0500 | [diff] [blame] | 111 | |
| 112 | // AfterTypeCheck is called immediately after a list of files |
| 113 | // has been type-checked and appended to info.Files. |
| 114 | // |
| 115 | // This optional hook function is the earliest opportunity for |
| 116 | // the client to observe the output of the type checker, |
| 117 | // which may be useful to reduce analysis latency when loading |
| 118 | // a large program. |
| 119 | // |
| 120 | // The function is permitted to modify info.Info, for instance |
| 121 | // to clear data structures that are no longer needed, which can |
| 122 | // dramatically reduce peak memory consumption. |
| 123 | // |
| 124 | // The function may be called twice for the same PackageInfo: |
| 125 | // once for the files of the package and again for the |
| 126 | // in-package test files. |
| 127 | // |
| 128 | // It must be safe to call concurrently from multiple goroutines. |
| 129 | AfterTypeCheck func(info *PackageInfo, files []*ast.File) |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 130 | } |
| 131 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 132 | // A PkgSpec specifies a non-importable package to be created by Load. |
| 133 | // Files are processed first, but typically only one of Files and |
| 134 | // Filenames is provided. The path needn't be globally unique. |
| 135 | // |
Alan Donovan | 6c803ab | 2016-11-28 16:48:17 -0500 | [diff] [blame] | 136 | // For vendoring purposes, the package's directory is the one that |
| 137 | // contains the first file. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 138 | type PkgSpec struct { |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 139 | Path string // package path ("" => use package declaration) |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 140 | Files []*ast.File // ASTs of already-parsed files |
| 141 | Filenames []string // names of files to be parsed |
Alan Donovan | 0dcaae1 | 2014-01-22 09:59:19 -0500 | [diff] [blame] | 142 | } |
| 143 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 144 | // A Program is a Go program loaded from source as specified by a Config. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 145 | type Program struct { |
| 146 | Fset *token.FileSet // the file set for this program |
| 147 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 148 | // Created[i] contains the initial package whose ASTs or |
| 149 | // filenames were supplied by Config.CreatePkgs[i], followed by |
| 150 | // the external test package, if any, of each package in |
| 151 | // Config.ImportPkgs ordered by ImportPath. |
Alan Donovan | 9d2ff75 | 2015-10-05 10:55:06 -0400 | [diff] [blame] | 152 | // |
| 153 | // NOTE: these files must not import "C". Cgo preprocessing is |
| 154 | // only performed on imported packages, not ad hoc packages. |
| 155 | // |
| 156 | // TODO(adonovan): we need to copy and adapt the logic of |
| 157 | // goFilesPackage (from $GOROOT/src/cmd/go/build.go) and make |
| 158 | // Config.Import and Config.Create methods return the same kind |
| 159 | // of entity, essentially a build.Package. |
| 160 | // Perhaps we can even reuse that type directly. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 161 | Created []*PackageInfo |
| 162 | |
| 163 | // Imported contains the initially imported packages, |
| 164 | // as specified by Config.ImportPkgs. |
| 165 | Imported map[string]*PackageInfo |
| 166 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 167 | // AllPackages contains the PackageInfo of every package |
| 168 | // encountered by Load: all initial packages and all |
| 169 | // dependencies, including incomplete ones. |
| 170 | AllPackages map[*types.Package]*PackageInfo |
Alan Donovan | 1b6275a | 2015-04-16 15:42:28 -0400 | [diff] [blame] | 171 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 172 | // importMap is the canonical mapping of package paths to |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 173 | // packages. It contains all Imported initial packages, but not |
| 174 | // Created ones, and all imported dependencies. |
Alan Donovan | 1b6275a | 2015-04-16 15:42:28 -0400 | [diff] [blame] | 175 | importMap map[string]*types.Package |
Alan Donovan | 3d82e7e | 2014-01-09 14:11:54 -0500 | [diff] [blame] | 176 | } |
| 177 | |
Alan Donovan | cd61993 | 2014-07-17 15:10:29 -0400 | [diff] [blame] | 178 | // PackageInfo holds the ASTs and facts derived by the type-checker |
| 179 | // for a single package. |
| 180 | // |
| 181 | // Not mutated once exposed via the API. |
Alan Donovan | cd61993 | 2014-07-17 15:10:29 -0400 | [diff] [blame] | 182 | type PackageInfo struct { |
| 183 | Pkg *types.Package |
| 184 | Importable bool // true if 'import "Pkg.Path()"' would resolve to this |
| 185 | TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors |
| 186 | Files []*ast.File // syntax trees for the package's files |
| 187 | Errors []error // non-nil if the package had errors |
| 188 | types.Info // type-checker deductions. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 189 | dir string // package directory |
Alan Donovan | cd61993 | 2014-07-17 15:10:29 -0400 | [diff] [blame] | 190 | |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 191 | checker *types.Checker // transient type-checker state |
| 192 | errorFunc func(error) |
Alan Donovan | cd61993 | 2014-07-17 15:10:29 -0400 | [diff] [blame] | 193 | } |
| 194 | |
| 195 | func (info *PackageInfo) String() string { return info.Pkg.Path() } |
| 196 | |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 197 | func (info *PackageInfo) appendError(err error) { |
| 198 | if info.errorFunc != nil { |
| 199 | info.errorFunc(err) |
| 200 | } else { |
| 201 | fmt.Fprintln(os.Stderr, err) |
| 202 | } |
| 203 | info.Errors = append(info.Errors, err) |
| 204 | } |
| 205 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 206 | func (conf *Config) fset() *token.FileSet { |
| 207 | if conf.Fset == nil { |
| 208 | conf.Fset = token.NewFileSet() |
Alan Donovan | f119874 | 2013-12-13 10:04:55 -0500 | [diff] [blame] | 209 | } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 210 | return conf.Fset |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 211 | } |
| 212 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 213 | // ParseFile is a convenience function (intended for testing) that invokes |
| 214 | // the parser using the Config's FileSet, which is initialized if nil. |
| 215 | // |
| 216 | // src specifies the parser input as a string, []byte, or io.Reader, and |
| 217 | // filename is its apparent name. If src is nil, the contents of |
| 218 | // filename are read from the file system. |
Alan Donovan | 2414677 | 2014-03-27 12:50:26 -0400 | [diff] [blame] | 219 | func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) { |
Alan Donovan | b8d26f5 | 2014-11-13 12:34:25 -0500 | [diff] [blame] | 220 | // TODO(adonovan): use conf.build() etc like parseFiles does. |
Alan Donovan | 2414677 | 2014-03-27 12:50:26 -0400 | [diff] [blame] | 221 | return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode) |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 222 | } |
| 223 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 224 | // FromArgsUsage is a partial usage message that applications calling |
| 225 | // FromArgs may wish to include in their -help output. |
| 226 | const FromArgsUsage = ` |
Alan Donovan | 9cce475 | 2013-10-10 12:37:49 -0400 | [diff] [blame] | 227 | <args> is a list of arguments denoting a set of initial packages. |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 228 | It may take one of two forms: |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 229 | |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 230 | 1. A list of *.go source files. |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 231 | |
| 232 | All of the specified files are loaded, parsed and type-checked |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 233 | as a single package. All the files must belong to the same directory. |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 234 | |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 235 | 2. A list of import paths, each denoting a package. |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 236 | |
| 237 | The package's directory is found relative to the $GOROOT and |
| 238 | $GOPATH using similar logic to 'go build', and the *.go files in |
Alan Donovan | 9cce475 | 2013-10-10 12:37:49 -0400 | [diff] [blame] | 239 | that directory are loaded, parsed and type-checked as a single |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 240 | package. |
| 241 | |
| 242 | In addition, all *_test.go files in the directory are then loaded |
| 243 | and parsed. Those files whose package declaration equals that of |
| 244 | the non-*_test.go files are included in the primary package. Test |
| 245 | files whose package declaration ends with "_test" are type-checked |
| 246 | as another package, the 'external' test package, so that a single |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 247 | import path may denote two packages. (Whether this behaviour is |
| 248 | enabled is tool-specific, and may depend on additional flags.) |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 249 | |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 250 | A '--' argument terminates the list of packages. |
| 251 | ` |
| 252 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 253 | // FromArgs interprets args as a set of initial packages to load from |
| 254 | // source and updates the configuration. It returns the list of |
| 255 | // unconsumed arguments. |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 256 | // |
| 257 | // It is intended for use in command-line interfaces that require a |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 258 | // set of initial packages to be specified; see FromArgsUsage message |
| 259 | // for details. |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 260 | // |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 261 | // Only superficial errors are reported at this stage; errors dependent |
| 262 | // on I/O are detected during Load. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 263 | func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) { |
| 264 | var rest []string |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 265 | for i, arg := range args { |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 266 | if arg == "--" { |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 267 | rest = args[i+1:] |
| 268 | args = args[:i] |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 269 | break // consume "--" and return the remaining args |
| 270 | } |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 271 | } |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 272 | |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 273 | if len(args) > 0 && strings.HasSuffix(args[0], ".go") { |
| 274 | // Assume args is a list of a *.go files |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 275 | // denoting a single ad hoc package. |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 276 | for _, arg := range args { |
| 277 | if !strings.HasSuffix(arg, ".go") { |
| 278 | return nil, fmt.Errorf("named files must be .go files: %s", arg) |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 279 | } |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 280 | } |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 281 | conf.CreateFromFilenames("", args...) |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 282 | } else { |
| 283 | // Assume args are directories each denoting a |
| 284 | // package and (perhaps) an external test, iff xtest. |
| 285 | for _, arg := range args { |
| 286 | if xtest { |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 287 | conf.ImportWithTests(arg) |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 288 | } else { |
| 289 | conf.Import(arg) |
| 290 | } |
Alan Donovan | 3f2f9a7 | 2013-09-06 18:13:57 -0400 | [diff] [blame] | 291 | } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 292 | } |
Alan Donovan | 08fadac | 2014-02-11 16:52:16 -0500 | [diff] [blame] | 293 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 294 | return rest, nil |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 295 | } |
| 296 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 297 | // CreateFromFilenames is a convenience function that adds |
| 298 | // a conf.CreatePkgs entry to create a package of the specified *.go |
| 299 | // files. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 300 | func (conf *Config) CreateFromFilenames(path string, filenames ...string) { |
| 301 | conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames}) |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 302 | } |
| 303 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 304 | // CreateFromFiles is a convenience function that adds a conf.CreatePkgs |
Alan Donovan | 0dcaae1 | 2014-01-22 09:59:19 -0500 | [diff] [blame] | 305 | // entry to create package of the specified path and parsed files. |
Alan Donovan | 0dcaae1 | 2014-01-22 09:59:19 -0500 | [diff] [blame] | 306 | func (conf *Config) CreateFromFiles(path string, files ...*ast.File) { |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 307 | conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files}) |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 308 | } |
| 309 | |
| 310 | // ImportWithTests is a convenience function that adds path to |
| 311 | // ImportPkgs, the set of initial source packages located relative to |
| 312 | // $GOPATH. The package will be augmented by any *_test.go files in |
| 313 | // its directory that contain a "package x" (not "package x_test") |
| 314 | // declaration. |
| 315 | // |
Alan Donovan | 0dcaae1 | 2014-01-22 09:59:19 -0500 | [diff] [blame] | 316 | // In addition, if any *_test.go files contain a "package x_test" |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 317 | // declaration, an additional package comprising just those files will |
| 318 | // be added to CreatePkgs. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 319 | func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 320 | |
| 321 | // Import is a convenience function that adds path to ImportPkgs, the |
| 322 | // set of initial packages that will be imported from source. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 323 | func (conf *Config) Import(path string) { conf.addImport(path, false) } |
| 324 | |
| 325 | func (conf *Config) addImport(path string, tests bool) { |
Alan Donovan | ae186f5 | 2016-01-08 14:46:14 -0500 | [diff] [blame] | 326 | if path == "C" { |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 327 | return // ignore; not a real package |
| 328 | } |
| 329 | if conf.ImportPkgs == nil { |
| 330 | conf.ImportPkgs = make(map[string]bool) |
| 331 | } |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 332 | conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 333 | } |
Alan Donovan | fb3d862 | 2013-12-09 09:36:29 -0500 | [diff] [blame] | 334 | |
| 335 | // PathEnclosingInterval returns the PackageInfo and ast.Node that |
| 336 | // contain source interval [start, end), and all the node's ancestors |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 337 | // up to the AST root. It searches all ast.Files of all packages in prog. |
| 338 | // exact is defined as for astutil.PathEnclosingInterval. |
Alan Donovan | fb3d862 | 2013-12-09 09:36:29 -0500 | [diff] [blame] | 339 | // |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 340 | // The zero value is returned if not found. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 341 | func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { |
| 342 | for _, info := range prog.AllPackages { |
Alan Donovan | fb3d862 | 2013-12-09 09:36:29 -0500 | [diff] [blame] | 343 | for _, f := range info.Files { |
Alan Donovan | 3d18472 | 2015-05-20 15:58:44 -0400 | [diff] [blame] | 344 | if f.Pos() == token.NoPos { |
| 345 | // This can happen if the parser saw |
| 346 | // too many errors and bailed out. |
| 347 | // (Use parser.AllErrors to prevent that.) |
| 348 | continue |
| 349 | } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 350 | if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) { |
Alan Donovan | fb3d862 | 2013-12-09 09:36:29 -0500 | [diff] [blame] | 351 | continue |
| 352 | } |
| 353 | if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { |
| 354 | return info, path, exact |
| 355 | } |
| 356 | } |
| 357 | } |
| 358 | return nil, nil, false |
| 359 | } |
| 360 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 361 | // InitialPackages returns a new slice containing the set of initial |
| 362 | // packages (Created + Imported) in unspecified order. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 363 | func (prog *Program) InitialPackages() []*PackageInfo { |
| 364 | infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported)) |
| 365 | infos = append(infos, prog.Created...) |
| 366 | for _, info := range prog.Imported { |
| 367 | infos = append(infos, info) |
| 368 | } |
| 369 | return infos |
| 370 | } |
| 371 | |
Alan Donovan | 1b6275a | 2015-04-16 15:42:28 -0400 | [diff] [blame] | 372 | // Package returns the ASTs and results of type checking for the |
| 373 | // specified package. |
| 374 | func (prog *Program) Package(path string) *PackageInfo { |
| 375 | if info, ok := prog.AllPackages[prog.importMap[path]]; ok { |
| 376 | return info |
| 377 | } |
| 378 | for _, info := range prog.Created { |
| 379 | if path == info.Pkg.Path() { |
| 380 | return info |
| 381 | } |
| 382 | } |
| 383 | return nil |
| 384 | } |
| 385 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 386 | // ---------- Implementation ---------- |
| 387 | |
| 388 | // importer holds the working state of the algorithm. |
| 389 | type importer struct { |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 390 | conf *Config // the client configuration |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 391 | start time.Time // for logging |
| 392 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 393 | progMu sync.Mutex // guards prog |
| 394 | prog *Program // the resulting program |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 395 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 396 | // findpkg is a memoization of FindPackage. |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 397 | findpkgMu sync.Mutex // guards findpkg |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 398 | findpkg map[findpkgKey]*findpkgValue |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 399 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 400 | importedMu sync.Mutex // guards imported |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 401 | imported map[string]*importInfo // all imported packages (incl. failures) by import path |
| 402 | |
| 403 | // import dependency graph: graph[x][y] => x imports y |
| 404 | // |
| 405 | // Since non-importable packages cannot be cyclic, we ignore |
| 406 | // their imports, thus we only need the subgraph over importable |
| 407 | // packages. Nodes are identified by their import paths. |
| 408 | graphMu sync.Mutex |
| 409 | graph map[string]map[string]bool |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 410 | } |
| 411 | |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 412 | type findpkgKey struct { |
| 413 | importPath string |
| 414 | fromDir string |
| 415 | mode build.ImportMode |
| 416 | } |
| 417 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 418 | type findpkgValue struct { |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 419 | ready chan struct{} // closed to broadcast readiness |
| 420 | bp *build.Package |
| 421 | err error |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 422 | } |
| 423 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 424 | // importInfo tracks the success or failure of a single import. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 425 | // |
| 426 | // Upon completion, exactly one of info and err is non-nil: |
| 427 | // info on successful creation of a package, err otherwise. |
| 428 | // A successful package may still contain type errors. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 429 | type importInfo struct { |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 430 | path string // import path |
| 431 | info *PackageInfo // results of typechecking (including errors) |
| 432 | complete chan struct{} // closed to broadcast that info is set. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 433 | } |
| 434 | |
| 435 | // awaitCompletion blocks until ii is complete, |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 436 | // i.e. the info field is safe to inspect. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 437 | func (ii *importInfo) awaitCompletion() { |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 438 | <-ii.complete // wait for close |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 439 | } |
| 440 | |
| 441 | // Complete marks ii as complete. |
| 442 | // Its info and err fields will not be subsequently updated. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 443 | func (ii *importInfo) Complete(info *PackageInfo) { |
| 444 | if info == nil { |
| 445 | panic("info == nil") |
Alan Donovan | 739a26a | 2015-07-29 14:08:07 -0400 | [diff] [blame] | 446 | } |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 447 | ii.info = info |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 448 | close(ii.complete) |
| 449 | } |
| 450 | |
| 451 | type importError struct { |
| 452 | path string // import path |
| 453 | err error // reason for failure to create a package |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 454 | } |
| 455 | |
| 456 | // Load creates the initial packages specified by conf.{Create,Import}Pkgs, |
| 457 | // loading their dependencies packages as needed. |
| 458 | // |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 459 | // On success, Load returns a Program containing a PackageInfo for |
| 460 | // each package. On failure, it returns an error. |
| 461 | // |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 462 | // If AllowErrors is true, Load will return a Program even if some |
| 463 | // packages contained I/O, parser or type errors, or if dependencies |
| 464 | // were missing. (Such errors are accessible via PackageInfo.Errors. If |
| 465 | // false, Load will fail if any package had an error. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 466 | // |
| 467 | // It is an error if no packages were loaded. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 468 | func (conf *Config) Load() (*Program, error) { |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 469 | // Create a simple default error handler for parse/type errors. |
| 470 | if conf.TypeChecker.Error == nil { |
| 471 | conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) } |
| 472 | } |
| 473 | |
Alan Donovan | 8cc1c75 | 2015-03-12 14:33:01 -0400 | [diff] [blame] | 474 | // Set default working directory for relative package references. |
| 475 | if conf.Cwd == "" { |
| 476 | var err error |
| 477 | conf.Cwd, err = os.Getwd() |
| 478 | if err != nil { |
| 479 | return nil, err |
| 480 | } |
| 481 | } |
| 482 | |
| 483 | // Install default FindPackage hook using go/build logic. |
Alan Donovan | 51931f8 | 2015-02-20 11:23:27 -0500 | [diff] [blame] | 484 | if conf.FindPackage == nil { |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 485 | conf.FindPackage = (*build.Context).Import |
Alan Donovan | 51931f8 | 2015-02-20 11:23:27 -0500 | [diff] [blame] | 486 | } |
| 487 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 488 | prog := &Program{ |
| 489 | Fset: conf.fset(), |
| 490 | Imported: make(map[string]*PackageInfo), |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 491 | importMap: make(map[string]*types.Package), |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 492 | AllPackages: make(map[*types.Package]*PackageInfo), |
| 493 | } |
| 494 | |
| 495 | imp := importer{ |
| 496 | conf: conf, |
| 497 | prog: prog, |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 498 | findpkg: make(map[findpkgKey]*findpkgValue), |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 499 | imported: make(map[string]*importInfo), |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 500 | start: time.Now(), |
| 501 | graph: make(map[string]map[string]bool), |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 502 | } |
| 503 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 504 | // -- loading proper (concurrent phase) -------------------------------- |
| 505 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 506 | var errpkgs []string // packages that contained errors |
| 507 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 508 | // Load the initially imported packages and their dependencies, |
| 509 | // in parallel. |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 510 | // No vendor check on packages imported from the command line. |
Alan Donovan | 1071209 | 2016-01-08 13:18:57 -0500 | [diff] [blame] | 511 | infos, importErrors := imp.importAll("", conf.Cwd, conf.ImportPkgs, ignoreVendor) |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 512 | for _, ie := range importErrors { |
| 513 | conf.TypeChecker.Error(ie.err) // failed to create package |
| 514 | errpkgs = append(errpkgs, ie.path) |
| 515 | } |
| 516 | for _, info := range infos { |
| 517 | prog.Imported[info.Pkg.Path()] = info |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 518 | } |
| 519 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 520 | // Augment the designated initial packages by their tests. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 521 | // Dependencies are loaded in parallel. |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 522 | var xtestPkgs []*build.Package |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 523 | for importPath, augment := range conf.ImportPkgs { |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 524 | if !augment { |
| 525 | continue |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 526 | } |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 527 | |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 528 | // No vendor check on packages imported from command line. |
Alan Donovan | 1071209 | 2016-01-08 13:18:57 -0500 | [diff] [blame] | 529 | bp, err := imp.findPackage(importPath, conf.Cwd, ignoreVendor) |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 530 | if err != nil { |
| 531 | // Package not found, or can't even parse package declaration. |
| 532 | // Already reported by previous loop; ignore it. |
| 533 | continue |
| 534 | } |
| 535 | |
| 536 | // Needs external test package? |
| 537 | if len(bp.XTestGoFiles) > 0 { |
| 538 | xtestPkgs = append(xtestPkgs, bp) |
| 539 | } |
| 540 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 541 | // Consult the cache using the canonical package path. |
| 542 | path := bp.ImportPath |
Alan Donovan | 739a26a | 2015-07-29 14:08:07 -0400 | [diff] [blame] | 543 | imp.importedMu.Lock() // (unnecessary, we're sequential here) |
| 544 | ii, ok := imp.imported[path] |
| 545 | // Paranoid checks added due to issue #11012. |
| 546 | if !ok { |
| 547 | // Unreachable. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 548 | // The previous loop called importAll and thus |
Alan Donovan | 739a26a | 2015-07-29 14:08:07 -0400 | [diff] [blame] | 549 | // startLoad for each path in ImportPkgs, which |
| 550 | // populates imp.imported[path] with a non-zero value. |
| 551 | panic(fmt.Sprintf("imported[%q] not found", path)) |
| 552 | } |
| 553 | if ii == nil { |
| 554 | // Unreachable. |
| 555 | // The ii values in this loop are the same as in |
| 556 | // the previous loop, which enforced the invariant |
| 557 | // that at least one of ii.err and ii.info is non-nil. |
| 558 | panic(fmt.Sprintf("imported[%q] == nil", path)) |
| 559 | } |
Alan Donovan | 739a26a | 2015-07-29 14:08:07 -0400 | [diff] [blame] | 560 | if ii.info == nil { |
| 561 | // Unreachable. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 562 | // awaitCompletion has the postcondition |
| 563 | // ii.info != nil. |
Alan Donovan | 739a26a | 2015-07-29 14:08:07 -0400 | [diff] [blame] | 564 | panic(fmt.Sprintf("imported[%q].info = nil", path)) |
| 565 | } |
| 566 | info := ii.info |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 567 | imp.importedMu.Unlock() |
| 568 | |
| 569 | // Parse the in-package test files. |
| 570 | files, errs := imp.conf.parsePackageFiles(bp, 't') |
| 571 | for _, err := range errs { |
| 572 | info.appendError(err) |
| 573 | } |
| 574 | |
| 575 | // The test files augmenting package P cannot be imported, |
| 576 | // but may import packages that import P, |
| 577 | // so we must disable the cycle check. |
| 578 | imp.addFiles(info, files, false) |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 579 | } |
| 580 | |
Alan Donovan | 6c803ab | 2016-11-28 16:48:17 -0500 | [diff] [blame] | 581 | createPkg := func(path, dir string, files []*ast.File, errs []error) { |
| 582 | info := imp.newPackageInfo(path, dir) |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 583 | for _, err := range errs { |
| 584 | info.appendError(err) |
| 585 | } |
| 586 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 587 | // Ad hoc packages are non-importable, |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 588 | // so no cycle check is needed. |
| 589 | // addFiles loads dependencies in parallel. |
| 590 | imp.addFiles(info, files, false) |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 591 | prog.Created = append(prog.Created, info) |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 592 | } |
| 593 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 594 | // Create packages specified by conf.CreatePkgs. |
| 595 | for _, cp := range conf.CreatePkgs { |
Alan Donovan | b56ef30 | 2017-02-07 15:33:46 -0500 | [diff] [blame] | 596 | files, errs := parseFiles(conf.fset(), conf.build(), nil, conf.Cwd, cp.Filenames, conf.ParserMode) |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 597 | files = append(files, cp.Files...) |
| 598 | |
| 599 | path := cp.Path |
Alan Donovan | 8913eae | 2015-02-27 17:05:06 -0500 | [diff] [blame] | 600 | if path == "" { |
| 601 | if len(files) > 0 { |
| 602 | path = files[0].Name.Name |
| 603 | } else { |
| 604 | path = "(unnamed)" |
| 605 | } |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 606 | } |
Alan Donovan | 6c803ab | 2016-11-28 16:48:17 -0500 | [diff] [blame] | 607 | |
Alan Donovan | b56ef30 | 2017-02-07 15:33:46 -0500 | [diff] [blame] | 608 | dir := conf.Cwd |
Alan Donovan | 6c803ab | 2016-11-28 16:48:17 -0500 | [diff] [blame] | 609 | if len(files) > 0 && files[0].Pos().IsValid() { |
| 610 | dir = filepath.Dir(conf.fset().File(files[0].Pos()).Name()) |
| 611 | } |
| 612 | createPkg(path, dir, files, errs) |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 613 | } |
| 614 | |
| 615 | // Create external test packages. |
| 616 | sort.Sort(byImportPath(xtestPkgs)) |
| 617 | for _, bp := range xtestPkgs { |
| 618 | files, errs := imp.conf.parsePackageFiles(bp, 'x') |
Alan Donovan | 6c803ab | 2016-11-28 16:48:17 -0500 | [diff] [blame] | 619 | createPkg(bp.ImportPath+"_test", bp.Dir, files, errs) |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 620 | } |
| 621 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 622 | // -- finishing up (sequential) ---------------------------------------- |
| 623 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 624 | if len(prog.Imported)+len(prog.Created) == 0 { |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 625 | return nil, errors.New("no initial packages were loaded") |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 626 | } |
| 627 | |
| 628 | // Create infos for indirectly imported packages. |
| 629 | // e.g. incomplete packages without syntax, loaded from export data. |
Alan Donovan | 1b6275a | 2015-04-16 15:42:28 -0400 | [diff] [blame] | 630 | for _, obj := range prog.importMap { |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 631 | info := prog.AllPackages[obj] |
| 632 | if info == nil { |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 633 | prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true} |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 634 | } else { |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 635 | // finished |
| 636 | info.checker = nil |
| 637 | info.errorFunc = nil |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 638 | } |
| 639 | } |
| 640 | |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 641 | if !conf.AllowErrors { |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 642 | // Report errors in indirectly imported packages. |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 643 | for _, info := range prog.AllPackages { |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 644 | if len(info.Errors) > 0 { |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 645 | errpkgs = append(errpkgs, info.Pkg.Path()) |
| 646 | } |
| 647 | } |
| 648 | if errpkgs != nil { |
Alan Donovan | f13ba78 | 2014-08-14 12:10:34 -0400 | [diff] [blame] | 649 | var more string |
| 650 | if len(errpkgs) > 3 { |
| 651 | more = fmt.Sprintf(" and %d more", len(errpkgs)-3) |
| 652 | errpkgs = errpkgs[:3] |
| 653 | } |
| 654 | return nil, fmt.Errorf("couldn't load packages due to errors: %s%s", |
| 655 | strings.Join(errpkgs, ", "), more) |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 656 | } |
| 657 | } |
| 658 | |
| 659 | markErrorFreePackages(prog.AllPackages) |
| 660 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 661 | return prog, nil |
| 662 | } |
| 663 | |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 664 | type byImportPath []*build.Package |
| 665 | |
| 666 | func (b byImportPath) Len() int { return len(b) } |
| 667 | func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath } |
| 668 | func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
| 669 | |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 670 | // markErrorFreePackages sets the TransitivelyErrorFree flag on all |
| 671 | // applicable packages. |
| 672 | func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) { |
| 673 | // Build the transpose of the import graph. |
| 674 | importedBy := make(map[*types.Package]map[*types.Package]bool) |
| 675 | for P := range allPackages { |
| 676 | for _, Q := range P.Imports() { |
| 677 | clients, ok := importedBy[Q] |
| 678 | if !ok { |
| 679 | clients = make(map[*types.Package]bool) |
| 680 | importedBy[Q] = clients |
| 681 | } |
| 682 | clients[P] = true |
| 683 | } |
| 684 | } |
| 685 | |
| 686 | // Find all packages reachable from some error package. |
| 687 | reachable := make(map[*types.Package]bool) |
| 688 | var visit func(*types.Package) |
| 689 | visit = func(p *types.Package) { |
| 690 | if !reachable[p] { |
| 691 | reachable[p] = true |
| 692 | for q := range importedBy[p] { |
| 693 | visit(q) |
| 694 | } |
| 695 | } |
| 696 | } |
| 697 | for _, info := range allPackages { |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 698 | if len(info.Errors) > 0 { |
Alan Donovan | ed45af7 | 2014-02-18 19:43:14 -0500 | [diff] [blame] | 699 | visit(info.Pkg) |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | // Mark the others as "transitively error-free". |
| 704 | for _, info := range allPackages { |
| 705 | if !reachable[info.Pkg] { |
| 706 | info.TransitivelyErrorFree = true |
| 707 | } |
| 708 | } |
| 709 | } |
| 710 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 711 | // build returns the effective build context. |
| 712 | func (conf *Config) build() *build.Context { |
| 713 | if conf.Build != nil { |
| 714 | return conf.Build |
| 715 | } |
| 716 | return &build.Default |
| 717 | } |
| 718 | |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 719 | // parsePackageFiles enumerates the files belonging to package path, |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 720 | // then loads, parses and returns them, plus a list of I/O or parse |
| 721 | // errors that were encountered. |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 722 | // |
| 723 | // 'which' indicates which files to include: |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 724 | // |
Russ Cox | d5f48fc | 2022-04-11 23:03:04 -0400 | [diff] [blame] | 725 | // 'g': include non-test *.go source files (GoFiles + processed CgoFiles) |
| 726 | // 't': include in-package *_test.go source files (TestGoFiles) |
| 727 | // 'x': include external *_test.go source files. (XTestGoFiles) |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 728 | func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) { |
Alan Donovan | 02f1928 | 2016-02-04 11:15:46 -0500 | [diff] [blame] | 729 | if bp.ImportPath == "unsafe" { |
Alan Donovan | ae186f5 | 2016-01-08 14:46:14 -0500 | [diff] [blame] | 730 | return nil, nil |
| 731 | } |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 732 | var filenames []string |
| 733 | switch which { |
| 734 | case 'g': |
| 735 | filenames = bp.GoFiles |
| 736 | case 't': |
| 737 | filenames = bp.TestGoFiles |
| 738 | case 'x': |
| 739 | filenames = bp.XTestGoFiles |
| 740 | default: |
| 741 | panic(which) |
| 742 | } |
Alan Donovan | 7746b67 | 2014-06-11 13:16:51 -0400 | [diff] [blame] | 743 | |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 744 | files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode) |
Alan Donovan | 7746b67 | 2014-06-11 13:16:51 -0400 | [diff] [blame] | 745 | |
| 746 | // Preprocess CgoFiles and parse the outputs (sequentially). |
| 747 | if which == 'g' && bp.CgoFiles != nil { |
Michael Matloob | 62181fa | 2018-08-07 15:25:39 -0400 | [diff] [blame] | 748 | cgofiles, err := cgo.ProcessFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode) |
Alan Donovan | 7746b67 | 2014-06-11 13:16:51 -0400 | [diff] [blame] | 749 | if err != nil { |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 750 | errs = append(errs, err) |
| 751 | } else { |
| 752 | files = append(files, cgofiles...) |
Alan Donovan | 7746b67 | 2014-06-11 13:16:51 -0400 | [diff] [blame] | 753 | } |
Alan Donovan | 7746b67 | 2014-06-11 13:16:51 -0400 | [diff] [blame] | 754 | } |
| 755 | |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 756 | return files, errs |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 757 | } |
| 758 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 759 | // doImport imports the package denoted by path. |
| 760 | // It implements the types.Importer signature. |
| 761 | // |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 762 | // It returns an error if a package could not be created |
| 763 | // (e.g. go/build or parse error), but type errors are reported via |
| 764 | // the types.Config.Error callback (the first of which is also saved |
| 765 | // in the package's PackageInfo). |
| 766 | // |
| 767 | // Idempotent. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 768 | func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) { |
Alan Donovan | c5ca59a | 2015-07-14 13:57:11 -0400 | [diff] [blame] | 769 | if to == "C" { |
| 770 | // This should be unreachable, but ad hoc packages are |
| 771 | // not currently subject to cgo preprocessing. |
Alan Donovan | 22934f0 | 2018-12-04 17:08:00 -0500 | [diff] [blame] | 772 | // See https://golang.org/issue/11627. |
Alan Donovan | c5ca59a | 2015-07-14 13:57:11 -0400 | [diff] [blame] | 773 | return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`, |
| 774 | from.Pkg.Path()) |
| 775 | } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 776 | |
Alan Donovan | 1071209 | 2016-01-08 13:18:57 -0500 | [diff] [blame] | 777 | bp, err := imp.findPackage(to, from.dir, 0) |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 778 | if err != nil { |
| 779 | return nil, err |
| 780 | } |
| 781 | |
Alan Donovan | ae186f5 | 2016-01-08 14:46:14 -0500 | [diff] [blame] | 782 | // The standard unsafe package is handled specially, |
| 783 | // and has no PackageInfo. |
Alan Donovan | 02f1928 | 2016-02-04 11:15:46 -0500 | [diff] [blame] | 784 | if bp.ImportPath == "unsafe" { |
Alan Donovan | ae186f5 | 2016-01-08 14:46:14 -0500 | [diff] [blame] | 785 | return types.Unsafe, nil |
| 786 | } |
| 787 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 788 | // Look for the package in the cache using its canonical path. |
| 789 | path := bp.ImportPath |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 790 | imp.importedMu.Lock() |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 791 | ii := imp.imported[path] |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 792 | imp.importedMu.Unlock() |
| 793 | if ii == nil { |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 794 | panic("internal error: unexpected import: " + path) |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 795 | } |
| 796 | if ii.info != nil { |
| 797 | return ii.info.Pkg, nil |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 798 | } |
| 799 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 800 | // Import of incomplete package: this indicates a cycle. |
| 801 | fromPath := from.Pkg.Path() |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 802 | if cycle := imp.findPath(path, fromPath); cycle != nil { |
Michael Matloob | 9e48ab1 | 2019-07-12 17:14:28 -0400 | [diff] [blame] | 803 | // Normalize cycle: start from alphabetically largest node. |
| 804 | pos, start := -1, "" |
| 805 | for i, s := range cycle { |
| 806 | if pos < 0 || s > start { |
| 807 | pos, start = i, s |
| 808 | } |
| 809 | } |
| 810 | cycle = append(cycle, cycle[:pos]...)[pos:] // rotate cycle to start from largest |
| 811 | cycle = append(cycle, cycle[0]) // add start node to end to show cycliness |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 812 | return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> ")) |
| 813 | } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 814 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 815 | panic("internal error: import of incomplete (yet acyclic) package: " + fromPath) |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 816 | } |
| 817 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 818 | // findPackage locates the package denoted by the importPath in the |
| 819 | // specified directory. |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 820 | func (imp *importer) findPackage(importPath, fromDir string, mode build.ImportMode) (*build.Package, error) { |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 821 | // We use a non-blocking duplicate-suppressing cache (gopl.io §9.7) |
| 822 | // to avoid holding the lock around FindPackage. |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 823 | key := findpkgKey{importPath, fromDir, mode} |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 824 | imp.findpkgMu.Lock() |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 825 | v, ok := imp.findpkg[key] |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 826 | if ok { |
| 827 | // cache hit |
| 828 | imp.findpkgMu.Unlock() |
| 829 | |
| 830 | <-v.ready // wait for entry to become ready |
| 831 | } else { |
| 832 | // Cache miss: this goroutine becomes responsible for |
| 833 | // populating the map entry and broadcasting its readiness. |
| 834 | v = &findpkgValue{ready: make(chan struct{})} |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 835 | imp.findpkg[key] = v |
Alan Donovan | 5a2875a | 2016-01-12 12:34:15 -0500 | [diff] [blame] | 836 | imp.findpkgMu.Unlock() |
| 837 | |
| 838 | ioLimit <- true |
| 839 | v.bp, v.err = imp.conf.FindPackage(imp.conf.build(), importPath, fromDir, mode) |
| 840 | <-ioLimit |
| 841 | |
| 842 | if _, ok := v.err.(*build.NoGoError); ok { |
| 843 | v.err = nil // empty directory is not an error |
| 844 | } |
| 845 | |
| 846 | close(v.ready) // broadcast ready condition |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 847 | } |
| 848 | return v.bp, v.err |
| 849 | } |
| 850 | |
| 851 | // importAll loads, parses, and type-checks the specified packages in |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 852 | // parallel and returns their completed importInfos in unspecified order. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 853 | // |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 854 | // fromPath is the package path of the importing package, if it is |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 855 | // importable, "" otherwise. It is used for cycle detection. |
| 856 | // |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 857 | // fromDir is the directory containing the import declaration that |
| 858 | // caused these imports. |
Alan Donovan | 0f870d4 | 2016-01-06 11:34:23 -0500 | [diff] [blame] | 859 | func (imp *importer) importAll(fromPath, fromDir string, imports map[string]bool, mode build.ImportMode) (infos []*PackageInfo, errors []importError) { |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 860 | if fromPath != "" { |
| 861 | // We're loading a set of imports. |
| 862 | // |
| 863 | // We must record graph edges from the importing package |
| 864 | // to its dependencies, and check for cycles. |
| 865 | imp.graphMu.Lock() |
| 866 | deps, ok := imp.graph[fromPath] |
| 867 | if !ok { |
| 868 | deps = make(map[string]bool) |
| 869 | imp.graph[fromPath] = deps |
| 870 | } |
Russ Cox | 06713c2 | 2021-02-18 15:04:08 -0500 | [diff] [blame] | 871 | for importPath := range imports { |
| 872 | deps[importPath] = true |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 873 | } |
| 874 | imp.graphMu.Unlock() |
| 875 | } |
| 876 | |
Russ Cox | 06713c2 | 2021-02-18 15:04:08 -0500 | [diff] [blame] | 877 | var pending []*importInfo |
| 878 | for importPath := range imports { |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 879 | if fromPath != "" { |
Russ Cox | 06713c2 | 2021-02-18 15:04:08 -0500 | [diff] [blame] | 880 | if cycle := imp.findPath(importPath, fromPath); cycle != nil { |
| 881 | // Cycle-forming import: we must not check it |
| 882 | // since it would deadlock. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 883 | if trace { |
Dominik Honnef | 361bcb2 | 2016-03-26 06:16:12 +0100 | [diff] [blame] | 884 | fmt.Fprintf(os.Stderr, "import cycle: %q\n", cycle) |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 885 | } |
| 886 | continue |
| 887 | } |
| 888 | } |
Russ Cox | 06713c2 | 2021-02-18 15:04:08 -0500 | [diff] [blame] | 889 | bp, err := imp.findPackage(importPath, fromDir, mode) |
| 890 | if err != nil { |
| 891 | errors = append(errors, importError{ |
| 892 | path: importPath, |
| 893 | err: err, |
| 894 | }) |
| 895 | continue |
| 896 | } |
| 897 | pending = append(pending, imp.startLoad(bp)) |
| 898 | } |
| 899 | |
| 900 | for _, ii := range pending { |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 901 | ii.awaitCompletion() |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 902 | infos = append(infos, ii.info) |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 903 | } |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 904 | |
| 905 | return infos, errors |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 906 | } |
| 907 | |
| 908 | // findPath returns an arbitrary path from 'from' to 'to' in the import |
| 909 | // graph, or nil if there was none. |
| 910 | func (imp *importer) findPath(from, to string) []string { |
| 911 | imp.graphMu.Lock() |
| 912 | defer imp.graphMu.Unlock() |
| 913 | |
| 914 | seen := make(map[string]bool) |
| 915 | var search func(stack []string, importPath string) []string |
| 916 | search = func(stack []string, importPath string) []string { |
| 917 | if !seen[importPath] { |
| 918 | seen[importPath] = true |
| 919 | stack = append(stack, importPath) |
| 920 | if importPath == to { |
| 921 | return stack |
| 922 | } |
| 923 | for x := range imp.graph[importPath] { |
| 924 | if p := search(stack, x); p != nil { |
| 925 | return p |
| 926 | } |
| 927 | } |
| 928 | } |
| 929 | return nil |
| 930 | } |
| 931 | return search(make([]string, 0, 20), from) |
| 932 | } |
| 933 | |
| 934 | // startLoad initiates the loading, parsing and type-checking of the |
| 935 | // specified package and its dependencies, if it has not already begun. |
| 936 | // |
| 937 | // It returns an importInfo, not necessarily in a completed state. The |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 938 | // caller must call awaitCompletion() before accessing its info field. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 939 | // |
| 940 | // startLoad is concurrency-safe and idempotent. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 941 | func (imp *importer) startLoad(bp *build.Package) *importInfo { |
| 942 | path := bp.ImportPath |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 943 | imp.importedMu.Lock() |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 944 | ii, ok := imp.imported[path] |
| 945 | if !ok { |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 946 | ii = &importInfo{path: path, complete: make(chan struct{})} |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 947 | imp.imported[path] = ii |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 948 | go func() { |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 949 | info := imp.load(bp) |
| 950 | ii.Complete(info) |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 951 | }() |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 952 | } |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 953 | imp.importedMu.Unlock() |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 954 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 955 | return ii |
| 956 | } |
| 957 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 958 | // load implements package loading by parsing Go source files |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 959 | // located by go/build. |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 960 | func (imp *importer) load(bp *build.Package) *PackageInfo { |
| 961 | info := imp.newPackageInfo(bp.ImportPath, bp.Dir) |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 962 | info.Importable = true |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 963 | files, errs := imp.conf.parsePackageFiles(bp, 'g') |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 964 | for _, err := range errs { |
| 965 | info.appendError(err) |
| 966 | } |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 967 | |
| 968 | imp.addFiles(info, files, true) |
| 969 | |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 970 | imp.progMu.Lock() |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 971 | imp.prog.importMap[bp.ImportPath] = info.Pkg |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 972 | imp.progMu.Unlock() |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 973 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 974 | return info |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 975 | } |
| 976 | |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 977 | // addFiles adds and type-checks the specified files to info, loading |
| 978 | // their dependencies if needed. The order of files determines the |
| 979 | // package initialization order. It may be called multiple times on the |
| 980 | // same package. Errors are appended to the info.Errors field. |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 981 | // |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 982 | // cycleCheck determines whether the imports within files create |
| 983 | // dependency edges that should be checked for potential cycles. |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 984 | func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) { |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 985 | // Ensure the dependencies are loaded, in parallel. |
| 986 | var fromPath string |
| 987 | if cycleCheck { |
| 988 | fromPath = info.Pkg.Path() |
| 989 | } |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 990 | // TODO(adonovan): opt: make the caller do scanImports. |
| 991 | // Callers with a build.Package can skip it. |
Alan Donovan | 1071209 | 2016-01-08 13:18:57 -0500 | [diff] [blame] | 992 | imp.importAll(fromPath, info.dir, scanImports(files), 0) |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 993 | |
| 994 | if trace { |
| 995 | fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n", |
| 996 | time.Since(imp.start), info.Pkg.Path(), len(files)) |
| 997 | } |
| 998 | |
Alan Donovan | 63c6481 | 2017-06-19 13:33:23 -0400 | [diff] [blame] | 999 | // Don't call checker.Files on Unsafe, even with zero files, |
| 1000 | // because it would mutate the package, which is a global. |
| 1001 | if info.Pkg == types.Unsafe { |
| 1002 | if len(files) > 0 { |
| 1003 | panic(`"unsafe" package contains unexpected files`) |
| 1004 | } |
| 1005 | } else { |
| 1006 | // Ignore the returned (first) error since we |
| 1007 | // already collect them all in the PackageInfo. |
| 1008 | info.checker.Files(files) |
| 1009 | info.Files = append(info.Files, files...) |
Alan Donovan | 7a49e42 | 2017-03-02 16:31:04 -0500 | [diff] [blame] | 1010 | } |
| 1011 | |
Alan Donovan | 2336c53 | 2016-02-21 18:45:02 -0500 | [diff] [blame] | 1012 | if imp.conf.AfterTypeCheck != nil { |
| 1013 | imp.conf.AfterTypeCheck(info, files) |
| 1014 | } |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 1015 | |
| 1016 | if trace { |
| 1017 | fmt.Fprintf(os.Stderr, "%s: stop %q\n", |
| 1018 | time.Since(imp.start), info.Pkg.Path()) |
| 1019 | } |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 1020 | } |
| 1021 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 1022 | func (imp *importer) newPackageInfo(path, dir string) *PackageInfo { |
Alan Donovan | 7a49e42 | 2017-03-02 16:31:04 -0500 | [diff] [blame] | 1023 | var pkg *types.Package |
| 1024 | if path == "unsafe" { |
| 1025 | pkg = types.Unsafe |
| 1026 | } else { |
| 1027 | pkg = types.NewPackage(path, "") |
| 1028 | } |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 1029 | info := &PackageInfo{ |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 1030 | Pkg: pkg, |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 1031 | Info: types.Info{ |
Alan Donovan | 64ec206 | 2014-01-28 16:46:24 -0500 | [diff] [blame] | 1032 | Types: make(map[ast.Expr]types.TypeAndValue), |
Alan Donovan | ced954c | 2014-02-27 13:21:59 -0500 | [diff] [blame] | 1033 | Defs: make(map[*ast.Ident]types.Object), |
| 1034 | Uses: make(map[*ast.Ident]types.Object), |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 1035 | Implicits: make(map[ast.Node]types.Object), |
| 1036 | Scopes: make(map[ast.Node]*types.Scope), |
| 1037 | Selections: make(map[*ast.SelectorExpr]*types.Selection), |
| 1038 | }, |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 1039 | errorFunc: imp.conf.TypeChecker.Error, |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 1040 | dir: dir, |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 1041 | } |
Tim King | 33002ea | 2022-02-14 21:42:10 +0000 | [diff] [blame] | 1042 | typeparams.InitInstanceInfo(&info.Info) |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 1043 | |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 1044 | // Copy the types.Config so we can vary it across PackageInfos. |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 1045 | tc := imp.conf.TypeChecker |
| 1046 | tc.IgnoreFuncBodies = false |
| 1047 | if f := imp.conf.TypeCheckFuncBodies; f != nil { |
| 1048 | tc.IgnoreFuncBodies = !f(path) |
| 1049 | } |
Alan Donovan | 542ffc7 | 2015-12-29 13:06:30 -0500 | [diff] [blame] | 1050 | tc.Importer = closure{imp, info} |
Alan Donovan | 3bbc630 | 2014-08-07 12:50:15 -0400 | [diff] [blame] | 1051 | tc.Error = info.appendError // appendError wraps the user's Error function |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 1052 | |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 1053 | info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 1054 | imp.progMu.Lock() |
Alan Donovan | cd987a3 | 2014-03-11 15:41:57 -0400 | [diff] [blame] | 1055 | imp.prog.AllPackages[pkg] = info |
Alan Donovan | 9c57c19 | 2015-04-14 16:56:02 -0400 | [diff] [blame] | 1056 | imp.progMu.Unlock() |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 1057 | return info |
Alan Donovan | fb3d862 | 2013-12-09 09:36:29 -0500 | [diff] [blame] | 1058 | } |
Alan Donovan | 542ffc7 | 2015-12-29 13:06:30 -0500 | [diff] [blame] | 1059 | |
| 1060 | type closure struct { |
| 1061 | imp *importer |
| 1062 | info *PackageInfo |
| 1063 | } |
| 1064 | |
| 1065 | func (c closure) Import(to string) (*types.Package, error) { return c.imp.doImport(c.info, to) } |