diff --git a/conan/tools/sbom/cyclonedx.py b/conan/tools/sbom/cyclonedx.py index 06ddc649b04..8e2f4b17194 100644 --- a/conan/tools/sbom/cyclonedx.py +++ b/conan/tools/sbom/cyclonedx.py @@ -194,21 +194,28 @@ def cyclonedx_1_6(conanfile, name=None, add_build=False, add_tests=False, **kwar return sbom_cyclonedx_1_6 +def _is_expr(license_value): + v = license_value.upper() + return " AND " in v or " OR " in v or " WITH " in v + + def _calculate_licenses(component): from conan.tools.sbom.spdx_licenses import NORMALIZED_VALID_SPDX_LICENSES - licenses = component.conanfile.license - if isinstance(licenses, str): # Just one license - field = "id" if licenses.lower() in NORMALIZED_VALID_SPDX_LICENSES else "name" - return [{"license": {field: licenses}}] - - return [ - # More than one license - {"license": { - "id" if lic.lower() in NORMALIZED_VALID_SPDX_LICENSES else "name": lic - }} - for lic in licenses - ] + licenses = component.conanfile.license + if isinstance(licenses, str): + licenses = [licenses] + + result = [] + for lic in licenses: + if lic.lower() in NORMALIZED_VALID_SPDX_LICENSES: + field = "id" + elif _is_expr(lic): + field = "expression" + else: + field = "name" + result.append({"license": {field: lic}}) + return result def _calculate_bomref(component): diff --git a/test/unittests/tools/sbom/test_spdx_expression.py b/test/unittests/tools/sbom/test_spdx_expression.py new file mode 100644 index 00000000000..a22875c8637 --- /dev/null +++ b/test/unittests/tools/sbom/test_spdx_expression.py @@ -0,0 +1,23 @@ +import pytest + +from conan.tools.sbom.cyclonedx import _calculate_licenses +from conan.test.utils.mocks import ConanFileMock + + +@pytest.mark.parametrize( + "license_value, expected", + [ + ("MIT", "id"), + ("mit", "id"), + ("MIT OR Apache-2.0", "expression"), + ("( MIT AND ( MIT ) )", "expression"), + ("(MIT WITH (MIT))", "expression"), + ("custom license", "name"), + ], +) +def test_license_field(license_value, expected): + component = type("Component", (), {})() + component.conanfile = ConanFileMock() + component.conanfile.license = license_value + field = next(iter(_calculate_licenses(component)[0]["license"])) + assert field == expected