From d17448bf054a0003dd9f004693971ad92dc68976 Mon Sep 17 00:00:00 2001 From: Christoph Steiger Date: Mon, 2 Mar 2026 13:47:59 +0100 Subject: [PATCH 1/4] feat(package): track Pre-Depends field Track the Pre-Depends field for binary packages. Pre-Depends relationships are similar to Depends relationships, but there are some subtle differences, see [1] for details. [1] https://www.debian.org/doc/debian-policy/ch-relationships.html#binary-dependencies-depends-recommends-suggests-enhances-pre-depends Signed-off-by: Christoph Steiger --- src/debsbom/dpkg/package.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/debsbom/dpkg/package.py b/src/debsbom/dpkg/package.py index 7ac431d..d16a403 100644 --- a/src/debsbom/dpkg/package.py +++ b/src/debsbom/dpkg/package.py @@ -566,6 +566,7 @@ class BinaryPackage(Package): architecture: str | None source: Dependency | None depends: list[Dependency] + pre_depends: list[Dependency] provides: list[VirtualPackage] built_using: list[Dependency] description: str | None @@ -582,6 +583,7 @@ def __init__( architecture: str | None = None, source: Dependency | None = None, depends: list[Dependency] = [], + pre_depends: list[Dependency] = [], provides: list[VirtualPackage] = [], built_using: list[Dependency] = [], description: str | None = None, @@ -597,6 +599,7 @@ def __init__( self.source = source self.version = Version(version) self.depends = depends + self.pre_depends = pre_depends self.provides = provides self.built_using = built_using self.description = description @@ -667,6 +670,10 @@ def merge_with(self, other: "BinaryPackage"): depends.extend(x for x in other.depends if x not in depends) self.depends = depends + pre_depends = list(self.pre_depends) + pre_depends.extend(x for x in other.pre_depends if x not in pre_depends) + self.pre_depends = pre_depends + built_using = list(self.built_using) built_using.extend(x for x in other.built_using if x not in built_using) self.built_using = built_using @@ -735,6 +742,9 @@ def from_deb822(cls, package) -> "BinaryPackage": pdepends = package.relations["depends"] or [] dependencies = Dependency.from_pkg_relations(pdepends) + pre_pdepends = package.relations["pre-depends"] or [] + pre_dependencies = Dependency.from_pkg_relations(pre_pdepends) + provides = VirtualPackage.from_pkg_relations(package.relations["provides"] or []) # static dependencies @@ -756,6 +766,7 @@ def from_deb822(cls, package) -> "BinaryPackage": source=srcdep, version=package.get("Version"), depends=dependencies, + pre_depends=pre_dependencies, provides=provides, built_using=sdepends, description=cls._cleanup_description(package.get("Description")), From a46f24a73a97c10ac5c6460f57c7ba9375c23a89 Mon Sep 17 00:00:00 2001 From: Christoph Steiger Date: Mon, 2 Mar 2026 13:52:38 +0100 Subject: [PATCH 2/4] chore(tests): add test for Pre-Depends field Add a Pre-Depends stanza to the minimal status file and check if it is properly parsed. Signed-off-by: Christoph Steiger --- tests/data/dpkg-status-minimal | 1 + tests/test_dpkg.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tests/data/dpkg-status-minimal b/tests/data/dpkg-status-minimal index 8ad1143..2246f79 100644 --- a/tests/data/dpkg-status-minimal +++ b/tests/data/dpkg-status-minimal @@ -7,6 +7,7 @@ Maintainer: Matthias Klose Architecture: amd64 Version: 2.40-2 Provides: binutils-gold, elf-binutils +Pre-Depends: init-system-helpers (>= 1.54~) Depends: binutils-common (= 2.40-2), libbinutils (= 2.40-2), binutils-x86-64-linux-gnu (= 2.40-2) Suggests: binutils-doc (>= 2.40-2) Conflicts: binutils-mingw-w64-i686 (<< 2.23.52.20130612-1+3), binutils-mingw-w64-x86-64 (<< 2.23.52.20130612-1+3), binutils-multiarch (<< 2.27-8), modutils (<< 2.4.19-1) diff --git a/tests/test_dpkg.py b/tests/test_dpkg.py index bf7707e..3d8bb93 100644 --- a/tests/test_dpkg.py +++ b/tests/test_dpkg.py @@ -48,6 +48,9 @@ def test_parse_minimal_status_file(mode): assert bpkg.maintainer == "Matthias Klose " assert bpkg.source == Dependency(bpkg.name, None, ("=", bpkg.version), arch="source") assert bpkg.version == "2.40-2" + assert bpkg.pre_depends == [ + Dependency("init-system-helpers", None, (">=", "1.54~")), + ] assert bpkg.depends == [ Dependency("binutils-common", None, ("=", bpkg.version)), Dependency("libbinutils", None, ("=", bpkg.version)), From 7af131fc0b3f1ed2e68729cde909d61675958ec7 Mon Sep 17 00:00:00 2001 From: Christoph Steiger Date: Mon, 2 Mar 2026 14:38:26 +0100 Subject: [PATCH 3/4] feat(generate): include Pre-Depends in SBOMs dependency graph Include Pre-Depends dependencies in the dependency graph. They are treated in the same way as "normal" Depends dependencies. Signed-off-by: Christoph Steiger --- docs/source/design-decisions.rst | 3 ++- src/debsbom/dpkg/package.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/source/design-decisions.rst b/docs/source/design-decisions.rst index ec9f151..d89ab44 100644 --- a/docs/source/design-decisions.rst +++ b/docs/source/design-decisions.rst @@ -57,7 +57,7 @@ The following table shows how fields in a Debian binary package are mapped to fi ``Conffiles``, \-, \- ``Depends``, [#depends]_, [#depends]_ ``Recommends``, \-, \- - ``Pre-Depends``, \-, \- + ``Pre-Depends``, [#pre_depends]_, [#pre_depends]_ ``Suggests``, \-, \- ``Description``, ``summary`` and ``description`` [#description]_, ``description`` ``Built-Using``, [#built_using_spdx]_, [#built_using_cdx]_ @@ -71,6 +71,7 @@ The following table shows how fields in a Debian binary package are mapped to fi .. [#architecture] The architecture is only part of the PURL .. [#source] When a ``Source`` is specified it gets a separate entry in the SBOM and the dependency is added .. [#depends] When a ``Dependency`` is specified a dependency is created for the related packages +.. [#pre_depends] ``Pre-Depends`` dependencies are treated exactly the same as ``Depends`` dependencies, i.e. simply appended to them .. [#description] The synopsis (first line) is the ``summary``, the rest goes into the ``description`` .. [#built_using_spdx] Any ``Built-Using`` dependency gets a separate entry and the ``GENERATED_FROM`` relationship is used .. [#built_using_cdx] Any ``Built-Using`` dependency gets a separate entry and the dependency is added diff --git a/src/debsbom/dpkg/package.py b/src/debsbom/dpkg/package.py index d16a403..4b06979 100644 --- a/src/debsbom/dpkg/package.py +++ b/src/debsbom/dpkg/package.py @@ -630,6 +630,11 @@ def source_package(self) -> SourcePackage | None: else: return None + @property + def all_depends(self) -> Iterable[Dependency]: + """Returns an iterator containing both "Depends" and "Pre-Depends.""" + return itertools.chain(self.depends, self.pre_depends) + @property def unique_depends(self): """ @@ -639,7 +644,7 @@ def unique_depends(self): """ seen = set() unique = [] - for dep in self.depends: + for dep in self.all_depends: key = (dep.name, dep.arch) if key not in seen: seen.add(key) From 3be1742bf0806d3558605145affe16ee2911061e Mon Sep 17 00:00:00 2001 From: Christoph Steiger Date: Mon, 2 Mar 2026 14:39:57 +0100 Subject: [PATCH 4/4] chore(tests): add test for Pre-Depends SBOM generation Add a test that checks if Pre-Depends dependencies are included in generated SBOMs. Signed-off-by: Christoph Steiger --- tests/root/pre-depends/var/lib/dpkg/status | 28 ++++++++++++++++++ tests/test_generation.py | 34 ++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/root/pre-depends/var/lib/dpkg/status diff --git a/tests/root/pre-depends/var/lib/dpkg/status b/tests/root/pre-depends/var/lib/dpkg/status new file mode 100644 index 0000000..4a3c012 --- /dev/null +++ b/tests/root/pre-depends/var/lib/dpkg/status @@ -0,0 +1,28 @@ +Package: test-pre-depends +Status: install ok installed +Priority: optional +Installed-Size: 1234 +Maintainer: Siemens +Architecture: amd64 +Version: 1.0.0-1 +Depends: depends (>= 1.2.3) +Pre-Depends: pre-depends (>= 2.3.4) +Description: Test Package for Pre-Depends + +Package: pre-depends +Status: install ok installed +Priority: optional +Installed-Size: 1234 +Maintainer: Siemens +Architecture: amd64 +Version: 2.3.4 +Description: Pre-Depends Package + +Package: depends +Status: install ok installed +Priority: optional +Installed-Size: 1234 +Maintainer: Siemens +Architecture: amd64 +Version: 1.2.3 +Description: Depends Package diff --git a/tests/test_generation.py b/tests/test_generation.py index 933d06f..59ca656 100644 --- a/tests/test_generation.py +++ b/tests/test_generation.py @@ -497,3 +497,37 @@ def test_virtual_package(tmpdir, sbom_generator): found = True break assert found + + +def test_pre_depends(tmpdir, sbom_generator): + _spdx_tools = pytest.importorskip("spdx_tools") + _cyclonedx = pytest.importorskip("cyclonedx") + + dbom = sbom_generator("tests/root/pre-depends") + outdir = Path(tmpdir) + dbom.generate(str(outdir / "sbom"), validate=True) + with open(outdir / "sbom.spdx.json") as file: + spdx_json = json.loads(file.read()) + relationships = spdx_json["relationships"] + assert { + "spdxElementId": "SPDXRef-test-pre-depends-amd64", + "relatedSpdxElement": "SPDXRef-pre-depends-amd64", + "relationshipType": "DEPENDS_ON", + } in relationships + assert { + "spdxElementId": "SPDXRef-test-pre-depends-amd64", + "relatedSpdxElement": "SPDXRef-depends-amd64", + "relationshipType": "DEPENDS_ON", + } in relationships + + with open(outdir / "sbom.cdx.json") as file: + spdx_json = json.loads(file.read()) + dependencies = spdx_json["dependencies"] + assert { + "dependsOn": [ + "pkg:deb/debian/depends@1.2.3?arch=amd64", + "pkg:deb/debian/pre-depends@2.3.4?arch=amd64", + "pkg:deb/debian/test-pre-depends@1.0.0-1?arch=source", + ], + "ref": "pkg:deb/debian/test-pre-depends@1.0.0-1?arch=amd64", + } in dependencies