github.com/spread-ai/gqlgen@v0.0.0-20221124102857-a6c8ef538a1d/internal/code/packages.go (about) 1 package code 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "os/exec" 9 "path/filepath" 10 11 "golang.org/x/tools/go/packages" 12 ) 13 14 var mode = packages.NeedName | 15 packages.NeedFiles | 16 packages.NeedImports | 17 packages.NeedTypes | 18 packages.NeedSyntax | 19 packages.NeedTypesInfo | 20 packages.NeedModule | 21 packages.NeedDeps 22 23 // Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages 24 // that can be invalidated as writes are made and packages are known to change. 25 type Packages struct { 26 packages map[string]*packages.Package 27 importToName map[string]string 28 loadErrors []error 29 30 numLoadCalls int // stupid test steam. ignore. 31 numNameCalls int // stupid test steam. ignore. 32 } 33 34 // ReloadAll will call LoadAll after clearing the package cache, so we can reload 35 // packages in the case that the packages have changed 36 func (p *Packages) ReloadAll(importPaths ...string) []*packages.Package { 37 p.packages = nil 38 return p.LoadAll(importPaths...) 39 } 40 41 func (p *Packages) checkModuleLoaded(pkgs []*packages.Package) bool { 42 for i := range pkgs { 43 if pkgs[i] == nil || pkgs[i].Module == nil { 44 return false 45 } 46 } 47 return true 48 } 49 50 // LoadAll will call packages.Load and return the package data for the given packages, 51 // but if the package already have been loaded it will return cached values instead. 52 func (p *Packages) LoadAll(importPaths ...string) []*packages.Package { 53 if p.packages == nil { 54 p.packages = map[string]*packages.Package{} 55 } 56 57 missing := make([]string, 0, len(importPaths)) 58 for _, path := range importPaths { 59 if _, ok := p.packages[path]; ok { 60 continue 61 } 62 missing = append(missing, path) 63 } 64 65 if len(missing) > 0 { 66 p.numLoadCalls++ 67 pkgs, err := packages.Load(&packages.Config{Mode: mode}, missing...) 68 69 // Sometimes packages.Load not loaded the module info. Call it again to reload it. 70 if !p.checkModuleLoaded(pkgs) { 71 fmt.Println("reloading module info") 72 pkgs, err = packages.Load(&packages.Config{Mode: mode}, missing...) 73 } 74 75 if err != nil { 76 p.loadErrors = append(p.loadErrors, err) 77 } 78 79 for _, pkg := range pkgs { 80 p.addToCache(pkg) 81 } 82 } 83 84 res := make([]*packages.Package, 0, len(importPaths)) 85 for _, path := range importPaths { 86 res = append(res, p.packages[NormalizeVendor(path)]) 87 } 88 return res 89 } 90 91 func (p *Packages) addToCache(pkg *packages.Package) { 92 imp := NormalizeVendor(pkg.PkgPath) 93 p.packages[imp] = pkg 94 for _, imp := range pkg.Imports { 95 if _, found := p.packages[NormalizeVendor(imp.PkgPath)]; !found { 96 p.addToCache(imp) 97 } 98 } 99 } 100 101 // Load works the same as LoadAll, except a single package at a time. 102 func (p *Packages) Load(importPath string) *packages.Package { 103 // Quick cache check first to avoid expensive allocations of LoadAll() 104 if p.packages != nil { 105 if pkg, ok := p.packages[importPath]; ok { 106 return pkg 107 } 108 } 109 110 pkgs := p.LoadAll(importPath) 111 if len(pkgs) == 0 { 112 return nil 113 } 114 return pkgs[0] 115 } 116 117 // LoadWithTypes tries a standard load, which may not have enough type info (TypesInfo== nil) available if the imported package is a 118 // second order dependency. Fortunately this doesnt happen very often, so we can just issue a load when we detect it. 119 func (p *Packages) LoadWithTypes(importPath string) *packages.Package { 120 pkg := p.Load(importPath) 121 if pkg == nil || pkg.TypesInfo == nil { 122 p.numLoadCalls++ 123 pkgs, err := packages.Load(&packages.Config{Mode: mode}, importPath) 124 if err != nil { 125 p.loadErrors = append(p.loadErrors, err) 126 return nil 127 } 128 p.addToCache(pkgs[0]) 129 pkg = pkgs[0] 130 } 131 return pkg 132 } 133 134 // NameForPackage looks up the package name from the package stanza in the go files at the given import path. 135 func (p *Packages) NameForPackage(importPath string) string { 136 if importPath == "" { 137 panic(errors.New("import path can not be empty")) 138 } 139 if p.importToName == nil { 140 p.importToName = map[string]string{} 141 } 142 143 importPath = NormalizeVendor(importPath) 144 145 // if its in the name cache use it 146 if name := p.importToName[importPath]; name != "" { 147 return name 148 } 149 150 // otherwise we might have already loaded the full package data for it cached 151 pkg := p.packages[importPath] 152 153 if pkg == nil { 154 // otherwise do a name only lookup for it but dont put it in the package cache. 155 p.numNameCalls++ 156 pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName}, importPath) 157 if err != nil { 158 p.loadErrors = append(p.loadErrors, err) 159 } else { 160 pkg = pkgs[0] 161 } 162 } 163 164 if pkg == nil || pkg.Name == "" { 165 return SanitizePackageName(filepath.Base(importPath)) 166 } 167 168 p.importToName[importPath] = pkg.Name 169 170 return pkg.Name 171 } 172 173 // Evict removes a given package import path from the cache, along with any packages that depend on it. Further calls 174 // to Load will fetch it from disk. 175 func (p *Packages) Evict(importPath string) { 176 delete(p.packages, importPath) 177 178 for _, pkg := range p.packages { 179 for _, imported := range pkg.Imports { 180 if imported.PkgPath == importPath { 181 p.Evict(pkg.PkgPath) 182 } 183 } 184 } 185 } 186 187 func (p *Packages) ModTidy() error { 188 p.packages = nil 189 tidyCmd := exec.Command("go", "mod", "tidy") 190 tidyCmd.Stdout = os.Stdout 191 tidyCmd.Stderr = os.Stdout 192 if err := tidyCmd.Run(); err != nil { 193 return fmt.Errorf("go mod tidy failed: %w", err) 194 } 195 return nil 196 } 197 198 // Errors returns any errors that were returned by Load, either from the call itself or any of the loaded packages. 199 func (p *Packages) Errors() PkgErrors { 200 var res []error //nolint:prealloc 201 res = append(res, p.loadErrors...) 202 for _, pkg := range p.packages { 203 for _, err := range pkg.Errors { 204 res = append(res, err) 205 } 206 } 207 return res 208 } 209 210 func (p *Packages) Count() int { 211 return len(p.packages) 212 } 213 214 type PkgErrors []error 215 216 func (p PkgErrors) Error() string { 217 var b bytes.Buffer 218 b.WriteString("packages.Load: ") 219 for _, e := range p { 220 b.WriteString(e.Error() + "\n") 221 } 222 return b.String() 223 }