This error often arrives after compilation has succeeded, which makes it feel oddly late. BitBake is protecting you from a quieter failure: a recipe installed files into its staging root, but those files would vanish before the image is assembled because no output package owns them.

What “installed” and “shipped” mean in BitBake

  • do_install copies artifacts into ${D}, a destination root used while building the recipe—not directly into the final image.

  • do_package walks that staged tree and assigns paths to packages listed in PACKAGES.

  • Each output package has matching rules in FILES:<package>.

  • The first package in PACKAGES whose FILES rules match a path claims it.

  • Only generated packages selected through image/package dependencies can reach the final root filesystem.

Read the QA message as a manifest diff

Initialized Yocto build environmentbash
bitbake example 2>&1 | tee example-build.log
grep -A20 "installed but not shipped" example-build.log
ERROR: example-1.0-r0 do_package: QA Issue: example: Files/directories were installed but not shipped in any package:
  /opt/example
  /opt/example/example.conf
[installed-vs-shipped]

Those paths are target paths, not host paths

  • The message names paths relative to the future target root filesystem.

  • A directory can be reported along with its children; decide ownership at the most useful package boundary.

  • The recipe name before the message is not necessarily the final output package name.

  • Fix all listed artifacts; solving only the first line usually exposes the remainder on the next run.

  • Keep the complete log because the task path and recipe version help locate ${WORKDIR}.

1. Inspect what do_install actually staged

Yocto Build Directorybash
bitbake -e example | grep -E '^(D|WORKDIR|PACKAGES)='
bitbake -c install -f example
find "$(bitbake -e example | sed -n 's/^D="\(.*\)"/\1/p')" -mindepth 1 -printf '%P\n' | sort
The evaluated environment prints the staging root and package list; the final command lists paths created beneath `${D}`.

Start with evaluated metadata, not guesses

  • bitbake -e shows the final value after includes, classes, overrides, and appends are applied.

  • -c install -f reruns the install task and dependent packaging tasks may need rebuilding afterward.

  • ${D} can vary with recipe, machine, tune, multilib, and build configuration.

  • The quoted path protects whitespace and prevents accidental shell expansion.

  • Inspect temp/log.do_install when the staged tree contains files you did not expect.

2. Assign a wanted artifact to the main package

recipes-example/example/example_1.0.bbbitbake
do_install() {
    install -d ${D}${datadir}/example
    install -m 0644 ${WORKDIR}/example.conf \
        ${D}${datadir}/example/example.conf
}
 
FILES:${PN} += "${datadir}/example/example.conf"

The install path and package path use different roots

  • Use ${D}${datadir} while installing because the task writes into the staging root.

  • Use ${datadir}/... in FILES:${PN} because packaging rules describe target paths; adding ${D} there is a common mistake.

  • install -d creates the destination with controlled permissions.

  • Mode 0644 is appropriate for a non-executable data/config example; choose permissions based on actual use.

  • Modern override syntax is FILES:${PN}; older release branches used FILES_${PN}. Follow the syntax supported by the project’s pinned BitBake version.

3. Create a deliberate subpackage

recipes-example/example/example_1.0.bbbitbake
PACKAGES =+ "${PN}-tools"
 
FILES:${PN}-tools = "${libexecdir}/example/*"
RDEPENDS:${PN}-tools += "bash"

Package ordering decides ownership

  • =+ prepends the tools package so it is considered before the main package.

  • A path matching more than one FILES rule goes to the earliest package in PACKAGES.

  • Name the exact output package in FILES and runtime dependency overrides.

  • Add RDEPENDS only for genuine runtime requirements; a shell-script shebang is one example.

  • A produced subpackage is not automatically installed in an image—add it through the image recipe, package group, or a justified runtime dependency.

4. Remove build-only or accidental files

recipes-example/example/example_1.0.bbappendbitbake
do_install:append() {
    rm -f ${D}${libdir}/example/*.la
    rmdir --ignore-fail-on-non-empty ${D}${libdir}/example
}

Deletion should be precise and explainable

  • Remove an artifact only after confirming the target never needs it.

  • Use paths rooted beneath ${D} and avoid broad recursive deletion.

  • rm -f tolerates a release where no matching file exists; ensure that flexibility does not conceal an upstream layout change.

  • rmdir removes only an empty directory, making it safer than recursive removal.

  • When possible, disable installation through the upstream project’s supported build option instead of cleaning up afterward.

5. Repackage and inspect the result

Yocto Build Directorybash
bitbake -c package -f example
oe-pkgdata-util list-pkgs | grep '^example'
oe-pkgdata-util list-pkg-files example
oe-pkgdata-util find-pkg '/usr/share/example/example.conf'
The package task completes without installed-vs-shipped; pkgdata identifies which generated package owns each target path.

Risk level: caution. Review the command before running it.

A clean task is only the first proof

  • Forcing a task changes build outputs but does not erase downloads or the entire shared-state cache.

  • list-pkg-files verifies package contents, which is stronger evidence than reading the recipe alone.

  • find-pkg resolves a target path to its owning package when pkgdata exists.

  • Use the actual output package name, not automatically the recipe filename.

  • Build the consuming image afterward and verify both its manifest and target filesystem.

Inspect package-split during a stubborn failure

Yocto Build Directorybash
workdir=$(bitbake -e example | sed -n 's/^WORKDIR="\(.*\)"/\1/p')
find "$workdir/packages-split" -mindepth 1 -maxdepth 4 -printf '%P\n' | sort | less
The tree shows each output package directory and the paths BitBake assigned to it.

This exposes rule overlap and empty packages

  • packages-split/<package>/ mirrors the files assigned to that package.

  • An unexpected owner usually means an earlier package has a broader matching rule.

  • An absent directory may mean do_package did not rerun after metadata changed.

  • Empty packages are normally not emitted unless ALLOW_EMPTY requests them.

  • Work directories are disposable build artifacts; never treat them as deployment output.

Why wildcard fixes can age badly

  • FILES:${PN} += "/opt/example" can intentionally claim a whole tree, but it may also absorb future plugins, secrets, debug data, or development files.

  • Use standard path variables such as ${bindir}, ${libdir}, ${sysconfdir}, and ${datadir} instead of hard-coded /usr layouts.

  • Keep headers, unversioned link libraries, static archives, debug symbols, and documentation in their conventional packages unless product policy says otherwise.

  • Never use ${D} inside FILES values.

  • Review package contents after upstream version bumps because install layouts can change without recipe syntax changing.

Common causes mapped to corrections

  • Custom `/opt` or `/srv` tree: add a precise FILES:<package> target path or move the artifact to a standard directory.

  • Empty directory: remove it if unnecessary, or package it only when runtime software genuinely requires it.

  • Upstream installs tests/examples: disable that install option, remove the files deliberately, or create a clearly named optional package.

  • Plugin directory: create a plugin subpackage/dynamic packaging policy and verify dependency ownership.

  • Wrong variable expansion: compare bitbake -e values and keep ${D} only on the install side.

  • Old underscore overrides copied forward: convert to colon overrides when the project release requires modern syntax.

  • One file lands in the wrong subpackage: inspect PACKAGES order and overlapping FILES globs.

  • Error returns after an upgrade: diff log.do_install, ${D}, and package contents across the old and new upstream versions.

A compact decision path

  1. Copy the complete list of reported target paths.

  2. Locate the install command or class that creates each path.

  3. Decide whether each artifact belongs on the target at all.

  4. For wanted files, choose the main package or a meaningful subpackage and add a precise FILES rule.

  5. For unwanted files, stop their installation or remove them safely beneath ${D}.

  6. Rerun packaging and inspect pkgdata/package-split ownership.

  7. Build the image and confirm its manifest and runtime filesystem.

Version and migration notes

  • Current Yocto documentation uses colon override syntax such as FILES:${PN} and do_install:append.

  • Historical recipes may correctly use FILES_${PN} and do_install_append on old, pinned releases.

  • Do not mechanically modernize syntax without matching the BitBake version used by every layer.

  • Keep all layers on compatible branches and validate migration notes when upgrading.

  • The underlying ownership rule—installed beneath ${D}, then claimed by an output package—remains the important mental model.

Verification checklist before merging

  • No installed-vs-shipped QA message remains without an approved explanation.

  • Every installed artifact has an intentional package owner.

  • No ${D} prefix appears in FILES values.

  • Output package names, package order, runtime dependencies, permissions, and conffile behavior are reviewed.

  • Package contents and image manifest match the product requirement.

  • Clean CI and the supported Yocto branch reproduce the result.

  • Broad QA suppression and accidental wildcard ownership are absent.

Authoritative references