Hi,

On Tue, Jan 13, 2026 at 03:43 PM, Adam Duskett wrote:
Add the ability to define a semicolon-delminated list of directories
to create on the extra partition.
 
I think using a semicolon to separate items in IMAGE_EXTRA_PARTITION_DIRECTORIES could be confusing, as it’s already used for tuples in the IMAGE_EXTRA_PARTITION_FILES variable. A space-delimited list might be more intuitive and consistent with how IMAGE_EXTRA_PARTITION_FILES is structured. For example:
 
IMAGE_EXTRA_PARTITION_DIRECTORIES_label-foo = "/test1 /test2/test3 /test4"
IMAGE_EXTRA_PARTITION_FILES_label-foo = "bar1.conf;foo1.conf bar2.conf bar3;foo3"
 
What do you think ?
 
Signed-off-by: Adam Duskett <adam.duskett@amarulasolutions.com>
---
meta/lib/oeqa/selftest/cases/wic.py | 11 ++++++
.../lib/wic/plugins/source/extra_partition.py | 38 +++++++++++++++++++
2 files changed, 49 insertions(+)

diff --git a/meta/lib/oeqa/selftest/cases/wic.py b/meta/lib/oeqa/selftest/cases/wic.py
index 33a6460677..d365345554 100644
--- a/meta/lib/oeqa/selftest/cases/wic.py
+++ b/meta/lib/oeqa/selftest/cases/wic.py
@@ -1657,10 +1657,14 @@ INITRAMFS_IMAGE = "core-image-initramfs-boot"
def test_extra_partition_plugin(self):
"""Test extra partition plugin"""
config = dedent("""\
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_label-foo = "/test1;/test2/test3"
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d = "/test1;/test2/test3"
IMAGE_EXTRA_PARTITION_FILES_label-foo = "bar.conf;foo.conf"
IMAGE_EXTRA_PARTITION_FILES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d = "bar.conf;foobar.conf"
IMAGE_EXTRA_PARTITION_FILES = "foo/*"
WICVARS:append = "\
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_label-foo \
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d \
IMAGE_EXTRA_PARTITION_FILES_label-foo \
IMAGE_EXTRA_PARTITION_FILES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d \
"
@@ -1692,6 +1696,13 @@ INITRAMFS_IMAGE = "core-image-initramfs-boot"
result = runCmd("wic ls %s | wc -l" % wicimg)
self.assertEqual('4', result.output, msg="Expect 3 partitions, not %s" % result.output)

+ for part, extra_dir in enumerate(["test1", "test2"]):
+ result = runCmd("wic ls %s:%d | grep -q \"%s\"" % (wicimg, part + 1, extra_dir))
+ self.assertEqual(0, result.status, msg="Directory '%s' not found in the partition #%d" % (extra_dir, part))
+
+ result = runCmd("wic ls %s:%d/test2 | grep -q \"test3\"" % (wicimg, part + 1))
+ self.assertEqual(0, result.status, msg="Directory test2/test3 not found in the partition #%d" % part)
+
for part, file in enumerate(["foo.conf", "foobar.conf", "bar.conf"]):
result = runCmd("wic ls %s:%d | grep -q \"%s\"" % (wicimg, part + 1, file))
self.assertEqual(0, result.status, msg="File '%s' not found in the partition #%d" % (file, part))
diff --git a/scripts/lib/wic/plugins/source/extra_partition.py b/scripts/lib/wic/plugins/source/extra_partition.py
index 3abd2bfd4c..fee72df615 100644
--- a/scripts/lib/wic/plugins/source/extra_partition.py
+++ b/scripts/lib/wic/plugins/source/extra_partition.py
@@ -20,13 +20,19 @@ class ExtraPartitionPlugin(SourcePlugin):
- File renaming.
- Suffixes to specify the target partition (by label, UUID, or partname),
enabling multiple extra partitions to coexist.
+ - Extra directories.

For example:

+ IMAGE_EXTRA_PARTITION_DIRECTORIES_label-foo = "/foo;/bar/baz"
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d = "/foo;/bar/baz"
+
IMAGE_EXTRA_PARTITION_FILES_label-foo = "bar.conf;foo.conf"
IMAGE_EXTRA_PARTITION_FILES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d = "bar.conf;foobar.conf"
IMAGE_EXTRA_PARTITION_FILES = "foo/*"
WICVARS:append = "\
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_label-foo \
+ IMAGE_EXTRA_PARTITION_DIRECTORIES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d \
IMAGE_EXTRA_PARTITION_FILES_label-foo \
IMAGE_EXTRA_PARTITION_FILES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d \
"
@@ -35,6 +41,7 @@ class ExtraPartitionPlugin(SourcePlugin):

name = 'extra_partition'
image_extra_partition_files_var_name = 'IMAGE_EXTRA_PARTITION_FILES'
+ image_extra_partition_dirs_var_name = 'IMAGE_EXTRA_PARTITION_DIRECTORIES'

@classmethod
def _get_extra_vars(cls, part, var_name):
@@ -50,6 +57,30 @@ class ExtraPartitionPlugin(SourcePlugin):
break
return extra_vars

+
+ @classmethod
+ def _parse_extra_directories(cls, part):
+ """
+ Parse the directories of which to copy.
+ """
+ cls.extra_dirs_task = []
+
+ extra_dirs = cls._get_extra_vars(part, cls.image_extra_partition_dirs_var_name)
+ if extra_dirs is None:
+ logger.debug('No extra directories defined, %s unset for entry #%d' % (cls.image_extra_partition_dirs_var_name, part.lineno))
+ return
+
+ logger.info('Extra dirs: %s', extra_dirs)
+ for src_entry in re.findall(r'[\w;\-./*]+', extra_dirs):
+ if ';' in src_entry:
+ dst_entries = src_entry.split(';')
+ if not dst_entries[0] or not dst_entries[1]:
+ raise WicError('Malformed extra dir entry: %s' % src_entry)
 
This raises an error for "foo;", but not for "foo;bar;".
With a space-delimited list, the src_entry can be directly added to the extra_dirs_task list.

+ for dst_entry in dst_entries:
+ cls.extra_dirs_task.append(dst_entry)
+ else:
+ cls.extra_dirs_task.append(src_entry)
+
@classmethod
def _parse_extra_files(cls, part, kernel_dir):
"""
@@ -114,6 +145,7 @@ class ExtraPartitionPlugin(SourcePlugin):
if not kernel_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")

+ cls._parse_extra_directories(part)
cls._parse_extra_files(part, kernel_dir)

@classmethod
@@ -132,6 +164,12 @@ class ExtraPartitionPlugin(SourcePlugin):
if not kernel_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")

+ if cls.extra_dirs_task:
+ for task in cls.extra_dirs_task:
+ mkdir_cmd = "mkdir -p %s/%s" % (extradir, task)
+ logger.debug(mkdir_cmd)
+ exec_cmd(mkdir_cmd)
+
if cls.extra_files_task:
for task in cls.extra_files_task:
src_path, dst_path = task
Regarding your patch 5/6, the plugin should indeed be able to create extra directories without requiring extra files. For example to prepare a data partition with only directories.
However, since extra partition plugin was not designed to create empty partitions (omitting --source achieve this), I think the plugin should raise an error if no files or directories are defined.
 
Pierre-Loup,