Lesson
Dependencies and Sysroots
How build-time and runtime dependencies affect recipe sysroots, native tools, and final images
Dependencies are one of the places where Yocto catches people out early.
The confusing part is that there are two different questions that sound similar:
- what does this recipe need while it is being built?
- what does the installed package need when it runs on the target?
Those are not the same question, and Yocto treats them differently. If a recipe needs a header or library during compilation, that is a build-time dependency. If the installed program needs another package on the target, that is a runtime dependency.
This matters because Yocto builds software in controlled environments. A recipe does not get to see every header, library, and tool that happens to exist somewhere in the build tree. BitBake prepares a sysroot for that recipe from the dependencies it has been told about. If the dependency is missing, the build may fail with an error that looks like an upstream source problem, even though the metadata is the real issue.
The Short Version
Use DEPENDS when the recipe needs something to build.
Use RDEPENDS when the generated package needs something to run.
That gives us the basic split:
- DEPENDS
Recipe-level build dependency. It controls what must be built and staged before this recipe’s build tasks run.
- RDEPENDS
Package-level runtime dependency. It controls what package dependencies are recorded for installation on the target.
The distinction is easy to say and surprisingly easy to get wrong in a real recipe. The names are close enough to look related, but they operate at different levels of the build.
Recipe build:
DEPENDS
-> other recipes are built
-> headers, libraries, and tools are staged into the recipe sysroot
Target runtime:
RDEPENDS:${PN}
-> package metadata says another package must be installed with this package
A Small Compile-Time Example
Imagine a small C program that uses zlib.
#include <stdio.h>
#include <zlib.h>
int main(void)
{
printf("zlib version: %s\n", zlibVersion());
return 0;
}
The recipe might start like this:
SUMMARY = "Small zlib example"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://zlib-demo.c"
S = "${UNPACKDIR}"
DEPENDS += "zlib"
do_compile() {
${CC} ${CFLAGS} ${LDFLAGS} ${S}/zlib-demo.c -lz -o zlib-demo
}
do_install() {
install -d ${D}${bindir}
install -m 0755 zlib-demo ${D}${bindir}/zlib-demo
}
We add zlib to DEPENDS because the recipe needs the zlib header and library
while compiling and linking. BitBake will build the zlib recipe first if it
needs to, then stage the relevant development files into this recipe’s sysroot.
Without DEPENDS += "zlib", the compiler may fail with something like:
fatal error: zlib.h: No such file or directory
That does not mean zlib does not exist anywhere in the Yocto build. It means
this recipe was not given access to it.
Why RDEPENDS Does Not Fix Compile Errors
It is tempting to try this instead:
RDEPENDS:${PN} += "zlib"
That is not the fix for a missing header.
RDEPENDS:${PN} affects the generated runtime package. It says that when the
main package is installed on the target, the package manager should also install
zlib. It does not put zlib.h into the compile environment. By the time
runtime package dependencies matter, do_compile() is long finished.
This is one of the most useful rules to remember:
In many cases, DEPENDS += "zlib" is enough for both the compile-time and
runtime sides because the packaging system can automatically detect shared
library dependencies. If your binary links against libz.so, Yocto can normally
record the runtime library dependency for you.
You still use explicit RDEPENDS when the relationship cannot be detected from
the binary itself, such as a shell script that calls another command.
A Runtime Dependency Example
Now consider a package that installs a shell script:
#!/bin/bash
echo "Running maintenance task"
The script does not need Bash to compile. There is no compile step. But it does need Bash on the target when someone runs it.
The recipe would use a runtime dependency:
SUMMARY = "Small maintenance script"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://maintenance-task"
S = "${UNPACKDIR}"
RDEPENDS:${PN} += "bash"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${S}/maintenance-task ${D}${bindir}/maintenance-task
}
Here RDEPENDS:${PN} += "bash" is correct because the installed package needs
the bash package at runtime.
Adding DEPENDS += "bash" would normally be wrong. It might cause more build
work, but it would not express the actual runtime requirement of the installed
package.
Recipe Dependencies and Package Dependencies
The next trap is that DEPENDS names recipes, while RDEPENDS normally names
packages.
That sounds like a small distinction until a recipe produces several packages.
For example, a recipe called example might generate:
exampleexample-devexample-dbgexample-docexample-tools
When you write:
DEPENDS += "example"
you are saying that this recipe depends on the example recipe being built and
staged for build use.
When you write:
RDEPENDS:${PN} += "example-tools"
you are saying that the package ${PN} needs the runtime package
example-tools installed with it.
This distinction becomes more important as projects grow. Recipe names and package names often match, especially for simple software, but they are not the same concept.
Recipe-Specific Sysroots
Older explanations of Yocto sometimes make it sound as if there is one large global sysroot. Modern Yocto uses recipe-specific sysroots.
For a target recipe, BitBake prepares a sysroot containing the build-time inputs
declared through DEPENDS and the dependencies that follow from them. This keeps
builds cleaner and helps avoid accidental dependencies on whatever happened to
be built earlier.
Two variables are especially useful to recognise:
- RECIPE_SYSROOT
The target sysroot for the recipe. This is where target headers, libraries, and related files from build-time dependencies are staged.
- RECIPE_SYSROOT_NATIVE
The native sysroot for the recipe. This is where host-executed tools needed by the recipe are staged.
You normally do not hard-code these paths in simple recipes, because the compiler and build classes are already set up to use them. They become useful when you are debugging and need to answer a very practical question:
Is the file my recipe needs actually in its sysroot?
You can inspect the values with:
bitbake -e zlib-demo | less
Then search for:
RECIPE_SYSROOT=
RECIPE_SYSROOT_NATIVE=
You can also enter the recipe’s build environment:
bitbake -c devshell zlib-demo
From there, you can inspect include paths, libraries, and tool locations in the
same environment the recipe sees. We will use devshell again in the diagnostic
lesson later in this module.
Native Tools
Not every dependency is a target library.
Sometimes a recipe needs a tool that runs on the build host while the recipe is being built. Examples include code generators, protocol compilers, or helper programs used by the upstream build system.
Yocto represents those as native recipes.
For example:
DEPENDS += "protobuf-native"
This means the recipe needs the host-executed protobuf tools while building. The
tool is built for the build host and staged into ${RECIPE_SYSROOT_NATIVE}.
The naming convention is important:
- foo
A normal target recipe or package. It builds for the target system.
- foo-native
A native variant that runs on the build host during the build.
- nativesdk-foo
A variant built for inclusion in an SDK, so it can run in the SDK host environment.
Use foo-native when the build process needs a tool to run during the Yocto
build. Use nativesdk-foo when you are constructing an SDK that should contain
that tool. They solve different problems.
A Native Tool Example
Suppose a project generates a source file before compiling:
schema-compiler protocol.schema generated.c
The schema-compiler program runs on the build host. It is not target software.
If the recipe for that tool is called schema-compiler, the application recipe
would normally depend on the native variant:
DEPENDS += "schema-compiler-native"
do_compile() {
schema-compiler ${S}/protocol.schema ${B}/generated.c
${CC} ${CFLAGS} ${LDFLAGS} ${B}/generated.c -o protocol-demo
}
The important part is not the exact tool name. The important part is choosing the right side of the build.
If the tool must run during the build, you need a native tool in the native sysroot. If the final program must run on the target, you need target packages in the image.
Why DEPENDS Does Not Install Software into the Image
Another common mistake goes the other way.
You add:
DEPENDS += "openssl"
The recipe now builds. Then you boot the image and expect the openssl command
to be installed.
That is not what DEPENDS promised.
DEPENDS makes the recipe available for building this recipe. It does not mean
“install this package into every image that contains my package”. If the image
needs the openssl command, add the relevant runtime package to the image or add
an appropriate runtime dependency from the package that really needs it.
The direction matters:
DEPENDS:
build this first so my recipe can compile
IMAGE_INSTALL:
install this package into the image
RDEPENDS:
when this package is installed, also install what it needs to run
How to Investigate Dependency Problems
When something is missing, start by asking which stage is missing it.
If compilation fails with a missing header or library, check DEPENDS and the
recipe sysroot:
bitbake -e myapp | less
bitbake -c devshell myapp
If the program builds but fails on the target because a command or shared object is missing, check package dependencies and image contents:
oe-pkgdata-util list-pkgs | grep myapp
oe-pkgdata-util lookup-recipe myapp
For final image selection, go back to the image and package lesson: Images and Packages. For the basic recipe structure, see Creating Yocto Recipes.
Summary
DEPENDS and RDEPENDS solve different problems.
The main ideas from this lesson are:
DEPENDSis a recipe build dependencyRDEPENDSis a package runtime dependency- recipe dependencies and package dependencies are not the same thing
${RECIPE_SYSROOT}contains target build inputs for one recipe${RECIPE_SYSROOT_NATIVE}contains host-executed build tools for one recipe- use
foo-nativewhen a tool must run during the build - use
nativesdk-foowhen that tool belongs in an SDK RDEPENDSdoes not make headers available at compile timeDEPENDSdoes not automatically install a package into the final image
Check your understanding
Quick quiz: dependencies and sysroots
Check the distinction between build-time inputs and runtime packages.