14 "honnef.co/go/tools/config"
15 "honnef.co/go/tools/internal/cache"
16 "honnef.co/go/tools/internal/go/gcimporter"
18 "golang.org/x/tools/go/packages"
21 const MaxFileSize = 50 * 1024 * 1024 // 50 MB
23 var errMaxFileSize = errors.New("file exceeds max file size")
25 type PackageSpec struct {
29 // Errors that occured while building the import graph. These will
30 // primarily be parse errors or failure to resolve imports, but
31 // may also be other errors.
32 Errors []packages.Error
34 CompiledGoFiles []string
37 Imports map[string]*PackageSpec
38 TypesSizes types.Sizes
44 func (spec *PackageSpec) String() string {
51 // Errors that occured while loading the package. These will
52 // primarily be parse or type errors, but may also be lower-level
53 // failures such as file-system ones.
54 Errors []packages.Error
61 // Graph resolves patterns and returns packages with all the
62 // information required to later load type information, and optionally
65 // The provided config can set any setting with the exception of Mode.
66 func Graph(cfg *packages.Config, patterns ...string) ([]*PackageSpec, error) {
67 var dcfg packages.Config
71 dcfg.Mode = packages.NeedName |
72 packages.NeedImports |
74 packages.NeedExportsFile |
76 packages.NeedCompiledGoFiles |
77 packages.NeedTypesSizes
78 pkgs, err := packages.Load(&dcfg, patterns...)
83 m := map[*packages.Package]*PackageSpec{}
84 packages.Visit(pkgs, nil, func(pkg *packages.Package) {
91 CompiledGoFiles: pkg.CompiledGoFiles,
92 OtherFiles: pkg.OtherFiles,
93 ExportFile: pkg.ExportFile,
94 Imports: map[string]*PackageSpec{},
95 TypesSizes: pkg.TypesSizes,
97 for path, imp := range pkg.Imports {
98 spec.Imports[path] = m[imp]
100 if cdir := config.Dir(pkg.GoFiles); cdir != "" {
101 cfg, err := config.Load(cdir)
103 spec.Errors = append(spec.Errors, convertError(err)...)
107 spec.Config = config.DefaultConfig
109 spec.Hash, err = computeHash(spec)
111 spec.Errors = append(spec.Errors, convertError(err)...)
115 out := make([]*PackageSpec, 0, len(pkgs))
116 for _, pkg := range pkgs {
117 if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" {
118 // If a package consists only of test files, then
119 // go/packages incorrectly(?) returns an empty package for
120 // the non-test variant. Get rid of those packages. See
123 // Do not, however, skip packages that have errors. Those,
124 // too, may have no files, but we want to print the
128 out = append(out, m[pkg])
134 type program struct {
136 packages map[string]*types.Package
141 Export map[*PackageSpec]time.Duration
144 // Load loads the package described in spec. Imports will be loaded
145 // from export data, while the package itself will be loaded from
148 // An error will only be returned for system failures, such as failure
149 // to read export data from disk. Syntax and type errors, among
150 // others, will only populate the returned package's Errors field.
151 func Load(spec *PackageSpec) (*Package, Stats, error) {
153 fset: token.NewFileSet(),
154 packages: map[string]*types.Package{},
158 Export: map[*PackageSpec]time.Duration{},
161 for _, imp := range spec.Imports {
162 if imp.PkgPath == "unsafe" {
167 _, b, err = prog.loadFromExport(imp, b)
168 stats.Export[imp] = time.Since(t)
170 return nil, stats, err
174 pkg, err := prog.loadFromSource(spec)
175 if err == errMaxFileSize {
176 pkg, _, err = prog.loadFromExport(spec, b)
178 stats.Source = time.Since(t)
179 return pkg, stats, err
182 // loadFromExport loads a package from export data.
183 func (prog *program) loadFromExport(spec *PackageSpec, b []byte) (*Package, []byte, error) {
184 // log.Printf("Loading package %s from export", spec)
185 if spec.ExportFile == "" {
186 return nil, b, fmt.Errorf("no export data for %q", spec.ID)
188 f, err := os.Open(spec.ExportFile)
194 b, err = gcimporter.GetExportData(f, b)
199 _, tpkg, err := gcimporter.IImportData(prog.fset, prog.packages, b[1:], spec.PkgPath)
208 // runtime.SetFinalizer(pkg, func(pkg *Package) {
209 // log.Println("Unloading package", pkg.PkgPath)
214 // loadFromSource loads a package from source. All of its dependencies
215 // must have been loaded already.
216 func (prog *program) loadFromSource(spec *PackageSpec) (*Package, error) {
217 if len(spec.Errors) > 0 {
218 panic("LoadFromSource called on package with errors")
223 Types: types.NewPackage(spec.PkgPath, spec.Name),
224 Syntax: make([]*ast.File, len(spec.CompiledGoFiles)),
226 TypesInfo: &types.Info{
227 Types: make(map[ast.Expr]types.TypeAndValue),
228 Defs: make(map[*ast.Ident]types.Object),
229 Uses: make(map[*ast.Ident]types.Object),
230 Implicits: make(map[ast.Node]types.Object),
231 Scopes: make(map[ast.Node]*types.Scope),
232 Selections: make(map[*ast.SelectorExpr]*types.Selection),
235 // runtime.SetFinalizer(pkg, func(pkg *Package) {
236 // log.Println("Unloading package", pkg.PkgPath)
239 // OPT(dh): many packages have few files, much fewer than there
240 // are CPU cores. Additionally, parsing each individual file is
241 // very fast. A naive parallel implementation of this loop won't
242 // be faster, and tends to be slower due to extra scheduling,
243 // bookkeeping and potentially false sharing of cache lines.
244 for i, file := range spec.CompiledGoFiles {
245 f, err := os.Open(file)
253 if fi.Size() >= MaxFileSize {
254 return nil, errMaxFileSize
256 af, err := parser.ParseFile(prog.fset, file, f, parser.ParseComments)
259 pkg.Errors = append(pkg.Errors, convertError(err)...)
264 importer := func(path string) (*types.Package, error) {
265 if path == "unsafe" {
266 return types.Unsafe, nil
269 // go/packages doesn't tell us that cgo preprocessing
270 // failed. When we subsequently try to parse the package,
271 // we'll encounter the raw C import.
272 return nil, errors.New("cgo preprocessing failed")
274 ispecpkg := spec.Imports[path]
276 return nil, fmt.Errorf("trying to import %q in the context of %q returned nil PackageSpec", path, spec)
278 ipkg := prog.packages[ispecpkg.PkgPath]
280 return nil, fmt.Errorf("trying to import %q (%q) in the context of %q returned nil PackageSpec", ispecpkg.PkgPath, path, spec)
285 Importer: importerFunc(importer),
286 Error: func(err error) {
287 pkg.Errors = append(pkg.Errors, convertError(err)...)
290 types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
294 func convertError(err error) []packages.Error {
295 var errs []packages.Error
296 // taken from go/packages
297 switch err := err.(type) {
300 errs = append(errs, err)
304 errs = append(errs, packages.Error{
305 Pos: err.Path + ":1",
306 Msg: err.Err.Error(),
307 Kind: packages.ParseError,
310 case scanner.ErrorList:
312 for _, err := range err {
313 errs = append(errs, packages.Error{
314 Pos: err.Pos.String(),
316 Kind: packages.ParseError,
322 errs = append(errs, packages.Error{
323 Pos: err.Fset.Position(err.Pos).String(),
325 Kind: packages.TypeError,
329 errs = append(errs, packages.Error{
332 Kind: packages.UnknownError,
338 type importerFunc func(path string) (*types.Package, error)
340 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }