Sorry, I forgot to copy Joshua...
Peter

> -----Original Message-----
> From: Marko, Peter (FT D EU SK BFS1) <[email protected]>
> Sent: Friday, June 5, 2026 2:05 PM
> To: [email protected]
> Cc: Marko, Peter (FT D EU SK BFS1) <[email protected]>
> Subject: [PATCH 2/2] rootfs,spdx: handle removed packages
> 
> From: Peter Marko <[email protected]>
> 
> SPDX should not list packages which were removed from rootfs as installed.
> 
> The list of installed packages does not contain them directly, but as
> dependencies of other installed packages.
> 
> Siwtch them to "other" to keep them in SPDX as part of the build and
> installation process.
> 
> Signed-off-by: Peter Marko <[email protected]>
> ---
>  meta/classes-recipe/create-spdx-image-3.0.bbclass |  7 +++++++
>  meta/lib/oe/rootfs.py                             |  6 ++++++
>  meta/lib/oe/sbom30.py                             |  8 +++++++-
>  meta/lib/oe/spdx30_tasks.py                       | 14 +++++++++++++-
>  4 files changed, 33 insertions(+), 2 deletions(-)
> 
> diff --git a/meta/classes-recipe/create-spdx-image-3.0.bbclass b/meta/classes-
> recipe/create-spdx-image-3.0.bbclass
> index 15a91e90e2..dfbd2961b3 100644
> --- a/meta/classes-recipe/create-spdx-image-3.0.bbclass
> +++ b/meta/classes-recipe/create-spdx-image-3.0.bbclass
> @@ -6,6 +6,7 @@
>  # SPDX image tasks
> 
>  SPDX_ROOTFS_PACKAGES = "${SPDXDIR}/rootfs-packages.json"
> +SPDX_ROOTFS_REMOVED_PACKAGES = "${SPDXDIR}/rootfs-removed-
> packages.json"
>  SPDXIMAGEDEPLOYDIR = "${SPDXDIR}/image-deploy"
>  SPDXROOTFSDEPLOY = "${SPDXDIR}/rootfs-deploy"
> 
> @@ -15,14 +16,20 @@ python spdx_collect_rootfs_packages() {
>      from oe.rootfs import image_list_installed_packages
> 
>      root_packages_file = Path(d.getVar("SPDX_ROOTFS_PACKAGES"))
> +    root_removed_packages_file =
> Path(d.getVar("SPDX_ROOTFS_REMOVED_PACKAGES"))
> 
>      packages = image_list_installed_packages(d)
>      if not packages:
>          packages = {}
> 
> +    removed_packages = (d.getVar("ROOTFS_REMOVED_PACKAGES") or
> "").split()
> +
>      root_packages_file.parent.mkdir(parents=True, exist_ok=True)
>      with root_packages_file.open("w") as f:
>          json.dump(packages, f)
> +
> +    with root_removed_packages_file.open("w") as f:
> +        json.dump(removed_packages, f)
>  }
>  ROOTFS_POSTUNINSTALL_COMMAND =+ "spdx_collect_rootfs_packages"
> 
> diff --git a/meta/lib/oe/rootfs.py b/meta/lib/oe/rootfs.py
> index 5eee48f587..b8830596ed 100644
> --- a/meta/lib/oe/rootfs.py
> +++ b/meta/lib/oe/rootfs.py
> @@ -261,10 +261,13 @@ class Rootfs(object, metaclass=ABCMeta):
> 
> 
>      def _uninstall_unneeded(self):
> +        removed_pkgs = set()
> +
>          # Remove the run-postinsts package if no delayed postinsts are found
>          delayed_postinsts = self._get_delayed_postinsts()
>          if delayed_postinsts is None:
>              if 
> os.path.exists(self.d.expand("${IMAGE_ROOTFS}${sysconfdir}/init.d/run-
> postinsts")) or
> os.path.exists(self.d.expand("${IMAGE_ROOTFS}${systemd_system_unitdir}/run-
> postinsts.service")):
> +                removed_pkgs.add("run-postinsts")
>                  self.pm.remove(["run-postinsts"])
> 
>          image_rorfs = bb.utils.contains("IMAGE_FEATURES", "read-only-rootfs",
> @@ -285,6 +288,7 @@ class Rootfs(object, metaclass=ABCMeta):
>              # to be uninstalled or to be managed correctly otherwise.
>              provider = self.d.getVar("VIRTUAL-RUNTIME_update-alternatives")
>              pkgs_to_remove = sorted([pkg for pkg in pkgs_installed if pkg in
> unneeded_pkgs], key=lambda x: x == provider)
> +            removed_pkgs.update(pkgs_to_remove)
> 
>              # update-alternatives provider is removed in its own remove()
>              # call because all package managers do not guarantee the packages
> @@ -296,6 +300,8 @@ class Rootfs(object, metaclass=ABCMeta):
>              if len(pkgs_to_remove) > 0:
>                  self.pm.remove([pkgs_to_remove[-1]], False)
> 
> +        self.d.setVar("ROOTFS_REMOVED_PACKAGES", "
> ".join(sorted(removed_pkgs)))
> +
>          if delayed_postinsts:
>              self._save_postinsts()
>              if image_rorfs:
> diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py
> index b379ff947c..4fa32266fa 100644
> --- a/meta/lib/oe/sbom30.py
> +++ b/meta/lib/oe/sbom30.py
> @@ -1122,7 +1122,7 @@ def find_by_spdxid(d, spdxid, *, required=False):
>      return find_jsonld(d, *jsonld_hash_path(hash_id(spdxid)), 
> required=required)
> 
> 
> -def create_sbom(d, name, root_elements, add_objectsets=[]):
> +def create_sbom(d, name, root_elements, add_objectsets=[],
> removed_packages=[]):
>      objset = ObjectSet.new_objset(d, name)
> 
>      sbom = objset.add(
> @@ -1142,6 +1142,12 @@ def create_sbom(d, name, root_elements,
> add_objectsets=[]):
>              + "\n  ".join(sorted(list(missing_spdxids)))
>          )
> 
> +    if removed_packages:
> +        for pkg in objset.foreach_type(oe.spdx30.software_Package):
> +            if pkg.name in removed_packages and pkg.software_primaryPurpose 
> ==
> oe.spdx30.software_SoftwarePurpose.install:
> +                pkg.software_primaryPurpose =
> oe.spdx30.software_SoftwarePurpose.other
> +                bb.note("Reclassified removed package %s SPDX entry from 
> install to
> other" % pkg.name)
> +
>      # Filter out internal extensions from final SBoMs
>      objset.remove_internal_extensions()
> 
> diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
> index 7cc46d579b..18c68f47de 100644
> --- a/meta/lib/oe/spdx30_tasks.py
> +++ b/meta/lib/oe/spdx30_tasks.py
> @@ -1532,6 +1532,7 @@ def create_image_sbom_spdx(d):
>      image_link_name = d.getVar("IMAGE_LINK_NAME")
>      imgdeploydir = Path(d.getVar("SPDXIMAGEDEPLOYDIR"))
>      machine = d.getVar("MACHINE")
> +    root_removed_packages_file =
> Path(d.getVar("SPDX_ROOTFS_REMOVED_PACKAGES"))
> 
>      spdx_path = imgdeploydir / (image_name + ".spdx.json")
> 
> @@ -1553,7 +1554,18 @@ def create_image_sbom_spdx(d):
>      for o in image_objset.foreach_root(oe.spdx30.software_File):
>          root_elements.append(oe.sbom30.get_element_link_id(o))
> 
> -    objset, sbom = oe.sbom30.create_sbom(d, image_name, root_elements)
> +    try:
> +        with root_removed_packages_file.open("r") as f:
> +            removed_packages = json.load(f)
> +    except FileNotFoundError:
> +        removed_packages = []
> +
> +    objset, sbom = oe.sbom30.create_sbom(
> +        d,
> +        image_name,
> +        root_elements,
> +        removed_packages=removed_packages,
> +    )
> 
>      # Set supplier on root elements if SPDX_IMAGE_SUPPLIER is defined
>      supplier = objset.new_agent("SPDX_IMAGE_SUPPLIER", add=False)
-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#238167): 
https://lists.openembedded.org/g/openembedded-core/message/238167
Mute This Topic: https://lists.openembedded.org/mt/119660891/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to