From e82c2f29d1f4d99277cf611873bf20c52917d236 Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 14:59:38 +0200 Subject: [PATCH 1/2] usr-linux: deploy the user space onto p2 as part of bsp-linux deploy `deploy.sh bsp-linux` extracted the rootfs onto p2 but never deployed the user space: bsp-linux:do_deploy depended on rootfs-linux:do_deploy only, so the apps under linux/usr/build/deploy reached the card only through a separate `deploy.sh usr-linux`. - bsp-linux:do_deploy now also depends on usr-linux:do_deploy, which itself runs after rootfs-linux:do_deploy: rootfs first, apps on top. - usr-linux:do_deploy copies with sudo rsync and check=True. p2 is written by rootfs-linux:do_deploy with `sudo cp -a` and is root-owned, so the unprivileged os.system("cp -r") was denied and its exit code ignored: the deploy "succeeded" with no apps on the card. It went unnoticed because nothing in a full deploy called this task. - verdin-imx8mp is skipped like in rootfs-linux:do_deploy (Tezi/HTTP storage, no p2 to mount). - `after do_build` dropped: its stated reason (do_prepare_initrd pulling this task early) no longer holds since do_prepare_initrd depends on rootfs-linux:do_build. A deploy now only copies what the build produced and fails clearly when it is missing, instead of rebuilding usr (and linux through do_unpack) on every deploy. --- .../recipes-bsp/linux/bsp-linux_1.0.bb | 20 ++++--- .../recipes-rootfs/linux/rootfs-linux_1.0.bb | 4 +- .../recipes-usr/linux/usr-linux_1.0.bb | 59 ++++++++++++------- 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb b/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb index 374a739a3..a910c987f 100644 --- a/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb +++ b/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb @@ -170,15 +170,17 @@ addtask do_prepare_initrd before do_itb # Deploy everything # # Deploy is decoupled from the build: it writes the already-built artefacts -# onto the boot media WITHOUT recompiling. It pulls rootfs-linux:do_deploy -# (which extracts the final rootfs.cpio — apps baked in at build time — onto -# p2) and writes the .itb produced by do_itb during `build.sh -a` onto p1 -# (__do_platform_deploy). It does NOT pull do_build / do_itb / usr-linux's -# rootfs.cpio populate — those belong to `build.sh -a`. Workflow: -# edit -> build.sh -> deploy.sh. A deploy with no prior build fails clearly -# (missing rootfs.cpio / .itb) rather than silently rebuilding. - -do_deploy[depends] = "filesystem:do_fs_check rootfs-linux:do_deploy" +# onto the boot media WITHOUT recompiling. It pulls usr-linux:do_deploy, which +# itself runs after rootfs-linux:do_deploy: the rootfs is extracted onto p2 +# first, then the usr apps (linux/usr/build/deploy) are copied on top — so a +# full deploy always carries the user space, whatever rootfs.cpio contains. +# It then writes the .itb produced by do_itb during `build.sh -a` onto p1 +# (__do_platform_deploy). It does NOT pull do_build / do_itb / usr-linux: +# do_build — those belong to `build.sh -a`. Workflow: edit -> build.sh -> +# deploy.sh. A deploy with no prior build fails clearly (missing rootfs.cpio / +# usr build/deploy / .itb) rather than silently rebuilding. + +do_deploy[depends] = "filesystem:do_fs_check rootfs-linux:do_deploy usr-linux:do_deploy" do_deploy[nostamp] = "1" python do_deploy() { diff --git a/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb b/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb index 2c9118a21..46ed5ab86 100644 --- a/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb +++ b/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb @@ -22,8 +22,8 @@ do_configure[noexec] = "1" do_build[depends] = "${IB_ROOTFS_METHOD}:do_build" do_build[depends] += "linux:do_build" -# do_deploy extracts rootfs.cpio (apps baked in by usr-linux:do_deploy), -# copies it onto p2 and repacks it. +# do_deploy extracts rootfs.cpio, copies it onto p2 and repacks it. The usr +# apps are copied on top afterwards by usr-linux:do_deploy. # # It DOES depend on ${IB_ROOTFS_METHOD}:do_build, and that dependency is # load-bearing rather than a leftover: nothing else orders this task after diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb index 9eae62bd9..de674d779 100644 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb +++ b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb @@ -25,38 +25,57 @@ do_unpack[depends] += "linux:do_build" do_deploy[depends] = "rootfs-linux:do_deploy" do_deploy[nostamp] = "1" -# Deploy the usr contents, i.e. the deploy/ dir, in the corresponding partition of the filesystem +# Deploy the usr contents, i.e. the deploy/ dir, into the rootfs partition +# (p2) of the filesystem. rootfs-linux:do_deploy runs first (dependency above) +# so the apps land on top of the freshly extracted rootfs; bsp-linux:do_deploy +# pulls this task, so a full `deploy.sh bsp-linux` always deploys usr too. + python do_deploy() { import os - - __do_fs_mount(d) - + + # Same exception as rootfs-linux:do_deploy: verdin-imx8mp storage goes + # through the Tezi / HTTP recovery flow, there is no p2 to mount here. + + if d.getVar('IB_PLATFORM') == "verdin-imx8mp": + bb.plain("verdin-imx8mp: rootfs delivered via Tezi/HTTP, skipping usr partition deploy") + return + IB_USR_PATH = d.getVar('IB_USR_PATH') IB_FILESYSTEM_PATH = d.getVar('IB_FILESYSTEM_PATH') IB_ROOTFS_PARTITION = d.getVar('IB_ROOTFS_PARTITION') - if not os.path.isdir(os.path.join(IB_USR_PATH, "build", "deploy")): - - __do_fs_umount(d) - bb.fatal("The {} does not exist; please build usr first...".format(IB_USR_PATH)) + deploy_src = os.path.join(IB_USR_PATH, "build", "deploy") + rootfs_dst = os.path.join(IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION) + + if not os.path.isdir(deploy_src): + bb.fatal("The {} does not exist; please build usr first...".format(deploy_src)) - if not os.path.isdir(os.path.join(IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION, "root")): + __do_fs_mount(d) + + if not os.path.isdir(os.path.join(rootfs_dst, "root")): __do_fs_umount(d) bb.fatal("The root directory is not present in the second partition; please deploy rootfs...") - - - os.system("cp -r {}/build/deploy/* {}/{}/".format(IB_USR_PATH, IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION)) - - __do_fs_umount(d) + + # p2 is written by rootfs-linux:do_deploy with `sudo cp -a`, so its tree + # is root-owned: the copy needs root too. --keep-dirlinks keeps the + # rootfs directory symlinks (e.g. /lib -> usr/lib) instead of replacing + # them with real directories. check=True: a failed copy must fail the + # deploy, not leave a rootfs silently without the apps. + + try: + utils_sudo(["rsync", "-a", "--keep-dirlinks", + deploy_src + "/", rootfs_dst + "/"], check=True) + finally: + __do_fs_umount(d) } -# `after do_build`: do_deploy copies build/deploy/ into the rootfs, so the -# recipe's own do_build must have produced it first. Without this ordering -# a single-shot `bitbake bsp-linux` (whose do_prepare_initrd pulls this -# do_deploy early) can schedule do_deploy before do_build and fatal on the -# missing build/deploy dir. -addtask do_deploy after do_build +# No `after do_build`: deploy is decoupled from the build (edit -> build.sh +# -> deploy.sh). do_deploy only copies the already-built build/deploy/ and +# fails clearly above when it is missing, instead of dragging usr-linux: +# do_build (and linux:do_build through do_unpack) into every deploy. + +addtask do_deploy # Build extra components which is not in src/ directory like modules do_build:prepend () { From 0ca71df0b38d17c5f57720b4194e3ff193124314 Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 15:12:22 +0200 Subject: [PATCH 2/2] usr-linux: deploy into rootfs.cpio when it is the ramfs, onto p2 otherwise Copying the user space onto p2 is right only when p2 is the running root. With IB_RAMFS_SOURCE = "rootfs" (the bsp.bbclass default) the kernel runs from the rootfs.cpio embedded in the ITB (root=/dev/ram), so apps copied onto p2 were never seen by the running system, and the initrd carried none. usr-linux:do_deploy now puts the apps in ONE place, chosen by IB_RAMFS_SOURCE: - "rootfs": baked INTO rootfs.cpio (extract, sudo rsync, re-pack). bsp-linux:do_prepare_initrd pulls it into the build, before gzipping the cpio into the ITB; it then depends on rootfs-linux:do_build (never on the boot media) and runs after usr-linux:do_build. - anything else ("initrd", pivoting to p2): copied onto p2 after rootfs-linux:do_deploy; bsp-linux:do_deploy pulls it, deploy-only, no `after do_build`. `deploy.sh usr-linux` therefore does the right thing in both modes. Validated with `bitbake -g bsp-linux` (build and deploy graphs) with IB_RAMFS_SOURCE forced to "rootfs" and to "initrd". --- .../recipes-bsp/linux/bsp-linux_1.0.bb | 23 ++++-- .../recipes-rootfs/linux/rootfs-linux_1.0.bb | 3 +- .../recipes-usr/linux/usr-linux_1.0.bb | 70 +++++++++++++++---- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb b/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb index a910c987f..56764e1bb 100644 --- a/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb +++ b/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb @@ -170,17 +170,30 @@ addtask do_prepare_initrd before do_itb # Deploy everything # # Deploy is decoupled from the build: it writes the already-built artefacts -# onto the boot media WITHOUT recompiling. It pulls usr-linux:do_deploy, which -# itself runs after rootfs-linux:do_deploy: the rootfs is extracted onto p2 -# first, then the usr apps (linux/usr/build/deploy) are copied on top — so a -# full deploy always carries the user space, whatever rootfs.cpio contains. +# onto the boot media WITHOUT recompiling. The rootfs is extracted onto p2 +# (rootfs-linux:do_deploy); when p2 is the running root (IB_RAMFS_SOURCE other +# than "rootfs") it also pulls usr-linux:do_deploy, which copies the usr apps +# (linux/usr/build/deploy) on top — see the anonymous function below. With +# the rootfs.cpio ramfs the apps travel inside the ITB instead. # It then writes the .itb produced by do_itb during `build.sh -a` onto p1 # (__do_platform_deploy). It does NOT pull do_build / do_itb / usr-linux: # do_build — those belong to `build.sh -a`. Workflow: edit -> build.sh -> # deploy.sh. A deploy with no prior build fails clearly (missing rootfs.cpio / # usr build/deploy / .itb) rather than silently rebuilding. -do_deploy[depends] = "filesystem:do_fs_check rootfs-linux:do_deploy usr-linux:do_deploy" +do_deploy[depends] = "filesystem:do_fs_check rootfs-linux:do_deploy" + +# usr-linux:do_deploy goes where the running root is (see usr-linux_1.0.bb): +# with IB_RAMFS_SOURCE = "rootfs" it bakes the apps into rootfs.cpio, so it is +# part of the BUILD, before do_prepare_initrd gzips that cpio into the ITB; +# otherwise it copies them onto p2 and is part of the DEPLOY. + +python () { + if (d.getVar('IB_RAMFS_SOURCE') or "rootfs").strip() == "rootfs": + d.appendVarFlag('do_prepare_initrd', 'depends', ' usr-linux:do_deploy') + else: + d.appendVarFlag('do_deploy', 'depends', ' usr-linux:do_deploy') +} do_deploy[nostamp] = "1" python do_deploy() { diff --git a/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb b/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb index 46ed5ab86..ddaba2735 100644 --- a/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb +++ b/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb @@ -23,7 +23,8 @@ do_build[depends] = "${IB_ROOTFS_METHOD}:do_build" do_build[depends] += "linux:do_build" # do_deploy extracts rootfs.cpio, copies it onto p2 and repacks it. The usr -# apps are copied on top afterwards by usr-linux:do_deploy. +# apps are either already in rootfs.cpio (IB_RAMFS_SOURCE = "rootfs") or +# copied on top afterwards by usr-linux:do_deploy. # # It DOES depend on ${IB_ROOTFS_METHOD}:do_build, and that dependency is # load-bearing rather than a leftover: nothing else orders this task after diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb index de674d779..dfbede236 100644 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb +++ b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb @@ -25,15 +25,62 @@ do_unpack[depends] += "linux:do_build" do_deploy[depends] = "rootfs-linux:do_deploy" do_deploy[nostamp] = "1" -# Deploy the usr contents, i.e. the deploy/ dir, into the rootfs partition -# (p2) of the filesystem. rootfs-linux:do_deploy runs first (dependency above) -# so the apps land on top of the freshly extracted rootfs; bsp-linux:do_deploy -# pulls this task, so a full `deploy.sh bsp-linux` always deploys usr too. +# Deploy the usr contents, i.e. the deploy/ dir, where the running system will +# find them. That depends on what the kernel boots as its root, and the apps go +# to ONE place only: +# +# IB_RAMFS_SOURCE = "rootfs" - the embedded ramfs IS rootfs.cpio, so the apps +# are baked INTO rootfs.cpio (rsync into the extracted tree, then re-pack). +# bsp-linux:do_prepare_initrd pulls this task into the BUILD, before it +# gzips rootfs.cpio into the ITB, so here it must not touch the boot media: +# it depends on rootfs-linux:do_build instead of rootfs-linux:do_deploy and +# runs after do_build (anonymous function below). +# +# any other value (the static "initrd" ramfs, which pivots to p2) - the apps +# are copied onto the rootfs partition p2, on top of what +# rootfs-linux:do_deploy extracted. bsp-linux:do_deploy pulls this task, +# so a full deploy always carries the user space. +# +# Unset means "rootfs", the bsp.bbclass default. + +def usr_linux_ramfs_is_rootfs(d): + return (d.getVar('IB_RAMFS_SOURCE') or "rootfs").strip() == "rootfs" + +python () { + if usr_linux_ramfs_is_rootfs(d): + d.setVarFlag('do_deploy', 'depends', 'rootfs-linux:do_build') + bb.build.addtask('do_deploy', None, 'do_build', d) +} python do_deploy() { import os + IB_USR_PATH = d.getVar('IB_USR_PATH') + deploy_src = os.path.join(IB_USR_PATH, "build", "deploy") + + if not os.path.isdir(deploy_src): + bb.fatal("The {} does not exist; please build usr first...".format(deploy_src)) + + if usr_linux_ramfs_is_rootfs(d): + IB_ROOTFS_PATH = d.getVar('IB_ROOTFS_PATH') + IB_PLATFORM = d.getVar('IB_PLATFORM') + + if not os.path.isfile(os.path.join(IB_ROOTFS_PATH, "board", IB_PLATFORM, "rootfs.cpio")): + bb.fatal("rootfs.cpio is missing; please build rootfs first...") + + # The extracted tree is root-owned (cpio -id), so rsync needs root to + # write into it while preserving mode bits and ownership. + + d.setVar('ROOTFS_FILENAME', 'rootfs') + __do_rootfs_mount(d) + utils_sudo(["rsync", "-a", "--keep-dirlinks", + deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) + __do_rootfs_umount(d) + + bb.plain("usr deployed into rootfs.cpio (IB_RAMFS_SOURCE = rootfs)") + return + # Same exception as rootfs-linux:do_deploy: verdin-imx8mp storage goes # through the Tezi / HTTP recovery flow, there is no p2 to mount here. @@ -41,16 +88,10 @@ python do_deploy() { bb.plain("verdin-imx8mp: rootfs delivered via Tezi/HTTP, skipping usr partition deploy") return - IB_USR_PATH = d.getVar('IB_USR_PATH') IB_FILESYSTEM_PATH = d.getVar('IB_FILESYSTEM_PATH') IB_ROOTFS_PARTITION = d.getVar('IB_ROOTFS_PARTITION') - - deploy_src = os.path.join(IB_USR_PATH, "build", "deploy") rootfs_dst = os.path.join(IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION) - if not os.path.isdir(deploy_src): - bb.fatal("The {} does not exist; please build usr first...".format(deploy_src)) - __do_fs_mount(d) if not os.path.isdir(os.path.join(rootfs_dst, "root")): @@ -70,10 +111,11 @@ python do_deploy() { __do_fs_umount(d) } -# No `after do_build`: deploy is decoupled from the build (edit -> build.sh -# -> deploy.sh). do_deploy only copies the already-built build/deploy/ and -# fails clearly above when it is missing, instead of dragging usr-linux: -# do_build (and linux:do_build through do_unpack) into every deploy. +# No static `after do_build`: in the p2 case do_deploy is a pure deploy step +# (edit -> build.sh -> deploy.sh) that copies the already-built build/deploy/ +# and fails clearly when it is missing, instead of dragging usr-linux:do_build +# (and linux:do_build through do_unpack) into every deploy. The rootfs.cpio +# case, which runs inside the build, gets its ordering above. addtask do_deploy