github.com/google/capslock@v0.2.3-0.20240517042941-dac19fc347c0/analyzer/load.go (about) 1 // Copyright 2023 Google LLC 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd 6 7 package analyzer 8 9 import ( 10 "go/types" 11 "os" 12 "path" 13 "sort" 14 "sync" 15 16 cpb "github.com/google/capslock/proto" 17 "golang.org/x/tools/go/packages" 18 "google.golang.org/protobuf/proto" 19 ) 20 21 var ( 22 standardLibraryPackagesOnce sync.Once 23 standardLibraryPackagesMap map[string]struct{} 24 ) 25 26 // LoadConfig specifies the build tags, GOOS value, and GOARCH value to use 27 // when loading packages. These will be used to determine when a file's build 28 // constraint is satisfied. See 29 // https://pkg.go.dev/cmd/go#hdr-Build_constraints for more information. 30 type LoadConfig struct { 31 BuildTags string 32 GOOS string 33 GOARCH string 34 } 35 36 // PackagesLoadModeNeeded is a packages.LoadMode that has all the bits set for 37 // the information that this package uses to perform its analysis. Users 38 // should load packages for analysis using this LoadMode (or a superset.) 39 const PackagesLoadModeNeeded packages.LoadMode = packages.NeedName | 40 packages.NeedFiles | 41 packages.NeedCompiledGoFiles | 42 packages.NeedImports | 43 packages.NeedDeps | 44 packages.NeedTypes | 45 packages.NeedSyntax | 46 packages.NeedTypesInfo | 47 packages.NeedTypesSizes | 48 packages.NeedModule 49 50 // GetQueriedPackages builds a set of *types.Package matching the input query so that 51 // we can limit the output to only functions in these packages, not 52 // their dependencies too. 53 func GetQueriedPackages(pkgs []*packages.Package) map[*types.Package]struct{} { 54 queriedPackages := map[*types.Package]struct{}{} 55 for _, p := range pkgs { 56 queriedPackages[p.Types] = struct{}{} 57 } 58 return queriedPackages 59 } 60 61 func LoadPackages(packageNames []string, lcfg LoadConfig) ([]*packages.Package, error) { 62 cfg := &packages.Config{Mode: PackagesLoadModeNeeded} 63 if lcfg.BuildTags != "" { 64 cfg.BuildFlags = []string{"-tags=" + lcfg.BuildTags} 65 } 66 if lcfg.GOOS != "" || lcfg.GOARCH != "" { 67 env := append([]string(nil), os.Environ()...) // go1.21 has slices.Clone for this 68 if lcfg.GOOS != "" { 69 env = append(env, "GOOS="+lcfg.GOOS) 70 } 71 if lcfg.GOARCH != "" { 72 env = append(env, "GOARCH="+lcfg.GOARCH) 73 } 74 cfg.Env = env 75 } 76 return packages.Load(cfg, packageNames...) 77 } 78 79 func standardLibraryPackages() map[string]struct{} { 80 standardLibraryPackagesOnce.Do(func() { 81 pkgs, err := packages.Load(nil, "std") 82 if err != nil { 83 panic(err.Error()) 84 } 85 standardLibraryPackagesMap = make(map[string]struct{}) 86 for _, p := range pkgs { 87 standardLibraryPackagesMap[p.PkgPath] = struct{}{} 88 } 89 }) 90 return standardLibraryPackagesMap 91 } 92 93 func collectModuleInfo(pkgs []*packages.Package) []*cpb.ModuleInfo { 94 pathToModule := make(map[string]*cpb.ModuleInfo) 95 forEachPackageIncludingDependencies(pkgs, func(pkg *packages.Package) { 96 m := pkg.Module 97 if m == nil || m.Path == "" || m.Version == "" { 98 // No module information. 99 return 100 } 101 if _, ok := pathToModule[m.Path]; ok { 102 // We've seen this module. 103 return 104 } 105 pm := new(cpb.ModuleInfo) 106 pm.Path = proto.String(m.Path) 107 pm.Version = proto.String(m.Version) 108 pathToModule[m.Path] = pm 109 }) 110 // Sort by path. 111 var modulePaths []string 112 for path := range pathToModule { 113 modulePaths = append(modulePaths, path) 114 } 115 sort.Strings(modulePaths) 116 // Construct the output slice. 117 var modules []*cpb.ModuleInfo 118 for _, path := range modulePaths { 119 modules = append(modules, pathToModule[path]) 120 } 121 return modules 122 } 123 124 func collectPackageInfo(pkgs []*packages.Package) []*cpb.PackageInfo { 125 var out []*cpb.PackageInfo 126 std := standardLibraryPackages() 127 forEachPackageIncludingDependencies(pkgs, func(pkg *packages.Package) { 128 if _, ok := std[pkg.PkgPath]; ok { 129 // Skip this package since it is part of the Go standard library. 130 return 131 } 132 pi := new(cpb.PackageInfo) 133 pi.Path = proto.String(pkg.PkgPath) 134 for _, i := range pkg.IgnoredFiles { 135 pi.IgnoredFiles = append(pi.IgnoredFiles, path.Base(i)) 136 } 137 out = append(out, pi) 138 }) 139 sort.Slice(out, func(i, j int) bool { 140 return out[i].GetPath() < out[j].GetPath() 141 }) 142 return out 143 }