git.sr.ht/~sircmpwn/gqlgen@v0.0.0-20200522192042-c84d29a1c940/internal/code/packages.go (about) 1 package code 2 3 import ( 4 "bytes" 5 "path/filepath" 6 7 "github.com/pkg/errors" 8 "golang.org/x/tools/go/packages" 9 ) 10 11 var mode = packages.NeedName | 12 packages.NeedFiles | 13 packages.NeedImports | 14 packages.NeedTypes | 15 packages.NeedSyntax | 16 packages.NeedTypesInfo 17 18 // Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages 19 // that can be invalidated as writes are made and packages are known to change. 20 type Packages struct { 21 packages map[string]*packages.Package 22 importToName map[string]string 23 loadErrors []error 24 25 numLoadCalls int // stupid test steam. ignore. 26 numNameCalls int // stupid test steam. ignore. 27 } 28 29 // LoadAll will call packages.Load and return the package data for the given packages, 30 // but if the package already have been loaded it will return cached values instead. 31 func (p *Packages) LoadAll(importPaths ...string) []*packages.Package { 32 if p.packages == nil { 33 p.packages = map[string]*packages.Package{} 34 } 35 36 missing := make([]string, 0, len(importPaths)) 37 for _, path := range importPaths { 38 if _, ok := p.packages[path]; ok { 39 continue 40 } 41 missing = append(missing, path) 42 } 43 44 if len(missing) > 0 { 45 p.numLoadCalls++ 46 pkgs, err := packages.Load(&packages.Config{Mode: mode}, missing...) 47 if err != nil { 48 p.loadErrors = append(p.loadErrors, err) 49 } 50 51 for _, pkg := range pkgs { 52 p.addToCache(pkg) 53 } 54 } 55 56 res := make([]*packages.Package, 0, len(importPaths)) 57 for _, path := range importPaths { 58 res = append(res, p.packages[NormalizeVendor(path)]) 59 } 60 return res 61 } 62 63 func (p *Packages) addToCache(pkg *packages.Package) { 64 imp := NormalizeVendor(pkg.PkgPath) 65 p.packages[imp] = pkg 66 for _, imp := range pkg.Imports { 67 if _, found := p.packages[NormalizeVendor(imp.PkgPath)]; !found { 68 p.addToCache(imp) 69 } 70 } 71 } 72 73 // Load works the same as LoadAll, except a single package at a time. 74 func (p *Packages) Load(importPath string) *packages.Package { 75 pkgs := p.LoadAll(importPath) 76 if len(pkgs) == 0 { 77 return nil 78 } 79 return pkgs[0] 80 } 81 82 // LoadWithTypes tries a standard load, which may not have enough type info (TypesInfo== nil) available if the imported package is a 83 // second order dependency. Fortunately this doesnt happen very often, so we can just issue a load when we detect it. 84 func (p *Packages) LoadWithTypes(importPath string) *packages.Package { 85 pkg := p.Load(importPath) 86 if pkg == nil || pkg.TypesInfo == nil { 87 p.numLoadCalls++ 88 pkgs, err := packages.Load(&packages.Config{Mode: mode}, importPath) 89 if err != nil { 90 p.loadErrors = append(p.loadErrors, err) 91 return nil 92 } 93 p.addToCache(pkgs[0]) 94 pkg = pkgs[0] 95 } 96 return pkg 97 } 98 99 // NameForPackage looks up the package name from the package stanza in the go files at the given import path. 100 func (p *Packages) NameForPackage(importPath string) string { 101 if importPath == "" { 102 panic(errors.New("import path can not be empty")) 103 } 104 if p.importToName == nil { 105 p.importToName = map[string]string{} 106 } 107 108 importPath = NormalizeVendor(importPath) 109 110 // if its in the name cache use it 111 if name := p.importToName[importPath]; name != "" { 112 return name 113 } 114 115 // otherwise we might have already loaded the full package data for it cached 116 pkg := p.packages[importPath] 117 118 if pkg == nil { 119 // otherwise do a name only lookup for it but dont put it in the package cache. 120 p.numNameCalls++ 121 pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName}, importPath) 122 if err != nil { 123 p.loadErrors = append(p.loadErrors, err) 124 } else { 125 pkg = pkgs[0] 126 } 127 } 128 129 if pkg == nil || pkg.Name == "" { 130 return SanitizePackageName(filepath.Base(importPath)) 131 } 132 133 p.importToName[importPath] = pkg.Name 134 135 return pkg.Name 136 } 137 138 // Evict removes a given package import path from the cache, along with any packages that depend on it. Further calls 139 // to Load will fetch it from disk. 140 func (p *Packages) Evict(importPath string) { 141 delete(p.packages, importPath) 142 143 for _, pkg := range p.packages { 144 for _, imported := range pkg.Imports { 145 if imported.PkgPath == importPath { 146 p.Evict(pkg.PkgPath) 147 } 148 } 149 } 150 } 151 152 // Errors returns any errors that were returned by Load, either from the call itself or any of the loaded packages. 153 func (p *Packages) Errors() PkgErrors { 154 var res []error //nolint:prealloc 155 res = append(res, p.loadErrors...) 156 for _, pkg := range p.packages { 157 for _, err := range pkg.Errors { 158 res = append(res, err) 159 } 160 } 161 return res 162 } 163 164 type PkgErrors []error 165 166 func (p PkgErrors) Error() string { 167 var b bytes.Buffer 168 b.WriteString("packages.Load: ") 169 for _, e := range p { 170 b.WriteString(e.Error() + "\n") 171 } 172 return b.String() 173 }