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  }