Bug 15076 - Make recursive task handling more flexible
Summary: Make recursive task handling more flexible
Status: NEW
Alias: None
Product: BitBake
Classification: Build System, Metadata & Runtime
Component: bitbake (show other bugs)
Version: unspecified
Hardware: x86 Multiple
: Medium enhancement
Target Milestone: 6.2
Assignee: Richard Purdie
QA Contact:
URL:
Whiteboard:
Depends on:
Blocks:
 
Reported: 2023-03-21 11:13 UTC by Gauthier HADERER
Modified: 2026-10-01 15:48 UTC (History)
4 users (show)

See Also:
OS type for building Yocto: ---
Type of Regression: ---
Verified:
Documentation change: Yes (doc changes required)


Attachments
Add the recxdeptask task flag (11.82 KB, patch)
2023-03-21 11:13 UTC, Gauthier HADERER
no flags Details | Diff
log.do_rootfs (336.21 KB, text/plain)
2023-11-29 10:48 UTC, Piotr Łobacz
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Gauthier HADERER 2023-03-21 11:13:12 UTC
Created attachment 4938 [details]
Add the recxdeptask task flag

Hi,

I would like to propose an enhancement on how recursive tasks are handled. The recrdeptask task flag considers all the recipes the task depend on, including the recursive task's own recipe. That's fine as long as the listed tasks don't depend on the recursive task. But I introduced a new kind of recipe which is a mix between a regular recipe and an image recipe. Its goal is to generate a fully operational lxc container, typically split in two packages, one containing the rootfs, the configuration file and hook scripts, the other one containing init manager files.

I'm aware this could be solved by creating a separate image recipe which creates the container's rootfs and then injects the resulting image back into a regular recipe. But I find this method inconvenient as this implies to maintain two recipes for each container... and we have a lot of them. As the rootfs's contents may depend on build options, we may have to duplicate them between the two recipes. Besides, we have to pack and unpack the rootfs (typically in a tarball) to transfer it between the two recipes, which leads to increased complexity and poorly readable code, even if it's hidden in a dedicated class. This is why I started to work on a single-recipe solution which is described below.

Container rootfs generation is built on top of image generation tools. I reuse rootfs_${IMAGE_PKGTYPE} classes and oe.rootfs.{create_rootfs,create_manifest} Python functions. This is insulated in a container class which provides a minimalist version of the image class's do_rootfs task. In the install task, I copy the rootfs to its final location, generate the configuration and hook scripts, and adjust file ownerships/permissions. Finally the resulting packages mentioned above are generated as usual.

In our image recipe, we then install the container's packages.

As indicated above, the recrdeptask task flag is not suited for this use case as it creates a circular dependency between the container recipe's do_rootfs and do_package_xxx tasks. The new recxdeptask task flag ("x" stands for external, in opposition to "i" in recideptask) works similar to the recrdeptask except it won't add dependencies on tasks from the recursive task's recipe.

I join the patch I use on my side. I passed all bitbake tests successfully. It works fine but as I've got very limited experience in Yocto/bitbake, I probably have missed something. Notably, I don't know if I must take into consideration dynamic packages when filtering the recursive task's dependencies.

Please let me know if you need more information, or if you have remarks on or objections against my proposal.

Cheers.
Comment 1 Richard Purdie 2023-03-22 17:31:02 UTC
I'm a bit reluctant to start adding more recXdeptask variants as people already struggle with the differences between the two we have.

Effectively what you're doing is creating two images inside one image recipe which isn't something we officially support. I understand the concern about duplication although there could be other ways to avoid that such as BBCLASSEXTEND of the recipe.

Looking at the code, I do have to wonder if we actually need those problematic self references. If we removed them, does that break the current image generation dependencies? 

In image recipes, we already do:

deltask do_package_qa
deltask do_packagedata
deltask do_package_write_ipk
deltask do_package_write_deb
deltask do_package_write_rpm

so I'm guessing the problem might just be the do_deploy dependencies?

I'd be interested to understand which dependencies in the existing code are causing problems and whether we actually need them.
Comment 2 Gauthier HADERER 2023-03-23 11:19:58 UTC
Hi Richard,

Thank you for your feedback. I do understand you're not really keen on adding a new recursive task flag.

Anyway I would like to clarify how we build and use containers, and to give you a broad picture of our environment.
Maybe you will be able to advise me a better solution.

We mainly use containers for security reasons, to insulate critical code which handles secrets from code which needs to interact with the external environment.

Except for the init manager itself, all services run in containers, which means we have many of them.

Each service has a recipe which packages the daemon, its libraries, configuration files... This recipe has nothing special, it's just a regular one.

For each service, we have a container recipe. It uses the service's packages to build the container's rootfs.
In turn, it also generates packages, typically one containing the container's files (rootfs, config...), one with the init manager scripts.
This recipe is also almost a recipe except it inherits our container class which adds the do_rootfs() task and provides a default implementation of do_install().

When the container recipe is executed, the following tasks are executed:
- do_fetch / do_unpack / ... / do_compile: same sequence as a regular recipe
- do_rootfs: builds the rootfs
- do_install: copy the rootfs, create the LXC configuration and scripts, tune file ownerships and permissions 
- do_packagedata / do_package / do_package_qa / do_package_write_xxx: as usual

Here's a simplified version of our container class:

<<<
...
# Directory to generate the container's rootfs into.
CONTAINER_ROOTFS_BUILD ?= "${WORKDIR}/rootfs"

# Packages to install into the container's rootfs.
CONTAINER_INSTALL ?= ""

# Packages to forcibly remove from the container's rootfs after all the
# packages in ${CONTAINER_INSTALL} have been installed and post install scripts
# have been executed.
CONTAINER_UNNEEDED ?= "base-files base-passwd libcap-bin shadow \
    shadow-securetty update-rc.d ${VIRTUAL-RUNTIME_update-alternatives}"
...
inherit ... image-postinst-intercepts rootfs_${IMAGE_PKGTYPE}
...
# Disable conflicting packaging operations.
EXCLUDE_FROM_SHLIBS = "1"
INHIBIT_PACKAGE_DEBUG_SPLIT = "1"
INHIBIT_PACKAGE_STRIP = "1"
PACKAGE_NO_LOCALE = "1"
...
fakeroot python do_rootfs() {
    from oe.rootfs import create_rootfs
    from oe.manifest import create_manifest

    localdata = d.createCopy()

    logger = localdata.getVar("BB_TASK_LOGGER", False)
    if logger:
        logcatcher = bb.utils.LogCatcher()
        logger.addHandler(logcatcher)
    else:
        logcatcher = None

    localdata.setVar("FORCE_RO_REMOVE", "1")
    localdata.setVar("IMAGE_GEN_DEBUGFS", "0")
    localdata.setVar("LINGUAS_INSTALL", "")
    localdata.setVar("NO_RECOMMENDATIONS", "1")
    localdata.setVar("PACKAGE_INSTALL", d.getVar("CONTAINER_INSTALL"))
    localdata.setVar("PACKAGE_INSTALL_ATTEMPTONLY", "")
    localdata.setVar("ROOTFS_RO_UNNEEDED", d.getVar("CONTAINER_UNNEEDED"))

    localdata.setVar("D", d.getVar("CONTAINER_ROOTFS_BUILD"))
    localdata.setVar("IMAGE_ROOTFS", d.getVar("CONTAINER_ROOTFS_BUILD"))

    localdata.setVar("DISTRO_FEATURES", "")

    localdata.setVar("IMAGE_FEATURES", "read-only-rootfs")

    localdata.setVar("ROOTFS_PREPROCESS_COMMAND", "")
    localdata.setVar("ROOTFS_POSTINSTALL_COMMAND", "")
    localdata.setVar("ROOTFS_POSTPROCESS_COMMAND", "")
    localdata.setVar("ROOTFS_POSTUNINSTALL_COMMAND",
                     "container_check_installed_packages")

    localdata.setVar("DEB_PREPROCESS_COMMANDS", "")
    localdata.setVar("DEB_POSTPROCESS_COMMANDS", "")

    localdata.setVar("OPKG_PREPROCESS_COMMANDS", "")
    localdata.setVar("OPKG_POSTPROCESS_COMMANDS", "")

    localdata.setVar("RPM_PREPROCESS_COMMANDS", "")
    localdata.setVar("RPM_POSTPROCESS_COMMANDS", "")

    localdata.setVar("IMAGE_DEVICE_TABLE", "")
    localdata.setVar("IMAGE_DEVICE_TABLES", "")
    localdata.setVar("USE_DEVFS", "0")

    localdata.setVar("USE_DEPMOD", "0")

    localdata.setVar("LDCONFIGDEPEND", "")

    create_manifest(localdata)

    create_rootfs(localdata, logcatcher=logcatcher)
}

do_rootfs[dirs] = "${TOPDIR}"
do_rootfs[cleandirs] = "${CONTAINER_ROOTFS_BUILD}"
do_rootfs[vardeps] += "container_check_installed_packages"
do_rootfs[file-checksums] += "${POSTINST_INTERCEPT_CHECKSUMS}"
do_rootfs[depends] += "\
    makedevs-native:do_populate_sysroot \
    virtual/fakeroot-native:do_populate_sysroot"
do_rootfs[rdepends] += "${@' '.join(['%s:do_package_write_${IMAGE_PKGTYPE}' \
    % name for name in (d.getVar('CONTAINER_INSTALL') or '').split()])}"
do_rootfs[recxdeptask] := "${@d.getVarFlag('do_rootfs', 'recrdeptask')}"
unset do_rootfs[recrdeptask]
>>>

In our image recipe, we install the container's packages in the usual way.

Let me know if my description is clear and if you can envisage other solutions to solve our container rootfs build issues.

Cheers
Comment 3 Richard Purdie 2023-03-23 23:37:43 UTC
We split package generation and image generation into distinct separate recipes, primarily because the dependency needs are quite different. Here, you're trying to merge them back together and than hack bitbake to make it work. We specifically avoided doing this to try and keep things simpler as the complexity in bitbake is already bad enough.

As an example, think about what happens with licensing when you do this. It is far from clear which license code should be running, where, or what it should handle the licenses for. I appreciate this probably isn't a concern for you, but any code we accept would need to handle a lot more corner cases.

I suspect that you can do what you're looking at with BBCLASSEXTEND and making the single recipe have two different variants, one which generates the packages and the other which generates the image from the packages.

For an example of what you can do with BBCLASSEXTEND, have a look at the mcextend.bbclass in core. native.bbclass, nativesdk.bbclass and others are more complex versions of that, mcextend is simpler. In your case you could simply just have two classes, you don't need a lot of the templating or renaming those core classes do.

I am curious if we could tweak the behaviour of the existing rrecdeptask code without adverse effects. Looking at your patches, I think I can see a hint it might be possible but it is hard to say without some experiments and analysis. I suspect the problem with image.bbclass as it stands is the do_populate_lic task which is present in both images and recipes.
Comment 4 Gauthier HADERER 2023-04-11 10:23:58 UTC
Hi Richard,

is there anything I can do to make this ticket move forward or should I just wait?

If you do think this change is not something your project wants to integrate, feel free to let me know. I understand our use case may not be something you want to focus on.

We are aware our implementation of container generation is just a first step to a well-integrated solution. We know we have issues we must deal with such as licenses as you pointed out. But we think it may be of interest to other users.
Comment 5 Richard Purdie 2023-04-11 14:51:00 UTC
(In reply to Gauthier HADERER from comment #4)
> is there anything I can do to make this ticket move forward or should I just
> wait?

I think I was wondering if you had any thoughts on the things I mentioned in my reply. Could we change the existing behaviour without too much impact to work for both use cases?

My feeling is something may be possible but someone would need to do some analysis to work out if that is the case. If you are able that would help move it forward. It may be a while before I find any time to look at this myself.

> If you do think this change is not something your project wants to
> integrate, feel free to let me know. I understand our use case may not be
> something you want to focus on.
> 
> We are aware our implementation of container generation is just a first step
> to a well-integrated solution. We know we have issues we must deal with such
> as licenses as you pointed out. But we think it may be of interest to other
> users.

I'm reluctant to take something which only solves part of the problem, particularly when I don't think the license piece will be solvable with the approach you're taking. Once we add functionality like this, it is near impossible to remove so we need to be sure it will work and I'm not convinced of that as yet.
Comment 6 Piotr Łobacz 2023-11-29 10:48:04 UTC
Created attachment 4992 [details]
log.do_rootfs

This file contains the error for do_rootfs
Comment 7 Piotr Łobacz 2023-11-29 10:49:06 UTC
(In reply to Piotr Łobacz from comment #6)
> Created attachment 4992 [details]
> log.do_rootfs
> 
> This file contains the error for do_rootfs

I'm sorry this was not my intention to put this file in here but in other bug... Can someone delete it?