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:

  • example
  • example-dev
  • example-dbg
  • example-doc
  • example-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:

  • DEPENDS is a recipe build dependency
  • RDEPENDS is 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-native when a tool must run during the build
  • use nativesdk-foo when that tool belongs in an SDK
  • RDEPENDS does not make headers available at compile time
  • DEPENDS does not automatically install a package into the final image

Quick quiz: dependencies and sysroots

Check the distinction between build-time inputs and runtime packages.

Question 1A recipe needs a header from `zlib` while compiling. Which dependency is the normal fix?
Question 2A shell script installed by your package calls `/bin/bash` on the target. Which dependency belongs in the package metadata?
Question 3What is the main purpose of `${RECIPE_SYSROOT}`?