github.com/anchore/syft@v1.38.2/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/internal" 9 "github.com/anchore/syft/syft/artifact" 10 "github.com/anchore/syft/syft/pkg" 11 "github.com/anchore/syft/syft/pkg/cataloger/generic" 12 ) 13 14 // Specification holds strings that indicate abstract resources that a package provides for other packages and 15 // requires for itself. These strings can represent anything from file paths, package names, or any other concept 16 // that is useful for dependency resolution within that packing ecosystem. 17 type Specification struct { 18 ProvidesRequires 19 20 // Variants allows for specifying multiple sets of provides/requires for a single package. This is useful 21 // in cases when you have conditional optional dependencies for a package. 22 Variants []ProvidesRequires 23 } 24 25 type ProvidesRequires struct { 26 // Provides holds a list of abstract resources that this package provides for other packages. 27 Provides []string 28 29 // Requires holds a list of abstract resources that this package requires from other packages. 30 Requires []string 31 } 32 33 // Specifier is a function that takes a package and extracts a Specification, describing resources 34 // the package provides and needs. 35 type Specifier func(pkg.Package) Specification 36 37 // Processor returns a generic processor that will resolve relationships between packages based on the dependency claims. 38 func Processor(s Specifier) generic.Processor { 39 return func(pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) { 40 // we can't move forward unless all package IDs have been set 41 for idx, p := range pkgs { 42 id := p.ID() 43 if id == "" { 44 p.SetID() 45 pkgs[idx] = p 46 } 47 } 48 49 rels = append(rels, Resolve(s, pkgs)...) 50 return pkgs, rels, err 51 } 52 } 53 54 // Resolve will create relationships between packages based on the dependency claims of each package. 55 func Resolve(specifier Specifier, pkgs []pkg.Package) (relationships []artifact.Relationship) { 56 pkgsProvidingResource := make(map[string]internal.Set[artifact.ID]) 57 58 pkgsByID := make(map[artifact.ID]pkg.Package) 59 specsByPkg := make(map[artifact.ID][]ProvidesRequires) 60 61 for _, p := range pkgs { 62 id := p.ID() 63 pkgsByID[id] = p 64 specsByPkg[id] = allProvides(pkgsProvidingResource, id, specifier(p)) 65 } 66 67 seen := strset.New() 68 for _, dependantPkg := range pkgs { 69 specs := specsByPkg[dependantPkg.ID()] 70 for _, spec := range specs { 71 for _, resource := range deduplicate(spec.Requires) { 72 for providingPkgID := range pkgsProvidingResource[resource] { 73 // prevent creating duplicate relationships 74 pairKey := string(providingPkgID) + "-" + string(dependantPkg.ID()) 75 if seen.Has(pairKey) { 76 continue 77 } 78 79 providingPkg := pkgsByID[providingPkgID] 80 81 relationships = append(relationships, 82 artifact.Relationship{ 83 From: providingPkg, 84 To: dependantPkg, 85 Type: artifact.DependencyOfRelationship, 86 }, 87 ) 88 89 seen.Add(pairKey) 90 } 91 } 92 } 93 } 94 return relationships 95 } 96 97 func allProvides(pkgsProvidingResource map[string]internal.Set[artifact.ID], id artifact.ID, spec Specification) []ProvidesRequires { 98 prs := []ProvidesRequires{spec.ProvidesRequires} 99 prs = append(prs, spec.Variants...) 100 101 for _, pr := range prs { 102 for _, resource := range deduplicate(pr.Provides) { 103 if pkgsProvidingResource[resource] == nil { 104 pkgsProvidingResource[resource] = internal.NewSet[artifact.ID]() 105 } 106 pkgsProvidingResource[resource].Add(id) 107 } 108 } 109 110 return prs 111 } 112 113 func deduplicate(ss []string) []string { 114 // note: we sort the set such that multiple invocations of this function will be deterministic 115 set := strset.New(ss...) 116 list := set.List() 117 sort.Strings(list) 118 return list 119 }