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

feat(license): add struct with types for licenses #256

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions pkg/python/packaging/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,31 @@ func (*Parser) Parse(r dio.ReadSeekerAt) ([]types.Library, []types.Dependency, e
if l := h.Get("License-Expression"); l != "" {
license = l
} else if l := h.Get("License"); l != "" {
license = l
// The license field can contain information about different licenses, license exceptions , etc.:
// https://packaging.python.org/en/latest/specifications/core-metadata/#license,
// but it is impossible to define a delimiter to separate them.
// Mark them, so we don't have to separate them later.
license = types.NonSeparableLicensePrefix + l
} else {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about checking license classifiers before license, but in this case we might miss some nuances (like license exclusion).
So I left the old order.

var licenses []string
// license classifiers are deprecated:
// https://peps.python.org/pep-0639/#deprecate-license-classifiers
for _, classifier := range h.Values("Classifier") {
if strings.HasPrefix(classifier, "License :: ") {
values := strings.Split(classifier, " :: ")
license = values[len(values)-1]
break
// there can be several classifiers with licenses
licenses = append(licenses, values[len(values)-1])
}
}
license = strings.Join(licenses, ", ")
}
if license == "" && h.Get("License-File") != "" {
license = "file://" + h.Get("License-File")
var licenseFiles []string
for _, licenseFile := range h.Values("License-File") {
// there can be several license files
licenseFiles = append(licenseFiles, "file://"+licenseFile)
}
license = strings.Join(licenseFiles, ", ")
}

return []types.Library{
Expand Down
12 changes: 6 additions & 6 deletions pkg/python/packaging/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestParse(t *testing.T) {
// cd /usr/lib/python3.9/site-packages/setuptools-52.0.0-py3.9.egg-info/
// cat PKG-INFO | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \
// tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
want: []types.Library{{Name: "setuptools", Version: "51.3.3", License: "UNKNOWN"}},
want: []types.Library{{Name: "setuptools", Version: "51.3.3", License: "non-separable: UNKNOWN"}},
},
{
name: "egg-info",
Expand All @@ -44,7 +44,7 @@ func TestParse(t *testing.T) {
// cd /usr/lib/python3.9/site-packages/
// cat distlib-0.3.1-py3.9.egg-info | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \
// tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python license"}},
want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "non-separable: Python license"}},
},
{
name: "wheel METADATA",
Expand All @@ -60,12 +60,12 @@ func TestParse(t *testing.T) {
want: []types.Library{{Name: "simple", Version: "0.1.0", License: ""}},
},
{
name: "wheel METADATA",
name: "wheel METADATA with license",

// for single METADATA file with known name
// cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}'
input: "testdata/distlib-0.3.1.METADATA",
want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python license"}},
want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "non-separable: Python license"}},
},
{
name: "invalid",
Expand All @@ -90,7 +90,7 @@ func TestParse(t *testing.T) {
{
Name: "zipp",
Version: "3.12.1",
License: "MIT License",
License: "Apache Software License, MIT License",
},
},
},
Expand All @@ -101,7 +101,7 @@ func TestParse(t *testing.T) {
{
Name: "networkx",
Version: "3.0",
License: "file://LICENSE.txt",
License: "file://LICENSE-APACHE, file://LICENSE.txt",
},
},
},
Expand Down
1 change: 1 addition & 0 deletions pkg/python/packaging/testdata/networkx-3.0.METADATA
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.8
License-File: LICENSE-APACHE
License-File: LICENSE.txt
Provides-Extra: default
Requires-Dist: numpy (>=1.20) ; extra == 'default'
Expand Down
1 change: 1 addition & 0 deletions pkg/python/packaging/testdata/zipp-3.12.1.METADATA
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Author: Jason R. Coombs
Author-email: jaraco@jaraco.com
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Expand Down
2 changes: 2 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,6 @@ const (
RefVCS RefType = "vcs"
RefIssueTracker RefType = "issue-tracker"
RefOther RefType = "other"

NonSeparableLicensePrefix = "non-separable: "
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have file:// prefix.

Perhaps we want to create new struct for License field.

just as idea:

type License struct {
	Value string
	Type  licenseType
}

type licenseType string

const (
	fileLicenseType             licenseType = "file" // filename for license file
	textLicenseType             licenseType = "text" // text of license (for split, classifier, etc.)
	nonSeparableTextLicenseType licenseType = "nonSeparable-text" // text of license without possible to split
	formattedLicenseType        licenseType = "formattedLicenses" // formatted licenses (e.g. in SPDX format)
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds reasonable. We can go with that approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knqyf263 I have added licenseType to this PR.
I also updated aquasecurity/trivy#5211 to show the required changes for Trivy.
#5211 requires some refactoring. I will do this if you confirm that we are ready for these changes.

)
Loading