github.com/anchore/syft@v1.38.2/syft/pkg/license_set.go (about)

     1  package pkg
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/gohugoio/hashstructure"
     8  
     9  	"github.com/anchore/syft/internal/log"
    10  	"github.com/anchore/syft/syft/artifact"
    11  )
    12  
    13  type LicenseSet struct {
    14  	set map[artifact.ID]License
    15  }
    16  
    17  func NewLicenseSet(licenses ...License) (s LicenseSet) {
    18  	for _, l := range licenses {
    19  		s.Add(l)
    20  	}
    21  
    22  	return s
    23  }
    24  
    25  func (s *LicenseSet) addToExisting(license License) (id artifact.ID, merged bool, err error) {
    26  	id, err = artifact.IDByHash(license)
    27  	if err != nil {
    28  		return id, false, fmt.Errorf("could not get the hash for a license: %w", err)
    29  	}
    30  
    31  	v, ok := s.set[id]
    32  	if !ok {
    33  		// doesn't exist safe to add
    34  		return id, false, nil
    35  	}
    36  
    37  	// we got the same id; we want to merge the URLs and Location data
    38  	// URLs/Location are not considered when taking the Hash
    39  	m, err := v.Merge(license)
    40  	if err != nil {
    41  		return id, false, fmt.Errorf("could not merge license into map: %w", err)
    42  	}
    43  	s.set[id] = *m
    44  
    45  	return id, true, nil
    46  }
    47  
    48  func (s *LicenseSet) Add(licenses ...License) {
    49  	if s.set == nil {
    50  		s.set = make(map[artifact.ID]License)
    51  	}
    52  	for _, l := range licenses {
    53  		// we only want to add licenses that are not empty
    54  		if l.Empty() {
    55  			continue
    56  		}
    57  		// note, this check should be moved to the license constructor in the future
    58  		if id, merged, err := s.addToExisting(l); err == nil && !merged {
    59  			// doesn't exist, add it
    60  			s.set[id] = l
    61  		} else if err != nil {
    62  			log.WithFields("error", err, "license", l).Trace("failed to add license to license set")
    63  		}
    64  	}
    65  }
    66  
    67  func (s LicenseSet) ToSlice(sorters ...func(a, b License) int) []License {
    68  	licenses := s.ToUnorderedSlice()
    69  
    70  	var sorted bool
    71  	for _, sorter := range sorters {
    72  		if sorter == nil {
    73  			continue
    74  		}
    75  		sort.Slice(licenses, func(i, j int) bool {
    76  			return sorter(licenses[i], licenses[j]) < 0
    77  		})
    78  		sorted = true
    79  		break
    80  	}
    81  
    82  	if !sorted {
    83  		sort.Sort(Licenses(licenses))
    84  	}
    85  
    86  	return licenses
    87  }
    88  
    89  func (s LicenseSet) ToUnorderedSlice() []License {
    90  	if s.set == nil {
    91  		return nil
    92  	}
    93  	licenses := make([]License, len(s.set))
    94  	idx := 0
    95  	for _, v := range s.set {
    96  		licenses[idx] = v
    97  		idx++
    98  	}
    99  	return licenses
   100  }
   101  
   102  func (s LicenseSet) Hash() (uint64, error) {
   103  	// access paths and filesystem IDs are not considered when hashing a license set, only the real paths
   104  	return hashstructure.Hash(s.ToSlice(), &hashstructure.HashOptions{
   105  		ZeroNil:      true,
   106  		SlicesAsSets: true,
   107  	})
   108  }
   109  
   110  func (s LicenseSet) Empty() bool {
   111  	return len(s.set) < 1
   112  }