package loader import ( "errors" "fmt" "go/ast" "go/parser" "go/scanner" "go/token" "go/types" "log" "os" "golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/go/packages" ) // Graph resolves patterns and returns packages with all the // information required to later load type information, and optionally // syntax trees. // // The provided config can set any setting with the exception of Mode. func Graph(cfg packages.Config, patterns ...string) ([]*packages.Package, error) { cfg.Mode = packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedTypesSizes pkgs, err := packages.Load(&cfg, patterns...) if err != nil { return nil, err } fset := token.NewFileSet() packages.Visit(pkgs, nil, func(pkg *packages.Package) { pkg.Fset = fset }) n := 0 for _, pkg := range pkgs { if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" { // If a package consists only of test files, then // go/packages incorrectly(?) returns an empty package for // the non-test variant. Get rid of those packages. See // #646. // // Do not, however, skip packages that have errors. Those, // too, may have no files, but we want to print the // errors. continue } pkgs[n] = pkg n++ } return pkgs[:n], nil } // LoadFromExport loads a package from export data. All of its // dependencies must have been loaded already. func LoadFromExport(pkg *packages.Package) error { pkg.IllTyped = true for path, pkg := range pkg.Imports { if pkg.Types == nil { return fmt.Errorf("dependency %q hasn't been loaded yet", path) } } if pkg.ExportFile == "" { return fmt.Errorf("no export data for %q", pkg.ID) } f, err := os.Open(pkg.ExportFile) if err != nil { return err } defer f.Close() r, err := gcexportdata.NewReader(f) if err != nil { return err } view := make(map[string]*types.Package) // view seen by gcexportdata seen := make(map[*packages.Package]bool) // all visited packages var visit func(pkgs map[string]*packages.Package) visit = func(pkgs map[string]*packages.Package) { for _, pkg := range pkgs { if !seen[pkg] { seen[pkg] = true view[pkg.PkgPath] = pkg.Types visit(pkg.Imports) } } } visit(pkg.Imports) tpkg, err := gcexportdata.Read(r, pkg.Fset, view, pkg.PkgPath) if err != nil { return err } pkg.Types = tpkg pkg.IllTyped = false return nil } // LoadFromSource loads a package from source. All of its dependencies // must have been loaded already. func LoadFromSource(pkg *packages.Package) error { pkg.IllTyped = true pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name) // OPT(dh): many packages have few files, much fewer than there // are CPU cores. Additionally, parsing each individual file is // very fast. A naive parallel implementation of this loop won't // be faster, and tends to be slower due to extra scheduling, // bookkeeping and potentially false sharing of cache lines. pkg.Syntax = make([]*ast.File, len(pkg.CompiledGoFiles)) for i, file := range pkg.CompiledGoFiles { f, err := parser.ParseFile(pkg.Fset, file, nil, parser.ParseComments) if err != nil { pkg.Errors = append(pkg.Errors, convertError(err)...) return err } pkg.Syntax[i] = f } pkg.TypesInfo = &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), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), } importer := func(path string) (*types.Package, error) { if path == "unsafe" { return types.Unsafe, nil } if path == "C" { // go/packages doesn't tell us that cgo preprocessing // failed. When we subsequently try to parse the package, // we'll encounter the raw C import. return nil, errors.New("cgo preprocessing failed") } imp := pkg.Imports[path] if imp == nil { return nil, nil } if len(imp.Errors) > 0 { return nil, imp.Errors[0] } return imp.Types, nil } tc := &types.Config{ Importer: importerFunc(importer), Error: func(err error) { pkg.Errors = append(pkg.Errors, convertError(err)...) }, } err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax) if err != nil { return err } pkg.IllTyped = false return nil } func convertError(err error) []packages.Error { var errs []packages.Error // taken from go/packages switch err := err.(type) { case packages.Error: // from driver errs = append(errs, err) case *os.PathError: // from parser errs = append(errs, packages.Error{ Pos: err.Path + ":1", Msg: err.Err.Error(), Kind: packages.ParseError, }) case scanner.ErrorList: // from parser for _, err := range err { errs = append(errs, packages.Error{ Pos: err.Pos.String(), Msg: err.Msg, Kind: packages.ParseError, }) } case types.Error: // from type checker errs = append(errs, packages.Error{ Pos: err.Fset.Position(err.Pos).String(), Msg: err.Msg, Kind: packages.TypeError, }) default: // unexpected impoverished error from parser? errs = append(errs, packages.Error{ Pos: "-", Msg: err.Error(), Kind: packages.UnknownError, }) // If you see this error message, please file a bug. log.Printf("internal error: error %q (%T) without position", err, err) } return errs } type importerFunc func(path string) (*types.Package, error) func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }