Skip to content

Yocto Project Concepts

Explanations for Yocto Project concepts that go beyond the surface of "how-to" information and reference (or look-up) material. Concepts such as components, workflow, toolchains, cache.

Last update: 2022-05-07


Hard Assignment =

Occur immediately as the statement is parsed.

VARIABLE = "value"
VARIABLE = 'value with " in it'
Soft (Default) Assignment ?=

Define a variable if it is undefined when the statement is parsed.

If the variable is defined, the soft assignment is lost.

If multiple soft assignments get parsed, the first one is used.

VARIABLE ?= "default value"
VARIABLE = "set value"
VARIABLE ?= "default value" # this does nothing

Weak (Default) Assignment ??=

Delay the assignment at the end of parsing process rather than immediately.

If multiple weak assignments get parsed, the last one is used.

VARIABLE  ?= "value 1" # this wins
VARIABLE  ?= "value 2"
VARIABLE ??= "value 1"
VARIABLE ??= "value 2" # this wins
VARIABLE  ?= "value 1" # this wins
VARIABLE ??= "value 2"
VARIABLE ??= "value 1"
VARIABLE  ?= "value 2" # this wins
VARIABLE ??= "value 1"
VARIABLE   = "value 3" # this always wins
VARIABLE  ?= "value 2"

Variable expansion ${}

Refer to the value of a variable.

  • The = operator does not immediately expand the variable reference. The expansion is deferred until the variable is used.
  • The := immediately expand the variable reference when is parsed.

A = "${B} hello" # A = linux world hello
B = "${C} world" # B = world
C = "linux"
A := "${B} hello" # A = ${B} hello
B := "${C} world" # B = ${C} world
C = "linux"
A  = "11"
B  = "${A}" # B = 22 when B is used
A  = "22"
C  = "${A}" # C = 22 when C is used
A  = "11"
B := "${A}" # B = 11 immediately
A  = "22"
C  = "${A}" # C = 22 when C is used

Appending +=, .=,_append=

Append values with or without a space.

+= automatically adds a space, but .= and _append= do not do that.

A  = "hello"
A += "my"          # A = hello my
A .= "sunny"       # A = hello mysunny
A_append = "world" # A = hello mysunnyworld
Prepending =+, =., _prepend=

Prepend values with or without a space.

=+ automatically adds a space, but =. and _prepend= do not do that.

A  = "hello"
A =+ "my"           # A = my hello
A =. "sunny"        # A = sunnymy hello
A_prepend = "world" # A = worldsunnymy hello
Removal _remove=

Remove all occurrences of a value in a list.

A = "123 456 123 789 123"
A_remove = "123" # A = " 456  789 ", note the spaces
Overriding syntax _append=, _prepend=, _remove=

Provide guaranteed operations, as compared to +=, =+.

1st parsed
A += "world" # does not work, A is undefined
2nd parsed
A  = "hello" # A = hello when A is used

However, using overriding syntax give a good result:

1st parsed
A_append = " world" # keep this action when A is used
2nd parsed
A  = "hello" # A = hello world when A is used

Check the value

After all configurations are parsed:

bitbake <target/recipe> -e | grep ^VARIABLE=


A layer is a logical collection of related recipes. Layer name should start with meta- to follow Yocto’s naming convention. Each layer has a priority, which is used by bitbake to decide which layer takes precedence if there are recipe files with the same name in multiple layers. A higher numeric value represents a higher priority.

Despite most of the customization can be done with the local.conf configuration file, it is not possible to: * Store recipes for your own software projects * Create your own images * Consolidate patches/modifications to other people’s recipes * Add a new custom kernel * Add a new machine

Depending on the type of layer, add the content:

  • If the layer is adding support for a machine, add the machine configuration in conf/machine/

  • If the layer is adding distro policy, add the distro configuration in conf/distro/

  • If the layer introduces new recipes, put the recipes you need in recipes-* subdirectories of the layer directory. Recipes are divided into categories {»but hard to decide»}

  1. Create new layer

    There are two ways to create your own layer.

    1. Manually

      mkdir meta-my-layer
      cp meta-my-layer

      Copy poky/meta-skeleton/conf/layer.conf to new layer:

      mkdir conf && cd conf
      cp ../../poky/meta-skeleton/conf/layer.conf .
    2. Using script

      bitbake-layers create-layer meta-my-layer

      The tool automates layer creation by setting up a subdirectory with a layer.conf configuration file, a recipes-example subdirectory that contains an recipe, a licensing file, and a README. Default priority of the layer is 6.

      ├── conf
      │   └── layer.conf
      ├── COPYING.MIT
      ├── README
      └── recipes-example
          └── example

    To check layer compatibility, run yocto-check-layer:

    yocto-check-layer meta-my-layer
  2. Layer configs


    # We have a conf and classes directory, add to BBPATH
    BBPATH .= ":${LAYERDIR}"
    # We have recipes-* directories, add to BBFILES
    BBFILES += "${LAYERDIR}/recipes-*/*/*.bb ${LAYERDIR}/recipes-*/*/*.bbappend"
    BBFILE_COLLECTIONS += "my-layer"
    BBFILE_PATTERN_my-layer = "^${LAYERDIR}/"
    BBFILE_PRIORITY_my-layer = "6"
    # This should only be incremented on significant changes that will
    # cause compatibility issues with other layers
    LAYERVERSION_my-layer = "1"
    LAYERDEPENDS_my-layer = "core"
    LAYERSERIES_COMPAT_my-layer = "dunfell"


Image is a top level recipe. It inherits the image.bbclass.

Create new Image#

You often need to create your own Image recipe in order to add new packages or functionality. There are 2 ways:

  1. Create an image from scratch

    The simplest way is to inherit the core-image bbclass, as it provides a set of image features that can be used very easily.

    1. Create an image directory

      mkdir -p recipes-examples/images
    2. Create the image recipe

      nano recipes-examples/images/
      SUMMARY = "A small boot image for LWL learners"
      LICENSE = "MIT"
      # start from core-image
      inherit core-image
      # core files for basic console boot
      IMAGE_INSTALL = "packagegroup-core-boot"
      # add our needed applications
      #IMAGE_INSTALL += "userprog"
  2. Extend an existing recipe (preferable)

    When an image mostly fits our needs, and we need to do minor adjustments on it, it is very convenient to reuse its code.

    This makes code maintenance easier and highlights the functional differences.

    nano recipes-examples/images/
    # select base image
    require recipes-core/images/
    # append our needed packages, use overriding syntax
    #IMAGE_INSTALL_append = " userprog"

Package group#

A package group is a set of packages that can be included on any image.

Using a package group name in IMAGE_INSTALL variable install all the packages defined by the package group into the root file system of your target image.

There are many package groups. There are present in subdirectories named packagegroups. They are recipe files(.bb) and starts with packagegroup-.

For example, packagegroup-core-boot: Provides the minimum set of packages necessary to create a bootable image with console.

ls poky/meta/recipes-core/packagegroups/               

Image features#

Another method for customizing your image is to enable or disable high-level image features by using the IMAGE_FEATURES and EXTRA_IMAGE_FEATURES variables.

Image features is the map of features to package groups.

IMAGE_FEATURES/EXTRA_IMAGE_FEATURES is made to enable special features for your image, such as empty password for root, debug image, special packages, x11, splash, ssh-server.

Best practice is to:

  • Use IMAGE_FEATURES from a recipe
  • Use EXTRA_IMAGE_FEATURES from local.conf

For example:

FEATURE_PACKAGES_x11 = "packagegroup-core-x11"
FEATURE_PACKAGES_x11-base = "packagegroup-core-x11-base"
FEATURE_PACKAGES_x11-sato = "packagegroup-core-x11-sato"
FEATURE_PACKAGES_tools-debug = "packagegroup-core-tools-debug"
FEATURE_PACKAGES_eclipse-debug = "packagegroup-core-eclipse-debug"
FEATURE_PACKAGES_tools-profile = "packagegroup-core-tools-profile"
FEATURE_PACKAGES_tools-testapps = "packagegroup-core-tools-testapps"
FEATURE_PACKAGES_tools-sdk = "packagegroup-core-sdk packagegroup-core-standalone-sdk-target"
FEATURE_PACKAGES_nfs-server = "packagegroup-core-nfs-server"
FEATURE_PACKAGES_nfs-client = "packagegroup-core-nfs-client"
FEATURE_PACKAGES_ssh-server-dropbear = "packagegroup-core-ssh-dropbear"
FEATURE_PACKAGES_ssh-server-openssh = "packagegroup-core-ssh-openssh"
IMAGE_FEATURES += "splash package-management x11-base x11-sato ssh-server-dropbear hwcodecs"
inherit core-image
EXTRA_IMAGE_FEATURES ?= "debug-tweaks"

Some Features

Debug tweaks enables password-less login for the root user. You must remove the debug-tweaks feature from production image.
Read-only RootFS helps to reduce wear on flash memory, and to eliminate system file corruption.
Boot splash screen
Installs debugging tools such as strace and gdb.
Installs a full SDK that runs on the device.

Image options#

Some other options also control the image content.


Determines the root filesystem image type.

If more than one format is specified, one image per format will be generated.

Image formats instructions are delivered in Poky: meta/classes/image_types.bbclass

If you have a particular layout on your storage (for example bootloader location on an SD card), you may want to create your own image type.

This is done through a class that inherits from image_types. It has to define a function named IMAGE_CMD_<type>.

Example: sdcard_image-rpi.bbclass in meta-raspberrypi


The name of the output image files minus the extension.

For example:

This file lists all the installed packages that make up the image.

Specifies the list of locales to install into the image during the root filesystem construction process.

For example:


Recipes are fundamental components in the Yocto Project environment. A Yocto/OpenEmbedded recipe is a text file with file extension .bb.

Each software component built by the OpenEmbedded build system requires a recipe to define the component. A recipe contains information about single piece of software.

Information such as:

  • Location from which to download the unaltered source
  • Any patches to be applied to that source (if needed)
  • Special configuration options to apply
  • How to compile the source files and
  • How to package the compiled output

Poky includes several classes that abstract the process for the most common development tools as projects based on Autotools, CMake, and QMake.

File Format: <base_name>_<version>.bb

Use lower-cased characters and do not include the reserved suffixes -native, -cross, -initial, or -dev.

List all recipes:

bitbake-layers show-recipes

Find a recipe in added layers:

bitbake-layers show-recipes <recipe>


Yocto/OpenEmbedded’s build tool bitbake parses a recipe and generates list of tasks that it can execute to perform the build steps:

  1. do_fetch: Fetches the source code
  2. do_unpack: Unpacks the source code into a working directory
  3. do_patch: Locates patch files and applies them to the source code
  4. do_configure: Configures the source by enabling and disabling any build-time and configuration options for the software being built
  5. do_compile: Compiles the source in the compilation directory
  6. do_install: Copies files from the compilation directory to a holding area
  7. do_package: Analyzes the content of the holding area and splits it into subsets based on available packages and files
  8. do_package_write_rpm: Creates the actual RPM packages and places them in the Package Feed area

Generally, the only tasks that the user needs to specify in a recipe are do_configure, do_compile and do_install ones.

The remaining tasks are automatically defined by the YP build system

The above task list is in the correct dependency order. They are executed from top to bottom.

You can use the -c argument to execute the specific task of a recipe.

bitbake -c compile <recipe>

Stage 1: Fetching Code (do_fetch)

Fetching is controlled mainly through the SRC_URI variable. Bitbake supports fetching source code from git, svn, https, ftp, etc.

URI scheme syntax: scheme://url;param1;param2


SRC_URI = "${PV}.tar.bz2"

Stage 2: Unpacking (do_unpack)

All local files found in SRC_URI are copied into the recipe’s working directory, in $BUILDDIR/tmp/work/.

When extracting a tarball, BitBake expects to find the extracted files in a directory named <application>-<version>. This is controlled by the S variable.

Stage 3: Patching Code (do_patch)

Sometimes it is necessary to patch code after it has been fetched.

Any files mentioned in SRC_URI whose names end in .patch or .diff or compressed versions of these suffixes (e.g. diff.gz) are treated as patches.

The do_patch task automatically applies these patches.

The build system should be able to apply patches with the -p1 option (i.e. one directory level in the path will be stripped off).

If your patch needs to have more directory levels stripped off, specify the number of levels using the striplevel option in the SRC_URI entry for the patch.

Stage 4: Configuration (do_configure)

Most software provides some means of setting build-time configuration options before compilation.

Typically, setting these options is accomplished by running a configured script with options, or by modifying a build configuration file.

  • Autotools: If your source files have a file, then your software is built using Autotools.

  • CMake: If your source files have a CMakeLists.txt file, then your software is built using CMake

  • If your source files do not have a or CMakeLists.txt file, you normally need to provide a do_configure task in your recipe unless there is nothing to configure.

Stage 5: Compilation (do_compile)

do_compile task happens after source is fetched, unpacked, and configured.

No package output

Check the do_compile in Bitbake file to make sure the compiling commands are correct, including the value of expanded variables.

Check the targets and dependencies in Makefile also.

Stage 6: Installation (do_install)

After compilation completes, BitBake executes the do_install task.

During do_install, the task copies the built files along with their hierarchy to locations that would mirror their locations on the target device.

install keyword

install not only copies files but also changes its ownership and permissions and optionally removes debugging symbols from executables.

It combines cp with chown, chmod and strip.

Not installed package

When a required package is not installed, do_rootfs will fail with errors.

The most happened error is Could not invoke dnf or No match for argument: , which is caused by do_install was not run, or by do_compile did not produce any output.

Check the do_compile and do_install in Bitbake file to make sure it call to oe-runmake if Makefile is used.

Check the targets and dependencies in Makefile also.

Stage 7: Packaging (do_package)

The do_package task splits the files produced by the recipe into logical components.

Even software that produces a single binary might still have debug symbols, documentation, and other logical components that should be split out.

The do_package task ensures that files are split up and packaged correctly.

Bitbake Variables

S : Contains the unpacked source files for a given recipe

D : The destination directory (root directory of where the files are installed, before creating the image)

WORKDIR: The location where the OpenEmbedded build system builds a recipe (i.e. does the work to create the package).

PN : The name of the recipe used to build the package

PV : The version of the recipe used to build the package

PR : The revision of the recipe used to build the package.

Example 1: Add userprog recipe

  1. Create userprog recipe by adding its folder and bb file:

    └── userprog
        ├── files
        │   ├── COPYING.MIT
        │   └── userprog.c
  2. Add code to userprog.c:

    #include <stdio.h>
    int main()
        printf("Hello World from ");
        printf("meta-my-layer/recipes-example/userprog \n");
        return 0;
  3. Declare the recipe in

    Run md5hash files/COPYING.MIT to get MD5 Checksum
    SUMMARY = "Example userprog recipe which prints out a message"
    DESCRIPTION = "Example userprog in the meta-my-layer/recipe-example/userprog"
    LIC_FILES_CHKSUM = "file://COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420"
    # source file
    SRC_URI  = "file://userprog.c"
    SRC_URI += "file://COPYING.MIT"
    # after fetching, set the source dir in build
    S = "${WORKDIR}"
    # call cross-compiler
    do_compile() {
        ${CC} userprog.c ${LDFLAGS} -o userprog
    # create directoty and install binary with permission 0755
    do_install() {
        install -d ${D}${bindir}
        install -m 0755 userprog ${D}${bindir}
    python do_before_build() {
        bb.plain("* USERPROG: before build *");
    addtask before_build before do_build
    python do_before_compile() {
        bb.plain("* USERPROG: before compile *");
    addtask before_compile before do_compile
  4. Build the recipe

    bitbake userprog

    Recipe sysroot will be built if it is not ready yet. This contains needed headers and libraries for generating binaries that run on the target architecture.

  5. Add the recipe to rootfs

    Use either:

    CORE_IMAGE_EXTRA_INSTALL_append = " userprog"
    IMAGE_INSTALL_append = " userprog"

Example 2: Use Makefile in userprog2 recipe

This example use Makefile to compile the source code.


oe-runmake will look for Makefile and can automatically call to primary target all or clean.

If Makefile has install target, Bitbake have to call oe-runmake install in do_install() function.

Directory tree:

├── files
│   ├── Makefile
│   └── userprog2.c

Source code of the program, which uses USE_SYSCALL variable passed from make:

#include <stdio.h>

int main()
    #ifdef USE_SYSCALL
        write(1, "USE_SYSCALL\n", 12);
        write(1, "Hello World from\n", 17);
        write(1, "meta-my-layer/recipes-example/userprog2\n", 40);
        printf("Hello World from\n");
    return 0;

Makefile which declares all, install, and clean.

# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings

# the name to use for both the target source file, and the output file:
TARGET = userprog2

all: $(TARGET)

    ${CC} $(CFLAGS) -o $(TARGET)  $(TARGET).c $(LDFLAGS)

    install -d $(DESTDIR)
    install -m 0755 $(TARGET) $(DESTDIR)

    rm -f $(TARGET)

The bitbake file have to send DESTDIR variable to make:
SUMMARY = "Example userprog2 recipe which prints out a message"
DESCRIPTION = "Example userprog2 in the meta-my-layer/recipe-example/userprog2"

LIC_FILES_CHKSUM = "file://COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420"

# source file
SRC_URI  = "file://userprog2.c \
            file://Makefile \
            file://COPYING.MIT \

# after fetching, set the source dir in build
S = "${WORKDIR}"

do_install() {
    oe_runmake install 'DESTDIR=${D}${bindir}'

Remote Recipe#

Yocto supports the ability to pull code from online git repositories as part of the build process.

The SRC_URI should point to a repository, with extra paramters if needed:

  • branch: The master branch by default, or set a branch in branch parameter

  • protocol: Set the protocol to https, file, or ssh.

  • tag: Select a tag revision on a branch

The SRCREV is used to set the revision, it can be ${AUTOREV} to pull the latest source revision, or the SHA1 Hash of a revision.

If the tag parameter is used, SRCREV is not needed anymore.

S = ${WORKDIR}/git

Working with Git source code

The git source code is downloaded into ${WORKDIR}/git directory, in which, you can easily update your code and commit back to the upstream.

However, there are somethings to mind:

  1. Run bitbake -c clean will wipe the git directory, you will loose editted code

  2. Run bitbake -c compile -f to force re-compile the source code with changes.

  3. Use patch files if you can not commit to the upstream

Example 3: Use git and apply patch

├── files
│   └── 001-increase-version.patch
diff --git a/userprog3.c b/userprog3.c
index 08aee7b..d593622 100755
--- a/userprog3.c
+++ b/userprog3.c
@@ -2,13 +2,14 @@

 int main()
+    printf("Patch applied!!!\n");
     #ifdef USE_SYSCALL
         write(1, "USE_SYSCALL\n", 12);
         write(1, "Hello World from\n", 17);
-        write(1, "meta-my-layer/recipes-example/userprog2\n", 40);
+        write(1, "meta-my-layer/recipes-example/userprog3\n", 40);
         printf("Hello World from\n");
-        printf("meta-my-layer/recipes-example/userprog2\n");
+        printf("meta-my-layer/recipes-example/userprog3\n");
     return 0;
SUMMARY = "Example userprog3 recipe which prints out a message"
DESCRIPTION = "Example userprog3 in the meta-my-layer/recipe-example/userprog3"

LIC_FILES_CHKSUM = "file://COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420"

# source file
SRC_URI  = "git:///${HOME}/local-userprog-repo;protocol=file \
            file://001-increase-version.patch \

# after fetching, set the source dir in build
S = "${WORKDIR}/git"

do_install() {
    oe_runmake install 'DESTDIR=${D}${bindir}'

Package files#

The do_package task splits the files produced by the recipe during do_install into logical components which are normal binary, debug binary, documents_, etc. These different files will be used in different image types.

Variables controls splitting:


List all the packages to be produced.

The default value: ${PN} ${PN}-dbg ${PN}-staticdev ${PN}-dev ${PN}-doc ${PN}-locale ${PACKAGE_BEFORE_PN}


Specify which files to include in each package by using an override to specify the package.

To use the FILES variable, provide a package name override that identifies the resulting package.

FILES_${PN} specifies the files to go into the main package.
FILES_${PN} += "${bindir}/mydir1 ${bindir}/mydir2/myfile"

use ${sysconfdir} rather than /etc, or ${bindir} rather than /usr/bin