Skip to content
Open
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
10 changes: 10 additions & 0 deletions docs/release-notes/snapcraft-9-0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ files had to end in ``.7zip``.
Additionally, 7zip files are now documented in the :ref:`source-type <PartSpec.source_type>`
key in the project file reference.

Deprecation of non-SPDX licenses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Previously, the ``license`` key could contain any value. Snapcraft now warns when the
value is not a valid `SPDX license expression <https://spdx.org/licenses/>`__ or
``proprietary``.

Non-SPDX values are still accepted for compatibility, but are deprecated and may be
rejected in a future release.

Backwards-incompatible changes
------------------------------

Expand Down
25 changes: 24 additions & 1 deletion snapcraft/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
VersionStr,
)
from craft_application.models.constraints import (
LicenseStr,
SingleEntryDict,
SingleEntryList,
UniqueList,
Expand All @@ -42,7 +43,14 @@
Grammar,
)
from craft_platforms import DebianArchitecture
from pydantic import ConfigDict, PrivateAttr, StringConstraints, error_wrappers
from pydantic import (
ConfigDict,
PrivateAttr,
StringConstraints,
TypeAdapter,
ValidationError,
error_wrappers,
)
from pydantic.json_schema import (
SkipJsonSchema, # noqa: TC002 (typing-only-third-party-import) # pydantic needs to import types at runtime for validation
)
Expand Down Expand Up @@ -2277,6 +2285,21 @@ def get_partitions(self) -> list[str] | None:
"""
return _get_partitions_from_components(self.components)

@pydantic.field_validator("license", mode="before")
@classmethod
def _warn_deprecated_license(cls, lic: str | None) -> str | None:
if lic is None:
return None

try:
TypeAdapter(LicenseStr).validate_python(lic)
except ValidationError:
emit.warning(
"Non-SPDX licenses are deprecated. Use SPDX license strings or 'proprietary' instead."
)

return lic


def _custom_error(error_msg: str):
def _validator(v: Any, next_: Any, ctx: pydantic.ValidationInfo):
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/models/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
from collections.abc import Callable
from contextlib import nullcontext
from typing import Any, cast
from unittest.mock import call

import pydantic
import pytest
from craft_application.errors import CraftValidationError
from craft_application.models import VersionStr
from craft_cli.pytest_plugin import RecordingEmitter
from craft_platforms import DebianArchitecture

import snapcraft.models
Expand Down Expand Up @@ -982,6 +984,34 @@ def test_snapcraftctl_old_bases(self, key, base, project_yaml_data):

Project.unmarshal(project_yaml_data(base=base, parts=parts_data))

@pytest.mark.parametrize(
("lic", "should_warn"),
[
("MIT", False),
("proprietary", False),
(None, False),
("DemonicContract", True),
],
)
def test_non_spdx_deprecation(
self,
lic: str | None,
should_warn: bool,
project_yaml_data: Callable[..., Any],
emitter: RecordingEmitter,
) -> None:
proj = Project.unmarshal(project_yaml_data(license=lic))

assert should_warn == (
call(
"warning",
"Non-SPDX licenses are deprecated. Use SPDX license strings or 'proprietary' instead.",
)
in emitter.interactions
)
# License should always remain unchanged
assert proj.license == lic


class TestHookValidation:
"""Validate hooks."""
Expand Down
Loading