github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/internal/spdxlicense/generate/generate_license_list.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "net/http" 8 "os" 9 "regexp" 10 "sort" 11 "strings" 12 "text/template" 13 "time" 14 ) 15 16 // This program generates license_list.go. 17 const ( 18 source = "license_list.go" 19 url = "https://spdx.org/licenses/licenses.json" 20 ) 21 22 var tmp = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. 23 // This file was generated by robots at {{ .Timestamp }} 24 // using data from {{ .URL }} 25 package spdxlicense 26 27 const Version = {{ printf "%q" .Version }} 28 29 var licenseIDs = map[string]string{ 30 {{- range $k, $v := .LicenseIDs }} 31 {{ printf "%q" $k }}: {{ printf "%q" $v }}, 32 {{- end }} 33 } 34 `)) 35 36 var versionMatch = regexp.MustCompile(`([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`) 37 38 func main() { 39 if err := run(); err != nil { 40 fmt.Println(err.Error()) 41 os.Exit(1) 42 } 43 } 44 45 func run() error { 46 resp, err := http.Get(url) 47 if err != nil { 48 return fmt.Errorf("unable to get licenses list: %w", err) 49 } 50 var result LicenseList 51 if err = json.NewDecoder(resp.Body).Decode(&result); err != nil { 52 return fmt.Errorf("unable to decode license list: %w", err) 53 } 54 defer func() { 55 if err := resp.Body.Close(); err != nil { 56 log.Fatalf("unable to close body: %+v", err) 57 } 58 }() 59 60 f, err := os.Create(source) 61 if err != nil { 62 return fmt.Errorf("unable to create %q: %w", source, err) 63 } 64 defer func() { 65 if err := f.Close(); err != nil { 66 log.Fatalf("unable to close %q: %+v", source, err) 67 } 68 }() 69 70 licenseIDs := processSPDXLicense(result) 71 72 err = tmp.Execute(f, struct { 73 Timestamp time.Time 74 URL string 75 Version string 76 LicenseIDs map[string]string 77 }{ 78 Timestamp: time.Now(), 79 URL: url, 80 Version: result.Version, 81 LicenseIDs: licenseIDs, 82 }) 83 84 if err != nil { 85 return fmt.Errorf("unable to generate template: %w", err) 86 } 87 return nil 88 } 89 90 // Parsing the provided SPDX license list necessitates a three pass approach. 91 // The first pass is only related to what SPDX considers the truth. We use license info to 92 // find replacements for deprecated licenses. 93 // The second pass attempts to generate known short/long version listings for each key. 94 // For info on some short name conventions see this document: 95 // https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-short-name. 96 // The short long listing generation attempts to build all license permutations for a given key. 97 // The new keys are then also associated with their relative SPDX value. If a key has already been entered 98 // we know to ignore it since it came from the first pass which is considered the SPDX source of truth. 99 // We also sort the licenses for the second pass so that cases like `GPL-1` associate to `GPL-1.0` and not `GPL-1.1`. 100 // The third pass is for overwriting deprecated licenses with replacements, for example GPL-2.0+ is deprecated 101 // and now maps to GPL-2.0-or-later. 102 func processSPDXLicense(result LicenseList) map[string]string { 103 // The order of variations/permutations of a license ID matter. 104 // The permutation code can generate the same value for two difference licenses, 105 // for example: The licenses `ABC-1.0` and `ABC-1.1` can both map to `ABC1`, 106 // we need to guarantee the order they are created to avoid mapping them incorrectly. 107 // To do this we use a sorted list. 108 sort.Slice(result.Licenses, func(i, j int) bool { 109 return result.Licenses[i].ID < result.Licenses[j].ID 110 }) 111 112 // keys are simplified by removing dashes and lowercasing ID 113 // this is so license declarations in the wild like: LGPL3 LGPL-3 lgpl3 and lgpl-3 can all match 114 licenseIDs := make(map[string]string) 115 for _, l := range result.Licenses { 116 // licensePerms includes the cleanID in return slice 117 cleanID := cleanLicenseID(l.ID) 118 licensePerms := buildLicenseIDPermutations(cleanID) 119 120 // if license is deprecated, find its replacement and add to licenseIDs 121 if l.Deprecated { 122 idToMap := l.ID 123 replacement := result.findReplacementLicense(l) 124 if replacement != nil { 125 idToMap = replacement.ID 126 } 127 // it's important to use the original licensePerms here so that the deprecated license 128 // can now point to the new correct license 129 for _, id := range licensePerms { 130 if _, exists := licenseIDs[id]; exists { 131 // can be used to debug duplicate license permutations and confirm that examples like GPL1 132 // do not point to GPL-1.1 133 // log.Println("duplicate license list permutation found when mapping deprecated license to replacement") 134 // log.Printf("already have key: %q for SPDX ID: %q; attempted to map replacement ID: %q for deprecated ID: %q\n", id, value, replacement.ID, l.ID) 135 continue 136 } 137 licenseIDs[id] = idToMap 138 } 139 } 140 141 // if license is not deprecated, add all permutations to licenseIDs 142 for _, id := range licensePerms { 143 if _, exists := licenseIDs[id]; exists { 144 // log.Println("found duplicate license permutation key for non deprecated license") 145 // log.Printf("already have key: %q for SPDX ID: %q; tried to insert as SPDX ID:%q\n", id, value, l.ID) 146 continue 147 } 148 licenseIDs[id] = l.ID 149 } 150 } 151 152 return licenseIDs 153 } 154 155 func cleanLicenseID(id string) string { 156 cleanID := strings.ToLower(id) 157 return strings.ReplaceAll(cleanID, "-", "") 158 }