| Message ID | 20260605120456.130264-2-peter.marko@siemens.com |
|---|---|
| State | Under Review |
| Headers | show |
| Series | [1/2] rootfs: move tasks using image_list_installed_packages to postuninstall | expand |
Sorry, I forgot to copy Joshua... Peter > -----Original Message----- > From: Marko, Peter (FT D EU SK BFS1) <Peter.Marko@siemens.com> > Sent: Friday, June 5, 2026 2:05 PM > To: openembedded-core@lists.openembedded.org > Cc: Marko, Peter (FT D EU SK BFS1) <Peter.Marko@siemens.com> > Subject: [PATCH 2/2] rootfs,spdx: handle removed packages > > From: Peter Marko <peter.marko@siemens.com> > > 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 <peter.marko@siemens.com> > --- > 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)
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)