1package loader 2 3import ( 4 "errors" 5 "fmt" 6 "go/ast" 7 "go/parser" 8 "go/scanner" 9 "go/token" 10 "go/types" 11 "log" 12 "os" 13 14 "golang.org/x/tools/go/gcexportdata" 15 "golang.org/x/tools/go/packages" 16) 17 18// Graph resolves patterns and returns packages with all the 19// information required to later load type information, and optionally 20// syntax trees. 21// 22// The provided config can set any setting with the exception of Mode. 23func Graph(cfg packages.Config, patterns ...string) ([]*packages.Package, error) { 24 cfg.Mode = packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedTypesSizes 25 pkgs, err := packages.Load(&cfg, patterns...) 26 if err != nil { 27 return nil, err 28 } 29 fset := token.NewFileSet() 30 packages.Visit(pkgs, nil, func(pkg *packages.Package) { 31 pkg.Fset = fset 32 }) 33 34 n := 0 35 for _, pkg := range pkgs { 36 if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" { 37 // If a package consists only of test files, then 38 // go/packages incorrectly(?) returns an empty package for 39 // the non-test variant. Get rid of those packages. See 40 // #646. 41 // 42 // Do not, however, skip packages that have errors. Those, 43 // too, may have no files, but we want to print the 44 // errors. 45 continue 46 } 47 pkgs[n] = pkg 48 n++ 49 } 50 return pkgs[:n], nil 51} 52 53// LoadFromExport loads a package from export data. All of its 54// dependencies must have been loaded already. 55func LoadFromExport(pkg *packages.Package) error { 56 pkg.IllTyped = true 57 for path, pkg := range pkg.Imports { 58 if pkg.Types == nil { 59 return fmt.Errorf("dependency %q hasn't been loaded yet", path) 60 } 61 } 62 if pkg.ExportFile == "" { 63 return fmt.Errorf("no export data for %q", pkg.ID) 64 } 65 f, err := os.Open(pkg.ExportFile) 66 if err != nil { 67 return err 68 } 69 defer f.Close() 70 71 r, err := gcexportdata.NewReader(f) 72 if err != nil { 73 return err 74 } 75 76 view := make(map[string]*types.Package) // view seen by gcexportdata 77 seen := make(map[*packages.Package]bool) // all visited packages 78 var visit func(pkgs map[string]*packages.Package) 79 visit = func(pkgs map[string]*packages.Package) { 80 for _, pkg := range pkgs { 81 if !seen[pkg] { 82 seen[pkg] = true 83 view[pkg.PkgPath] = pkg.Types 84 visit(pkg.Imports) 85 } 86 } 87 } 88 visit(pkg.Imports) 89 tpkg, err := gcexportdata.Read(r, pkg.Fset, view, pkg.PkgPath) 90 if err != nil { 91 return err 92 } 93 pkg.Types = tpkg 94 pkg.IllTyped = false 95 return nil 96} 97 98// LoadFromSource loads a package from source. All of its dependencies 99// must have been loaded already. 100func LoadFromSource(pkg *packages.Package) error { 101 pkg.IllTyped = true 102 pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name) 103 104 // OPT(dh): many packages have few files, much fewer than there 105 // are CPU cores. Additionally, parsing each individual file is 106 // very fast. A naive parallel implementation of this loop won't 107 // be faster, and tends to be slower due to extra scheduling, 108 // bookkeeping and potentially false sharing of cache lines. 109 pkg.Syntax = make([]*ast.File, len(pkg.CompiledGoFiles)) 110 for i, file := range pkg.CompiledGoFiles { 111 f, err := parser.ParseFile(pkg.Fset, file, nil, parser.ParseComments) 112 if err != nil { 113 pkg.Errors = append(pkg.Errors, convertError(err)...) 114 return err 115 } 116 pkg.Syntax[i] = f 117 } 118 pkg.TypesInfo = &types.Info{ 119 Types: make(map[ast.Expr]types.TypeAndValue), 120 Defs: make(map[*ast.Ident]types.Object), 121 Uses: make(map[*ast.Ident]types.Object), 122 Implicits: make(map[ast.Node]types.Object), 123 Scopes: make(map[ast.Node]*types.Scope), 124 Selections: make(map[*ast.SelectorExpr]*types.Selection), 125 } 126 127 importer := func(path string) (*types.Package, error) { 128 if path == "unsafe" { 129 return types.Unsafe, nil 130 } 131 if path == "C" { 132 // go/packages doesn't tell us that cgo preprocessing 133 // failed. When we subsequently try to parse the package, 134 // we'll encounter the raw C import. 135 return nil, errors.New("cgo preprocessing failed") 136 } 137 imp := pkg.Imports[path] 138 if imp == nil { 139 return nil, nil 140 } 141 if len(imp.Errors) > 0 { 142 return nil, imp.Errors[0] 143 } 144 return imp.Types, nil 145 } 146 tc := &types.Config{ 147 Importer: importerFunc(importer), 148 Error: func(err error) { 149 pkg.Errors = append(pkg.Errors, convertError(err)...) 150 }, 151 } 152 err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax) 153 if err != nil { 154 return err 155 } 156 pkg.IllTyped = false 157 return nil 158} 159 160func convertError(err error) []packages.Error { 161 var errs []packages.Error 162 // taken from go/packages 163 switch err := err.(type) { 164 case packages.Error: 165 // from driver 166 errs = append(errs, err) 167 168 case *os.PathError: 169 // from parser 170 errs = append(errs, packages.Error{ 171 Pos: err.Path + ":1", 172 Msg: err.Err.Error(), 173 Kind: packages.ParseError, 174 }) 175 176 case scanner.ErrorList: 177 // from parser 178 for _, err := range err { 179 errs = append(errs, packages.Error{ 180 Pos: err.Pos.String(), 181 Msg: err.Msg, 182 Kind: packages.ParseError, 183 }) 184 } 185 186 case types.Error: 187 // from type checker 188 errs = append(errs, packages.Error{ 189 Pos: err.Fset.Position(err.Pos).String(), 190 Msg: err.Msg, 191 Kind: packages.TypeError, 192 }) 193 194 default: 195 // unexpected impoverished error from parser? 196 errs = append(errs, packages.Error{ 197 Pos: "-", 198 Msg: err.Error(), 199 Kind: packages.UnknownError, 200 }) 201 202 // If you see this error message, please file a bug. 203 log.Printf("internal error: error %q (%T) without position", err, err) 204 } 205 return errs 206} 207 208type importerFunc func(path string) (*types.Package, error) 209 210func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } 211