Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
18 changes: 13 additions & 5 deletions src/fromager/pkgmetadata/pep639.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
- https://spdx.org/licenses/
"""

from license_expression import LicenseExpression, get_spdx_licensing
from license_expression import ExpressionError, LicenseExpression, get_spdx_licensing
from packaging.licenses import InvalidLicenseExpression, canonicalize_license_expression
from packaging.metadata import Metadata


Expand Down Expand Up @@ -62,10 +63,17 @@ def license_from_metadata_values(


def _parse_spdx(text: str, *, simplify: bool = False) -> LicenseExpression:
"""Parse, validate, and simplify a SPDX license expression"""
# LicenseRef are references to non-SPDX licenses
validate = not text.startswith("LicenseRef-")
expr = _SPDX.parse(text, validate=validate)
"""Parse, validate, and simplify a SPDX license expression

`canonicalize_license_expression()` validates, so the rules match
PEP 639 exactly (e.g. ``LicenseRef-`` anywhere, but only SPDX
exceptions after ``WITH``); license-expression only builds the object.
"""
try:
canonical = canonicalize_license_expression(text)
except InvalidLicenseExpression as err:
raise ExpressionError(str(err)) from err
expr = _SPDX.parse(canonical, validate=False)
if simplify:
expr = expr.simplify()
return expr
Expand Down
34 changes: 34 additions & 0 deletions tests/test_pkgmetadata.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from license_expression import ExpressionError
from packaging.metadata import Metadata, RawMetadata

from fromager.pkgmetadata import (
Expand Down Expand Up @@ -82,6 +83,14 @@ def test_project_urls_from_metadata(
["metadata", "expected"],
[
[md("MIT AND MIT or MIT"), "MIT"],
# LicenseRef is valid anywhere in a PEP 639 expression
[md("MIT OR LicenseRef-Proprietary"), "LicenseRef-Proprietary OR MIT"],
[md("LicenseRef-Proprietary"), "LicenseRef-Proprietary"],
# exception keys must not be reported as unknown next to a LicenseRef
[
md("MIT WITH LLVM-exception OR LicenseRef-z"),
"LicenseRef-z OR MIT WITH LLVM-exception",
],
[md(license="MIT License"), "MIT"],
[md(license="MIT and PSF-2.0"), "MIT AND PSF-2.0"],
[md(license="BSD 3-Clause License"), "BSD-3-Clause"],
Expand Down Expand Up @@ -133,3 +142,28 @@ def test_license_from_metadata_errors(
license_text=license,
classifiers=classifiers,
)


@pytest.mark.parametrize(
["license_expression", "match"],
[
# a leading LicenseRef must not disable validation of other keys
("LicenseRef-Foo OR Apache 2", "Apache 2"),
# SPDX idstrings only allow letters, digits, "." and "-"
("LicenseRef-a_b", "(?i)LicenseRef-a_b"),
("MIT WITH NotAnException", "(?i)NotAnException"),
# PEP 639 only allows SPDX exceptions after WITH
("Apache-2.0 WITH LicenseRef-x", "(?i)LicenseRef-x"),
],
)
def test_license_from_metadata_invalid_expression(
license_expression: str, match: str
) -> None:
with pytest.raises(ExpressionError, match=match):
license_from_metadata_values(license_expression=license_expression)


def test_license_from_metadata_canonicalizes_licenseref_prefix() -> None:
# PEP 639 matches the LicenseRef- prefix case-insensitively
expr = license_from_metadata_values(license_expression="licenseref-foo")
assert str(expr) == "LicenseRef-foo"
Loading