Working with Variables

How to set, override, and manipulate variables in the Yocto Project

Variables are one of the most important parts of working with the Yocto Project. They control almost everything:

  • where files are installed
  • which packages are built
  • how recipes behave
  • how images are configured
  • how machine and distro specific behaviour is selected

If you are comfortable with variables, the rest of Yocto starts to make much more sense.

Why Variables Matter

BitBake metadata is mostly about describing build behaviour through variables.

A recipe does not have to hard-code paths such as /usr/bin or /etc. Instead, they often makes use of variables such as ${bindir} and ${sysconfdir}.

Variable types

There are two types of variables:

  • Global Variables
    • Defined in configuration files (.conf)
    • By convention in uppercase, e.g. MACHINE
  • Local Variables
    • Defined in recipes
    • Values are strings
      • INITRAMFS_IMAGE_BUNDLE ?= "1"

A distro does not usually hard-code every package list in every image either. It defines defaults through variables that can then be extended or overridden.

This makes the system flexible, but it also means you need to understand:

  • how to assign a value
  • when a value is expanded
  • how to add to or remove from an existing value
  • how overrides affect the final result

Common Assignment Operators

The most common operators are:

Setting a Value

=

Set a variable absolutely. BitBake will take the value assigned at that point in time. The only way to change it would be to use another absolute assignment in a later recipe.

VAR = "value"
?=

Set a value but only if the variable has not previously been set somewhere else. This is useful for default values, where the user may want to override it in their own recipe.

Use ?= when you want to supply a default only if nobody has already provided a value.

VAR ?= "value"
??=

Set a weak default value that may still be replaced later. This also sets a default value, but waits until the end of the processing to commit it. This means that a later recipe can also override the value.

VAR ??= "value"

Multi-line Assignments

You can assign a variable value that goes over multiple lines by escaping the ‘new-line’. This is often used to make the assignment easier to read.

VAR = "This value \
  needs multiple lines \
  to make it more readable \
"

Changing a Value

:append

Append text to the final value of a variable.

:prepend

Prepend text to the final value of a variable.

:remove

Remove matching text from the final value of a variable.

Appending and Prepending

If you want to add to an existing variable, the safest modern approach is usually to use :append or :prepend.

IMAGE_INSTALL:append = " htop strace"

You can also prepend:

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

This pattern is common in .bbappend files when extending the search path for additional files.

Removing Values

Use :remove when you need to remove something from a value that has already been built up elsewhere.

DISTRO_FEATURES:remove = "x11"
IMAGE_INSTALL:remove = "nano"

This is much cleaner than trying to reconstruct the whole variable by hand.

Immediate Expansion with :=

Normally, many variables are expanded later when BitBake actually needs them. Sometimes you want the right-hand side to be expanded immediately.

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

This is important because THISDIR depends on the file being parsed. If you wait until later expansion, you may no longer get the value you expected.

Overrides

Overrides let you set different values depending on context such as:

  • the package
  • the machine
  • the distro
  • the recipe task

For example:

SRC_URI:append:qemux86 = " file://qemu-extra.cfg"
IMAGE_INSTALL:append:pn-core-image-minimal = " strace"

This means the change only applies when that specific override is active.

Overrides are one of the most powerful parts of the Yocto Project, but also one of the easiest places to create confusing behaviour if you forget the context in which the variable is being evaluated.

Basic Expansion

Variables are usually referenced using ${...} syntax.

MYDIR = "/opt/demo"
MYFILE = "${MYDIR}/config.txt"

In that example, MYFILE expands to /opt/demo/config.txt.

You will see this style everywhere in Yocto metadata.

Variable Expansion in Shell Tasks

Inside shell tasks, BitBake variables are expanded before the shell runs.

do_install() {
    install -d ${D}${bindir}
    install -m 0755 mytool ${D}${bindir}/mytool
}

In that example:

  • ${D} is the destination staging area
  • ${bindir} is usually /usr/bin

So the file is installed into the package staging area under /usr/bin.

Inline Python Expansion

Sometimes the value you need depends on logic rather than a fixed string.

ROOTFS_DEVICE = "${@'/dev/mmcblk0' if d.getVar('IMAGE_BASENAME') \
 == 'dev-image' else '/dev/mmcblk1'}"

This makes use of inline Python:

  • d.getVar() reads a BitBake variable
  • the expression returns the string that becomes the final value

Use this carefully. It is useful, but large amounts of inline Python can make metadata hard to read.

Order of Evaluation

Common Patterns

Add a Package to an Image

IMAGE_INSTALL:append = " i2c-tools"

Add a Distro Feature

DISTRO_FEATURES:append = " systemd"

Remove a Distro Feature

DISTRO_FEATURES:remove = "x11"

Set a Package-Specific Variable

RDEPENDS:${PN} += "bash"

${PN} means “the package name for this recipe”.

Add Files to a Package

FILES:${PN} += " /usr/local/bin/mytool"

This is a common pattern when a recipe installs something outside the usual default packaging locations.

Common Mistakes

Forgetting the Leading Space

This is one of the most common mistakes:

IMAGE_INSTALL:append = "htop"

That will usually produce a broken combined value.

The correct form is:

IMAGE_INSTALL:append = " htop"

Overwriting Instead of Extending

If you write:

DISTRO_FEATURES = "systemd"

you replace the whole variable.

That may be what you want, but often what you really meant was:

DISTRO_FEATURES:append = " systemd"

Using Immediate Expansion Without Needing It

:= is useful, but do not use it everywhere. If you do not need immediate expansion, prefer the simpler form.

How to Inspect Variable Values

One of the most useful debugging commands is:

bitbake-getvar <variable-name>

For example:

bitbake-getvar DISTRO_FEATURES
NOTE: Starting bitbake server...
#
# $DISTRO_FEATURES [6 operations]
#   set? /home/ming/wip/devheads/repos/ECW_Yocto/build/../layers/project/meta-ecw/meta-ecw-core/conf/distro/microforge.conf:20
#     "${DISTRO_FEATURES_DEFAULT} ${MICROFORGE_DEFAULT_DISTRO_FEATURES}"
#   :remove /home/ming/wip/devheads/repos/ECW_Yocto/build/../layers/project/meta-ecw/meta-ecw-core/conf/distro/microforge.conf:21
#     "x11"
#   set? /home/ming/wip/devheads/repos/ECW_Yocto/build/../layers/third-party/poky/meta/conf/distro/include/default-distrovars.inc:29
#     "${DISTRO_FEATURES_DEFAULT}"
#   :append /home/ming/wip/devheads/repos/ECW_Yocto/build/../layers/third-party/poky/meta/conf/distro/include/init-manager-systemd.inc:2
#     " systemd usrmerge"
#   set /home/ming/wip/devheads/repos/ECW_Yocto/build/../layers/third-party/poky/meta/conf/documentation.conf:145
#     [doc] "The features enabled for the distribution."
#   set? /home/ming/wip/devheads/repos/ECW_Yocto/build/../layers/third-party/poky/meta/conf/bitbake.conf:903
#     ""
# pre-expansion value:
#   "${DISTRO_FEATURES_DEFAULT} ${MICROFORGE_DEFAULT_DISTRO_FEATURES} systemd usrmerge"
DISTRO_FEATURES="acl alsa bluetooth debuginfod ext2 ipv4 ipv6 pcmcia usbgadget usbhost wifi xattr nfs zeroconf pci 3g nfc  vfat seccomp usrmerge systemd systemd-resolved networkd  systemd usrmerge"

This will show you not only the final value of variable, but also, importantly, which file(s) set the values.

If a variable is not behaving the way you expect, bitbake-getvar is usually the best place to start.

Summary

Variables are the language of Yocto metadata.

The main ideas from this lesson are:

  • use = for normal assignment
  • use ?= and ??= for defaults
  • use :append, :prepend, and :remove to manipulate existing values
  • use := only when immediate expansion is required
  • remember that overrides change values depending on context
  • use bitbake-getvar to inspect the final value when debugging

Quick quiz: Yocto variables

A few short checks before you move on.

Question 1 You want to add `htop` to an existing image package list without replacing what is already there. Which form is most appropriate?
Question 2 What is the actual problem with writing `IMAGE_INSTALL:append = "htop"` for a space-separated list?
Question 3 Which case is the best reason to use `:=` instead of normal deferred expansion?