github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/license.go (about) 1 package pkg 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/anchore/syft/internal" 8 "github.com/anchore/syft/internal/log" 9 "github.com/anchore/syft/syft/artifact" 10 "github.com/anchore/syft/syft/file" 11 "github.com/anchore/syft/syft/license" 12 ) 13 14 var _ sort.Interface = (*Licenses)(nil) 15 16 // License represents an SPDX Expression or license value extracted from a packages metadata 17 // We want to ignore URLs and Location since we merge these fields across equal licenses. 18 // A License is a unique combination of value, expression and type, where 19 // its sources are always considered merged and additions to the evidence 20 // of where it was found and how it was sourced. 21 // This is different from how we treat a package since we consider package paths 22 // in order to distinguish if packages should be kept separate 23 // this is different for licenses since we're only looking for evidence 24 // of where a license was declared/concluded for a given package 25 type License struct { 26 Value string `json:"value"` 27 SPDXExpression string `json:"spdxExpression"` 28 Type license.Type `json:"type"` 29 URLs internal.StringSet `hash:"ignore"` 30 Locations file.LocationSet `hash:"ignore"` 31 } 32 33 type Licenses []License 34 35 func (l Licenses) Len() int { 36 return len(l) 37 } 38 39 func (l Licenses) Less(i, j int) bool { 40 if l[i].Value == l[j].Value { 41 if l[i].SPDXExpression == l[j].SPDXExpression { 42 if l[i].Type == l[j].Type { 43 // While URLs and location are not exclusive fields 44 // returning true here reduces the number of swaps 45 // while keeping a consistent sort order of 46 // the order that they appear in the list initially 47 // If users in the future have preference to sorting based 48 // on the slice representation of either field we can update this code 49 return true 50 } 51 return l[i].Type < l[j].Type 52 } 53 return l[i].SPDXExpression < l[j].SPDXExpression 54 } 55 return l[i].Value < l[j].Value 56 } 57 58 func (l Licenses) Swap(i, j int) { 59 l[i], l[j] = l[j], l[i] 60 } 61 62 func NewLicense(value string) License { 63 spdxExpression, err := license.ParseExpression(value) 64 if err != nil { 65 log.Trace("unable to parse license expression for %q: %w", value, err) 66 } 67 68 return License{ 69 Value: value, 70 SPDXExpression: spdxExpression, 71 Type: license.Declared, 72 URLs: internal.NewStringSet(), 73 Locations: file.NewLocationSet(), 74 } 75 } 76 77 func NewLicenseFromType(value string, t license.Type) License { 78 spdxExpression, err := license.ParseExpression(value) 79 if err != nil { 80 log.Trace("unable to parse license expression: %w", err) 81 } 82 83 return License{ 84 Value: value, 85 SPDXExpression: spdxExpression, 86 Type: t, 87 URLs: internal.NewStringSet(), 88 Locations: file.NewLocationSet(), 89 } 90 } 91 92 func NewLicensesFromValues(values ...string) (licenses []License) { 93 for _, v := range values { 94 licenses = append(licenses, NewLicense(v)) 95 } 96 return 97 } 98 99 func NewLicensesFromLocation(location file.Location, values ...string) (licenses []License) { 100 for _, v := range values { 101 if v == "" { 102 continue 103 } 104 licenses = append(licenses, NewLicenseFromLocations(v, location)) 105 } 106 return 107 } 108 109 func NewLicenseFromLocations(value string, locations ...file.Location) License { 110 l := NewLicense(value) 111 for _, loc := range locations { 112 l.Locations.Add(loc) 113 } 114 return l 115 } 116 117 func NewLicenseFromURLs(value string, urls ...string) License { 118 l := NewLicense(value) 119 for _, u := range urls { 120 if u != "" { 121 l.URLs.Add(u) 122 } 123 } 124 return l 125 } 126 127 // this is a bit of a hack to not infinitely recurse when hashing a license 128 func (s License) Merge(l License) (*License, error) { 129 sHash, err := artifact.IDByHash(s) 130 if err != nil { 131 return nil, err 132 } 133 lHash, err := artifact.IDByHash(l) 134 if err != nil { 135 return nil, err 136 } 137 if err != nil { 138 return nil, err 139 } 140 if sHash != lHash { 141 return nil, fmt.Errorf("cannot merge licenses with different hash") 142 } 143 144 s.URLs.Add(l.URLs.ToSlice()...) 145 if s.Locations.Empty() && l.Locations.Empty() { 146 return &s, nil 147 } 148 149 s.Locations.Add(l.Locations.ToSlice()...) 150 return &s, nil 151 }