set the package-database of a "lower image" to unpack and build upon
when installing packages for the current image. This way a lean image
will be created, which only holds the packages that are not already
present in the lower image.

An image build such could then be used with overlayfs or systemd-
sysext to extend the "lower image" on demand; for development purposes
on a device running the "lower image" in RO mode for example.

A configuration could look as follows:
  some-core-image.bb
    inherit image
    IMAGE_GEN_PKGDBFS = "1"

  extending-image.bb
    inherit image
    IMAGE_BASE_PKGDB = "some-core-image"

Signed-off-by: Johannes Schneider <johannes.schnei...@leica-geosystems.com>
---
 meta/classes-recipe/image.bbclass             | 23 ++++++++--
 meta/conf/documentation.conf                  |  3 +-
 meta/lib/oe/package_manager/deb/rootfs.py     |  2 +
 meta/lib/oe/package_manager/ipk/rootfs.py     |  6 ++-
 meta/lib/oe/package_manager/rpm/rootfs.py     |  7 ++-
 meta/lib/oe/rootfs.py                         | 18 ++++++++
 meta/lib/oeqa/selftest/cases/imagefeatures.py | 46 +++++++++++++++++++
 7 files changed, 97 insertions(+), 8 deletions(-)

diff --git a/meta/classes-recipe/image.bbclass 
b/meta/classes-recipe/image.bbclass
index 3ccaaa17b8..c573c37cd8 100644
--- a/meta/classes-recipe/image.bbclass
+++ b/meta/classes-recipe/image.bbclass
@@ -42,8 +42,16 @@ IMAGE_FEATURES ?= ""
 IMAGE_FEATURES[type] = "list"
 IMAGE_FEATURES[validitems] += "debug-tweaks read-only-rootfs 
read-only-rootfs-delayed-postinsts stateless-rootfs empty-root-password 
allow-empty-password allow-root-login serial-autologin-root 
post-install-logging overlayfs-etc"
 
-# Generate snapshot of the package database?
+# Image layering:
+# a "base image" would create a snapshot of the package-database after the
+# installation of all packages into the rootfs is done. The next/other image
+# "layered on-top" of the former would then import that database and install
+# further packages; without reinstalling packages/dependencies that are already
+# installed in the layer below.
+# Set to '1' in a "base image" recipe, to preserve a snapshot of the package 
database.
 IMAGE_GEN_PKGDBFS ?= "0"
+# "PN" of a "base image", upon which the current image is to be built upon.
+IMAGE_BASE_PKGDB ?= ""
 
 # Generate companion debugfs?
 IMAGE_GEN_DEBUGFS ?= "0"
@@ -118,6 +126,15 @@ do_rootfs[depends] += " \
 "
 do_rootfs[recrdeptask] += "do_packagedata"
 
+python () {
+    # make sure that the 'base image' has been queued in before this
+    # image wants to unpack and build upon the formers pgkdb
+    base_image = d.getVar('IMAGE_BASE_PKGDB')
+    pn = d.getVar('PN')
+    if base_image and base_image != pn:
+        d.appendVarFlag("do_rootfs", 'depends', ' '+ base_image + 
':do_image_complete')
+}
+
 def rootfs_command_variables(d):
     return 
['ROOTFS_POSTPROCESS_COMMAND','ROOTFS_PREPROCESS_COMMAND','ROOTFS_POSTINSTALL_COMMAND','ROOTFS_POSTUNINSTALL_COMMAND','OPKG_PREPROCESS_COMMANDS','OPKG_POSTPROCESS_COMMANDS','IMAGE_POSTPROCESS_COMMAND',
             
'IMAGE_PREPROCESS_COMMAND','RPM_PREPROCESS_COMMANDS','RPM_POSTPROCESS_COMMANDS','DEB_PREPROCESS_COMMANDS','DEB_POSTPROCESS_COMMANDS']
@@ -134,8 +151,8 @@ def rootfs_variables(d):
                  
'IMAGE_ROOTFS_MAXSIZE','IMAGE_NAME','IMAGE_LINK_NAME','IMAGE_MANIFEST','DEPLOY_DIR_IMAGE','IMAGE_FSTYPES','IMAGE_INSTALL_COMPLEMENTARY','IMAGE_LINGUAS',
 'IMAGE_LINGUAS_COMPLEMENTARY', 'IMAGE_LOCALES_ARCHIVE',
                  
'MULTILIBRE_ALLOW_REP','MULTILIB_TEMP_ROOTFS','MULTILIB_VARIANTS','MULTILIBS','ALL_MULTILIB_PACKAGE_ARCHS','MULTILIB_GLOBAL_VARIANTS','BAD_RECOMMENDATIONS','NO_RECOMMENDATIONS',
                  
'PACKAGE_ARCHS','PACKAGE_CLASSES','TARGET_VENDOR','TARGET_ARCH','TARGET_OS','OVERRIDES','BBEXTENDVARIANT','FEED_DEPLOYDIR_BASE_URI','INTERCEPT_DIR','USE_DEVFS',
-                 'CONVERSIONTYPES', 'IMAGE_GEN_PKGDBFS', 'IMAGE_GEN_DEBUGFS', 
'ROOTFS_RO_UNNEEDED', 'IMGDEPLOYDIR', 'PACKAGE_EXCLUDE_COMPLEMENTARY', 
'REPRODUCIBLE_TIMESTAMP_ROOTFS',
-                 'IMAGE_INSTALL_DEBUGFS']
+                 'CONVERSIONTYPES', 'IMAGE_GEN_PKGDBFS', 'IMAGE_BASE_PKGDB', 
'IMAGE_GEN_DEBUGFS', 'ROOTFS_RO_UNNEEDED', 'IMGDEPLOYDIR', 
'PACKAGE_EXCLUDE_COMPLEMENTARY',
+                 'REPRODUCIBLE_TIMESTAMP_ROOTFS', 'IMAGE_INSTALL_DEBUGFS']
     variables.extend(rootfs_command_variables(d))
     variables.extend(variable_depends(d))
     return " ".join(variables)
diff --git a/meta/conf/documentation.conf b/meta/conf/documentation.conf
index 7ec6f07dfc..9013556c04 100644
--- a/meta/conf/documentation.conf
+++ b/meta/conf/documentation.conf
@@ -208,6 +208,7 @@ ICECC_PATH[doc] = "The location of the icecc binary."
 ICECC_CLASS_DISABLE[doc] = "Identifies user classes that you do not want the 
Icecream distributed compile support to consider."
 ICECC_RECIPE_DISABLE[doc] = "Identifies user recipes that you do not want the 
Icecream distributed compile support to consider."
 ICECC_RECIPE_ENABLE[doc] = "Identifies user recipes that use an empty 
PARALLEL_MAKE variable that you want to force remote distributed compilation on 
using the Icecream distributed compile support."
+IMAGE_BASE_PKGDB[doc] = "Set to the PN of a base-image name, which was built 
with IMAGE_GEN_PKGDBFS enabled; Then this image will only add/install packages 
into its rootfs that are not already in the base-image."
 IMAGE_BASENAME[doc] = "The base name of image output files."
 IMAGE_BOOT_FILES[doc] = "Whitespace separated list of files from 
${DEPLOY_DIR_IMAGE} to place in boot partition. Entries will be installed under 
a same name as the source file. To change the destination file name, pass a 
desired name after a semicolon (eg. u-boot.img;uboot)."
 IMAGE_CLASSES[doc] = "A list of classes that all images should inherit."
@@ -215,7 +216,7 @@ IMAGE_FEATURES[doc] = "The primary list of features to 
include in an image. Conf
 IMAGE_FSTYPES[doc] = "Formats of root filesystem images that you want to have 
created."
 IMAGE_FSTYPES_DEBUGFS[doc] = "Formats of the debug root filesystem images that 
you want to have created."
 IMAGE_GEN_DEBUGFS[doc] = "When set to '1', generate a companion debug 
object/source filesystem image."
-IMAGE_GEN_PKGDBFS[doc] = "When set to '1', create a snapshot of the 
package-manager state after the rootfs has been assembled."
+IMAGE_GEN_PKGDBFS[doc] = "When set to '1', create a snapshot of the 
package-manager state after the rootfs has been assembled; This snapshot can 
then be used in a different image recipe, that sets IMAGE_BASE_PKGDB to this 
images PN."
 IMAGE_INSTALL[doc] = "Specifies the packages to install into an image. Image 
recipes set IMAGE_INSTALL to specify the packages to install into an image 
through image.bbclass."
 IMAGE_LINGUAS[doc] = "Specifies the list of locales to install into the image 
during the root filesystem construction process."
 IMAGE_NAME[doc] = "The name of the output image files minus the extension."
diff --git a/meta/lib/oe/package_manager/deb/rootfs.py 
b/meta/lib/oe/package_manager/deb/rootfs.py
index 43107c8663..71a21df09b 100644
--- a/meta/lib/oe/package_manager/deb/rootfs.py
+++ b/meta/lib/oe/package_manager/deb/rootfs.py
@@ -152,6 +152,8 @@ class PkgRootfs(DpkgOpkgRootfs):
 
         execute_pre_post_process(self.d, deb_pre_process_cmds)
 
+        self._unpack_pkg_db_rootfs(['/var/lib/dpkg'])
+
         if self.progress_reporter:
             self.progress_reporter.next_stage()
             # Don't support incremental, so skip that
diff --git a/meta/lib/oe/package_manager/ipk/rootfs.py 
b/meta/lib/oe/package_manager/ipk/rootfs.py
index 64d9bc7969..408faa8030 100644
--- a/meta/lib/oe/package_manager/ipk/rootfs.py
+++ b/meta/lib/oe/package_manager/ipk/rootfs.py
@@ -276,12 +276,16 @@ class PkgRootfs(DpkgOpkgRootfs):
         pkgs_to_install = self.manifest.parse_initial_manifest()
         opkg_pre_process_cmds = self.d.getVar('OPKG_PREPROCESS_COMMANDS')
         opkg_post_process_cmds = self.d.getVar('OPKG_POSTPROCESS_COMMANDS')
+        opkg_lib_dir = self.d.getVar('OPKGLIBDIR')
+        opkg_dir = os.path.join(opkg_lib_dir, 'opkg')
 
         # update PM index files
         self.pm.write_index()
 
         execute_pre_post_process(self.d, opkg_pre_process_cmds)
 
+        self._unpack_pkg_db_rootfs([opkg_dir])
+
         if self.progress_reporter:
             self.progress_reporter.next_stage()
             # Steps are a bit different in order, skip next
@@ -317,8 +321,6 @@ class PkgRootfs(DpkgOpkgRootfs):
         if self.progress_reporter:
             self.progress_reporter.next_stage()
 
-        opkg_lib_dir = self.d.getVar('OPKGLIBDIR')
-        opkg_dir = os.path.join(opkg_lib_dir, 'opkg')
         self._setup_pkg_db_rootfs([opkg_dir])
         self._setup_dbg_rootfs([opkg_dir])
 
diff --git a/meta/lib/oe/package_manager/rpm/rootfs.py 
b/meta/lib/oe/package_manager/rpm/rootfs.py
index 673006c131..9986b13b5f 100644
--- a/meta/lib/oe/package_manager/rpm/rootfs.py
+++ b/meta/lib/oe/package_manager/rpm/rootfs.py
@@ -67,12 +67,15 @@ class PkgRootfs(Rootfs):
         pkgs_to_install = self.manifest.parse_initial_manifest()
         rpm_pre_process_cmds = self.d.getVar('RPM_PREPROCESS_COMMANDS')
         rpm_post_process_cmds = self.d.getVar('RPM_POSTPROCESS_COMMANDS')
+        package_paths = ['/etc/rpm', '/etc/rpmrc', '/etc/dnf', '/var/lib/rpm', 
'/var/cache/dnf', '/var/lib/dnf']
 
         # update PM index files
         self.pm.write_index()
 
         execute_pre_post_process(self.d, rpm_pre_process_cmds)
 
+        self._unpack_pkg_db_rootfs(package_paths)
+
         if self.progress_reporter:
             self.progress_reporter.next_stage()
 
@@ -110,8 +113,8 @@ class PkgRootfs(Rootfs):
         if self.progress_reporter:
             self.progress_reporter.next_stage()
 
-        self._setup_pkg_db_rootfs(['/etc/rpm', '/etc/rpmrc', '/etc/dnf', 
'/var/lib/rpm', '/var/cache/dnf', '/var/lib/dnf'])
-        self._setup_dbg_rootfs(['/etc/rpm', '/etc/rpmrc', '/etc/dnf', 
'/var/lib/rpm', '/var/cache/dnf', '/var/lib/dnf'])
+        self._setup_pkg_db_rootfs(package_paths)
+        self._setup_dbg_rootfs(package_paths)
 
         execute_pre_post_process(self.d, rpm_post_process_cmds)
 
diff --git a/meta/lib/oe/rootfs.py b/meta/lib/oe/rootfs.py
index 6d6e888a30..9d691c58cd 100644
--- a/meta/lib/oe/rootfs.py
+++ b/meta/lib/oe/rootfs.py
@@ -106,6 +106,24 @@ class Rootfs(object, metaclass=ABCMeta):
     def _cleanup(self):
         pass
 
+    def _unpack_pkg_db_rootfs(self, package_paths):
+        import tarfile
+        pn = self.d.getVar('PN')
+        base_pkgdb = self.d.getVar('IMAGE_BASE_PKGDB')
+        if not base_pkgdb or pn == base_pkgdb:
+            return
+
+        fname = self.d.getVar('DEPLOY_DIR_IMAGE') + '/' + 
self.d.getVar('IMAGE_BASE_PKGDB') + '-' + self.d.getVar('MACHINE')  + 
'.rootfs-pkgdb.tar.gz'
+        if not os.path.exists(fname):
+            bb.fatal("PKGDB for '%s' does not exist,\n\texpected: %s\n\tthe 
recipe for '%s' should set: IMAGE_GEN_PKGDBFS='1'" % (base_pkgdb, fname, 
base_pkgdb))
+            return
+
+        bb.note("  unpacking base image package database...")
+        if fname.endswith("tar.gz"):
+            tar = tarfile.open(fname, "r:gz")
+            tar.extractall(path=self.image_rootfs)
+            tar.close()
+
     def _setup_pkg_db_rootfs(self, package_paths):
         gen_pkg_db_fs = bb.utils.to_boolean(self.d.getVar('IMAGE_GEN_PKGDBFS'))
         if not gen_pkg_db_fs:
diff --git a/meta/lib/oeqa/selftest/cases/imagefeatures.py 
b/meta/lib/oeqa/selftest/cases/imagefeatures.py
index 4d4005e00c..aa5c7c7ccc 100644
--- a/meta/lib/oeqa/selftest/cases/imagefeatures.py
+++ b/meta/lib/oeqa/selftest/cases/imagefeatures.py
@@ -349,6 +349,52 @@ SKIP_RECIPE[busybox] = "Don't build this"
         # check for a common base package, a random library for example:
         self.assertTrue("libc6" in result.output, msg='Failed query installed 
packages')
 
+    def test_image_use_pkgdbfs(self):
+        """
+        Summary:     Check pkgdb snapshot
+        Expected:    1. core-image-minimal is built with IMAGE_GEN_PKGDBFS 
variable set
+                     2. core-image-minimal-mtdutils is built with:
+                        IMAGE_BASE_PKGDB pointing to the former, to build upon 
it
+                     3. the rootfs of both images is checked, and there should 
be no file
+                        from any of the installed packages that shows up in 
both images
+        """
+
+        base_image = 'core-image-minimal'
+        extension_image = 'core-image-minimal-mtdutils'
+
+        features = 'IMAGE_FSTYPES = "tar.bz2"\n'
+        # enable usrmerge so that all relevant pieces are in one place = /usr
+        features += 'DISTRO_FEATURES += "usrmerge"\n'
+        features += 'IMAGE_GEN_PKGDBFS = "1"\n'
+        features += 'IMAGE_BASE_PKGDB = "%s"\n' % base_image
+        self.write_config(features)
+
+        # through task dependencies the base_image should automagically be 
built
+        bitbake(extension_image)
+
+        # collect image content of both images
+        img_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], 
base_image)
+        img_file = os.path.join(img_vars['DEPLOY_DIR_IMAGE'], "%s.%s" % 
(img_vars['IMAGE_LINK_NAME'], 'tar.bz2'))
+        self.logger.debug("base image: %s" % img_file)
+        img_content = runCmd('tar -tf %s' % img_file)
+
+        ext_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], 
extension_image)
+        ext_file = os.path.join(ext_vars['DEPLOY_DIR_IMAGE'], "%s.%s" % 
(ext_vars['IMAGE_LINK_NAME'], 'tar.bz2'))
+        self.logger.debug("extension image: %s" % ext_file)
+        ext_content = runCmd('tar -tf %s' % ext_file)
+
+        # filter out folders and anything outside of /usr
+        img_files = [ x for x in img_content.output.splitlines() if not 
x.endswith('/') and x.startswith('./usr/') ]
+        self.logger.debug("image files:\n%s" % img_files)
+        ext_files = [ x for x in ext_content.output.splitlines() if not 
x.endswith('/') and x.startswith('./usr/') ]
+
+        # check mutual exclusive content listing
+        self.logger.debug("extension files:\n%s" % ext_files)
+        for i in img_files:
+            self.assertTrue(i not in ext_files, "found %s in extension" % i)
+        for e in ext_files:
+            self.assertTrue(e not in img_files, "found %s in base-image" % e)
+
     def test_empty_image(self):
         """Test creation of image with no packages"""
         image = 'test-empty-image'
-- 
2.34.1

-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#196001): 
https://lists.openembedded.org/g/openembedded-core/message/196001
Mute This Topic: https://lists.openembedded.org/mt/104500431/21656
Group Owner: openembedded-core+ow...@lists.openembedded.org
Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub 
[arch...@mail-archive.com]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to