Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overwrite deprecated SPDX licenses automatically #1009

Merged
merged 12 commits into from Aug 2, 2022
6 changes: 3 additions & 3 deletions internal/formats/common/cyclonedxhelpers/licenses_test.go
Expand Up @@ -49,7 +49,7 @@ func Test_encodeLicense(t *testing.T) {
},
expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{ID: "MIT"}},
{License: &cyclonedx.License{ID: "GPL-3.0"}},
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
},
},
{
Expand All @@ -60,7 +60,7 @@ func Test_encodeLicense(t *testing.T) {
},
},
expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{ID: "GPL-3.0"}},
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
},
},
{
Expand All @@ -71,7 +71,7 @@ func Test_encodeLicense(t *testing.T) {
},
},
expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{ID: "GPL-2.0"}},
{License: &cyclonedx.License{ID: "GPL-2.0-only"}},
},
},
}
Expand Down
6 changes: 3 additions & 3 deletions internal/formats/common/spdxhelpers/license_test.go
Expand Up @@ -44,7 +44,7 @@ func Test_License(t *testing.T) {
"GPL-3.0",
},
},
expected: "MIT AND GPL-3.0",
expected: "MIT AND GPL-3.0-only",
},
{
name: "cap insensitive",
Expand All @@ -53,7 +53,7 @@ func Test_License(t *testing.T) {
"gpl-3.0",
},
},
expected: "GPL-3.0",
expected: "GPL-3.0-only",
},
{
name: "debian to spdx conversion",
Expand All @@ -62,7 +62,7 @@ func Test_License(t *testing.T) {
"GPL-2",
},
},
expected: "GPL-2.0",
expected: "GPL-2.0-only",
},
}
for _, test := range tests {
Expand Down
75 changes: 64 additions & 11 deletions internal/spdxlicense/generate/generate_license_list.go
Expand Up @@ -37,16 +37,40 @@ var licenseIDs = map[string]string{

var versionMatch = regexp.MustCompile(`-([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)

type License struct {
ID string `json:"licenseId"`
Name string `json:"name"`
Text string `json:"licenseText"`
Deprecated bool `json:"isDeprecatedLicenseId"`
OSIApproved bool `json:"isOsiApproved"`
SeeAlso []string `json:"seeAlso"`
}

func (l *License) isEquivalent(other License) bool {
jonasagx marked this conversation as resolved.
Show resolved Hide resolved
if l.Name != other.Name {
return false
}

if l.OSIApproved != other.OSIApproved {
return false
}

if len(l.SeeAlso) != len(other.SeeAlso) {
return false
}

for i := 0; i < len(l.SeeAlso); i++ {
jonasagx marked this conversation as resolved.
Show resolved Hide resolved
if l.SeeAlso[i] != other.SeeAlso[i] {
return false
}
}

return l.ID != other.ID
}

type LicenseList struct {
Version string `json:"licenseListVersion"`
Licenses []struct {
ID string `json:"licenseId"`
Name string `json:"name"`
Text string `json:"licenseText"`
Deprecated bool `json:"isDeprecatedLicenseId"`
OSIApproved bool `json:"isOsiApproved"`
SeeAlso []string `json:"seeAlso"`
} `json:"licenses"`
Version string `json:"licenseListVersion"`
Licenses []License `json:"licenses"`
}

func main() {
Expand Down Expand Up @@ -102,24 +126,36 @@ func run() error {
return nil
}

// Parsing the provided SPDX license list necessitates a two pass approach.
// The first pass is only related to what SPDX considers the truth. These K:V pairs will never be overwritten.
// Parsing the provided SPDX license list necessitates a three pass approach.
// The first pass is only related to what SPDX considers the truth. We use license info to
// find replacements for deprecated licenses.
// The second pass attempts to generate known short/long version listings for each key.
// For info on some short name conventions see this document:
// https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-short-name.
// The short long listing generation attempts to build all license permutations for a given key.
// The new keys are then also associated with their relative SPDX value. If a key has already been entered
// we know to ignore it since it came from the first pass which is considered the SPDX source of truth.
// 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`.
// The third pass is for overwriting deprecated licenses with replacements, for example GPL-2.0+ is deprecated
// and now maps to GPL-2.0-or-later.
func processSPDXLicense(result LicenseList) map[string]string {
// first pass build map
var licenseIDs = make(map[string]string)
var overwrite = make(map[string]string)

for _, l := range result.Licenses {
cleanID := strings.ToLower(l.ID)
if _, exists := licenseIDs[cleanID]; exists {
log.Fatalf("duplicate license ID found: %q", cleanID)
}
licenseIDs[cleanID] = l.ID

if l.Deprecated {
replacement := findEquivalentLicense(l, result.Licenses)
if replacement != nil {
overwrite[cleanID] = replacement.ID
}
}
wagoodman marked this conversation as resolved.
Show resolved Hide resolved
}

sort.Slice(result.Licenses, func(i, j int) bool {
Expand All @@ -139,9 +175,26 @@ func processSPDXLicense(result LicenseList) map[string]string {
}
}

for k, v := range licenseIDs {
cleanID := strings.ToLower(v)
wagoodman marked this conversation as resolved.
Show resolved Hide resolved
if remap, exists := overwrite[cleanID]; exists {
licenseIDs[k] = remap
}
}

return licenseIDs
}

func findEquivalentLicense(deprecated License, allLicenses []License) *License {
jonasagx marked this conversation as resolved.
Show resolved Hide resolved
for _, l := range allLicenses {
if l.isEquivalent(deprecated) {
wagoodman marked this conversation as resolved.
Show resolved Hide resolved
return &l
}
}

return nil
}

func buildLicensePermutations(license string) (perms []string) {
lv := findLicenseVersion(license)
vp := versionPermutations(lv)
Expand Down
48 changes: 48 additions & 0 deletions internal/spdxlicense/generate/generate_license_list_test.go
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLicensePermutations(t *testing.T) {
Expand Down Expand Up @@ -144,3 +145,50 @@ func TestFindLicenseVersion(t *testing.T) {
})
}
}

func TestReplaceDeprecatedLicenses(t *testing.T) {
jonasagx marked this conversation as resolved.
Show resolved Hide resolved
results := LicenseList{
Licenses: []License{
{
ID: "ABC-1.0+",
Name: "The ABC License 1.0",
Deprecated: true,
},
{
ID: "ABC-1.0-Or-later",
Name: "The ABC License 1.0",
},
{
ID: "ABC-1.0",
Name: "The ABC License 1.0 Only",
Deprecated: true,
},
{
ID: "ABC-1.0-Only",
Name: "The ABC License 1.0 Only",
},
},
}

expected := map[string]string{
"abc-1": "ABC-1.0-Only",
"abc-1-only": "ABC-1.0-Only",
"abc-1.0": "ABC-1.0-Only",
"abc-1.0.0": "ABC-1.0-Only",
"abc-1.0-only": "ABC-1.0-Only",
"abc-1.0.0-only": "ABC-1.0-Only",
"abc-1+": "ABC-1.0-Or-later",
"abc-1.0+": "ABC-1.0-Or-later",
"abc-1.0.0+": "ABC-1.0-Or-later",
"abc-1-or-later": "ABC-1.0-Or-later",
"abc-1.0-or-later": "ABC-1.0-Or-later",
"abc-1.0.0-or-later": "ABC-1.0-Or-later",
}

licenses := processSPDXLicense(results)
for k, v := range licenses {
e := expected[k]
// t.Logf("%s --> %s, e: %s", k, v, e)
jonasagx marked this conversation as resolved.
Show resolved Hide resolved
require.Equal(t, e, v, k)
}
}
66 changes: 33 additions & 33 deletions internal/spdxlicense/license_list.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion internal/spdxlicense/license_list_test.go
Expand Up @@ -10,6 +10,7 @@ func TestLicenceListIDs(t *testing.T) {
// do a sanity check on the generated data
assert.Equal(t, "0BSD", licenseIDs["0bsd"])
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl-2.1"])
assert.Equal(t, "GPL-2.0", licenseIDs["gpl-2"])
assert.Equal(t, "GPL-2.0-only", licenseIDs["gpl-2"])
assert.Equal(t, "GPL-2.0-or-later", licenseIDs["gpl-2+"])
assert.NotEmpty(t, Version)
}
6 changes: 3 additions & 3 deletions internal/spdxlicense/license_test.go
Expand Up @@ -17,11 +17,11 @@ func TestIDParse(t *testing.T) {
},
{
"GPL-2",
"GPL-2.0",
"GPL-2.0-only",
},
{
"GPL-2+",
"GPL-2.0+",
"GPL-1+",
wagoodman marked this conversation as resolved.
Show resolved Hide resolved
"GPL-1.0-or-later",
},
{
"GPL-3.0.0-or-later",
Expand Down