github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/internal/dependency/resolver.go (about) 1 package dependency 2 3 import ( 4 "sort" 5 6 "github.com/scylladb/go-set/strset" 7 8 "github.com/anchore/syft/syft/artifact" 9 "github.com/anchore/syft/syft/pkg" 10 "github.com/anchore/syft/syft/pkg/cataloger/generic" 11 ) 12 13 // Specification holds strings that indicate abstract resources that a package provides for other packages and 14 // requires for itself. These strings can represent anything from file paths, package names, or any other concept 15 // that is useful for dependency resolution within that packing ecosystem. 16 type Specification struct { 17 // Provides holds a list of abstract resources that this package provides for other packages. 18 Provides []string 19 20 // Requires holds a list of abstract resources that this package requires from other packages. 21 Requires []string 22 } 23 24 // Specifier is a function that takes a package and extracts a Specification, describing resources 25 // the package provides and needs. 26 type Specifier func(pkg.Package) Specification 27 28 // Processor returns a generic processor that will resolve relationships between packages based on the dependency claims. 29 func Processor(s Specifier) generic.Processor { 30 return func(pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) { 31 // we can't move forward unless all package IDs have been set 32 for idx, p := range pkgs { 33 id := p.ID() 34 if id == "" { 35 p.SetID() 36 pkgs[idx] = p 37 } 38 } 39 40 rels = append(rels, resolve(s, pkgs)...) 41 return pkgs, rels, err 42 } 43 } 44 45 // resolve will create relationships between packages based on the dependency claims of each package. 46 func resolve(specifier Specifier, pkgs []pkg.Package) (relationships []artifact.Relationship) { 47 pkgsProvidingResource := make(map[string][]artifact.ID) 48 49 pkgsByID := make(map[artifact.ID]pkg.Package) 50 specsByPkg := make(map[artifact.ID]Specification) 51 52 for _, p := range pkgs { 53 id := p.ID() 54 pkgsByID[id] = p 55 specsByPkg[id] = specifier(p) 56 for _, resource := range deduplicate(specifier(p).Provides) { 57 pkgsProvidingResource[resource] = append(pkgsProvidingResource[resource], id) 58 } 59 } 60 61 seen := strset.New() 62 for _, dependantPkg := range pkgs { 63 spec := specsByPkg[dependantPkg.ID()] 64 for _, resource := range deduplicate(spec.Requires) { 65 for _, providingPkgID := range pkgsProvidingResource[resource] { 66 // prevent creating duplicate relationships 67 pairKey := string(providingPkgID) + "-" + string(dependantPkg.ID()) 68 if seen.Has(pairKey) { 69 continue 70 } 71 72 providingPkg := pkgsByID[providingPkgID] 73 74 relationships = append(relationships, 75 artifact.Relationship{ 76 From: providingPkg, 77 To: dependantPkg, 78 Type: artifact.DependencyOfRelationship, 79 }, 80 ) 81 82 seen.Add(pairKey) 83 } 84 } 85 } 86 return relationships 87 } 88 89 func deduplicate(ss []string) []string { 90 // note: we sort the set such that multiple invocations of this function will be deterministic 91 set := strset.New(ss...) 92 list := set.List() 93 sort.Strings(list) 94 return list 95 }