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  }