github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/license_set.go (about) 1 package pkg 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/mitchellh/hashstructure/v2" 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 have a value 54 // note, this check should be moved to the license constructor in the future 55 if l.Value != "" { 56 if id, merged, err := s.addToExisting(l); err == nil && !merged { 57 // doesn't exist, add it 58 s.set[id] = l 59 } else if err != nil { 60 log.Trace("license set failed to add license %#v: %+v", l, err) 61 } 62 } 63 } 64 } 65 66 func (s LicenseSet) ToSlice() []License { 67 if s.set == nil { 68 return nil 69 } 70 var licenses []License 71 for _, v := range s.set { 72 licenses = append(licenses, v) 73 } 74 sort.Sort(Licenses(licenses)) 75 return licenses 76 } 77 78 func (s LicenseSet) Hash() (uint64, error) { 79 // access paths and filesystem IDs are not considered when hashing a license set, only the real paths 80 return hashstructure.Hash(s.ToSlice(), hashstructure.FormatV2, &hashstructure.HashOptions{ 81 ZeroNil: true, 82 SlicesAsSets: true, 83 }) 84 } 85 86 func (s LicenseSet) Empty() bool { 87 return len(s.set) < 1 88 }