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  }