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  }