| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- package loader
- import (
- "fmt"
- "go/ast"
- "go/parser"
- "go/scanner"
- "go/token"
- "go/types"
- "log"
- "os"
- "sync"
- "golang.org/x/tools/go/gcexportdata"
- "golang.org/x/tools/go/packages"
- )
- type Loader struct {
- exportMu sync.RWMutex
- }
- // 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 (ld *Loader) 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
- })
- return pkgs, nil
- }
- // LoadFromExport loads a package from export data. All of its
- // dependencies must have been loaded already.
- func (ld *Loader) LoadFromExport(pkg *packages.Package) error {
- ld.exportMu.Lock()
- defer ld.exportMu.Unlock()
- 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 (ld *Loader) LoadFromSource(pkg *packages.Package) error {
- ld.exportMu.RLock()
- defer ld.exportMu.RUnlock()
- 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
- }
- 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) }
|