From d5e5b738aa21f4e504867dcdeb1e67c41bacf292 Mon Sep 17 00:00:00 2001 From: Emre K <110906681+kocaemre@users.noreply.github.com> Date: Fri, 16 Oct 2026 04:29:57 +0200 Subject: [PATCH] fix(sbom): preserve source version in upstream entry Use the resolved source release when no upstream purl is configured, so local wheel version overrides do not misidentify the source. Clarify the upstream setting documentation. Co-Authored-By: Hermes Agent Closes: #1376 Signed-off-by: Emre K <110906681+kocaemre@users.noreply.github.com> --- src/fromager/packagesettings/_models.py | 5 +++-- src/fromager/sbom.py | 9 ++++----- tests/test_sbom.py | 27 +++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/fromager/packagesettings/_models.py b/src/fromager/packagesettings/_models.py index 896e0eaa5..7aa54e0c0 100644 --- a/src/fromager/packagesettings/_models.py +++ b/src/fromager/packagesettings/_models.py @@ -333,8 +333,9 @@ class PurlConfig(pydantic.BaseModel): GENERATED_FROM relationship. Used for packages sourced from GitHub/GitLab rather than PyPI. - When absent, the upstream purl is auto-derived from the downstream - purl without the ``repository_url`` qualifier. + When absent, the upstream purl keeps the downstream name, type, and + namespace but uses the resolved source version instead of a local + downstream version override. The ``repository_url`` qualifier is omitted. """ diff --git a/src/fromager/sbom.py b/src/fromager/sbom.py index 9ec4372f9..1ce7a4092 100644 --- a/src/fromager/sbom.py +++ b/src/fromager/sbom.py @@ -67,9 +67,9 @@ def _build_upstream_purl( """Build the upstream source package URL. If ``upstream`` is set in the per-package ``PurlConfig``, it is - used as-is. Otherwise, the upstream purl is derived from the same - base as the downstream purl but without the ``repository_url`` - qualifier. + used as-is. Otherwise, the upstream purl uses the resolved source + version rather than a downstream wheel version override, and excludes + the ``repository_url`` qualifier. """ pc = pbi.purl_config if pc and pc.upstream: @@ -78,12 +78,11 @@ def _build_upstream_purl( purl_type = pc.type if pc else None purl_namespace = pc.namespace if pc else None purl_name = pc.name if pc else None - purl_version = pc.version if pc else None return PackageURL( type=purl_type or sbom_settings.purl_type, namespace=purl_namespace, name=purl_name or name, - version=purl_version or str(version), + version=str(version), ) diff --git a/tests/test_sbom.py b/tests/test_sbom.py index 50e38e075..b228c3fba 100644 --- a/tests/test_sbom.py +++ b/tests/test_sbom.py @@ -147,6 +147,33 @@ def test_generate_sbom_document_identity_follows_purl_version_override( _validate_spdx(doc) +def test_generate_sbom_upstream_uses_source_version_without_upstream_override( + tmp_path: pathlib.Path, +) -> None: + """Keep the source release distinct from a locally versioned wheel.""" + ctx = make_sbom_ctx( + tmp_path, + sbom_settings=SbomSettings(), + package_overrides={"purl": {"version": "0.5.5+local.1"}}, + ) + doc = sbom.generate_sbom( + ctx=ctx, + req=Requirement("test-pkg==0.5.5"), + version=Version("0.5.5"), + ) + + wheel, upstream = doc["packages"] + assert wheel["versionInfo"] == "0.5.5+local.1" + assert wheel["externalRefs"][0]["referenceLocator"] == ( + "pkg:pypi/test-pkg@0.5.5%2Blocal.1" + ) + assert upstream["versionInfo"] == "0.5.5" + assert upstream["externalRefs"][0]["referenceLocator"] == ( + "pkg:pypi/test-pkg@0.5.5" + ) + _validate_spdx(doc) + + def test_generate_sbom_namespace_is_unique_per_document( tmp_path: pathlib.Path, ) -> None: