Lesson
BitBake Tasks and Classes
Understanding task execution, recipe flow, and how classes provide reusable build behaviour
Once you have written a recipe, the next question is simple: what actually happens when BitBake builds it?
The answer is that BitBake does not treat a recipe as one large build script. Instead, it works through a sequence of named tasks, with each task responsible for one stage of the build. A lot of the behaviour in those tasks also comes from classes that the recipe inherits.
If you understand tasks and classes, recipes become much easier to read, debug, and customise.
The Big Picture
At a high level, BitBake usually works through a flow like this:
- fetch the source
- unpack the source
- apply patches
- configure the software
- compile the software
- install files into a staging area
- split those files into packages
That sequence is expressed through tasks.
For example, when you run:
bitbake mytool
BitBake does not only run one command. It decides which tasks are needed, checks their dependencies, reuses cached results where possible, and then executes the required task graph in the right order.
Common Tasks
Some of the most important tasks you will see are:
- do_fetch
-
Download or retrieve the source defined by
SRC_URI. - do_unpack
-
Unpack the fetched source into the working area.
- do_patch
-
Apply patches listed in
SRC_URI. - do_configure
-
Prepare the source tree for compilation.
- do_compile
-
Build the software.
- do_install
-
Copy the built files into the package staging area.
- do_package
-
Split installed files into packages.
- do_rootfs
-
Install selected packages into the final root filesystem for an image.
You do not define every one of these tasks yourself. In many recipes, several come from inherited classes.
A Simple Recipe Flow
Consider our demo recipe:
SUMMARY = "Simple demo tool"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://hello-demo.sh"
S = "${WORKDIR}"
do_install() {
install -d ${D}${bindir}
install -m 0755 ${WORKDIR}/hello-demo.sh ${D}${bindir}/hello-demo.sh
}
Even though the recipe only defines do_install(), BitBake still knows how to
run the earlier and later stages around it.
That means:
do_fetchgets the local file fromSRC_URIdo_unpackstages it in the working areado_installcopies it into${D}do_packageturns the installed result into one or more packages
This is one of the most important Yocto ideas: you usually describe behaviour by adding metadata to an existing build framework, not by writing every step from scratch.
Tasks Are Connected by Dependencies
Tasks do not run in isolation.
For example, do_compile normally depends on earlier work such as fetching,
unpacking, patching, and configuration. do_install depends on compilation
finishing successfully. Packaging depends on installation.
That dependency structure is what allows BitBake to:
- execute tasks in the correct order
- skip work that is already up to date
- rebuild only the parts affected by a change
- share cached results when possible
This is why Yocto builds can be both powerful and sometimes confusing. A small metadata change may affect one task, several tasks, or an entire chain of tasks depending on what changed.
Running a Specific Task
You do not always need to run the full recipe build.
If you just want to run a specific task (with dependencies) you can use the -c
flag with BitBake,
bitbake -c <TASK> <RECIPE>
Where <TASK> is the name of the task without the do_, e.g.
bitbake -c fetch mytool
bitbake -c unpack mytool
bitbake -c compile mytool
bitbake -c install mytool
This is extremely useful when debugging.
For example:
- use
-c fetchif you suspect the source URL is wrong - use
-c patchif a patch is failing - use
-c compileif the build system is misconfigured - use
-c installif files are not being staged where you expect
What do_install() Really Does
One common mistake is thinking do_install() places files directly on
the target image.
It does not.
do_install() copies files into a staging area, usually under ${D}, which is
inside the recipe’s work area. Later tasks package those files, and image tasks
decide which packages should actually be installed into the final root filesystem.
That separation is why Yocto can:
- generate several packages from one recipe
- decide which packages belong in which image
- support development packages, debug packages, and runtime packages separately
What Classes Are
A class is a reusable piece of BitBake metadata. These often contain python functions that are useful in many different places.
Classes usually live in .bbclass files and provide shared behaviour that many
recipes can inherit.
For example:
inherit cmake
or:
inherit autotools pkgconfig
When a recipe inherits a class, it gains variables, task implementations, helper functions, and defaults defined by that class.
This keeps recipes shorter and more consistent.
Why Classes Matter
Without classes, every recipe would need to manually define how to configure and compile its software.
That would create a lot of repetition.
Instead, classes capture common patterns such as:
- building an Autotools project
- building a CMake project
- integrating
pkg-config - installing a
systemdservice - handling Python packaging
This means the recipe can focus on the parts that are specific to that piece of software rather than reimplementing the whole build process.
Common Classes
Some very common classes include:
- autotools
-
Provides standard configure, compile, and install behaviour for Autotools projects.
- cmake
-
Provides build behaviour for projects that use CMake.
- pkgconfig
-
Helps ensure
pkg-configintegration works correctly during the build. - systemd
-
Adds support for packaging and enabling
systemdservice units. - kernel
-
Provides shared kernel recipe behaviour.
- core-image
-
Provides standard behaviour for image recipes.
You do not need to memorise every class immediately. The important thing is to
recognise that much of the recipe behaviour is often inherited rather than
defined directly in the .bb file.
Example: An Autotools Recipe
A recipe for a normal Autotools project might look like this:
SUMMARY = "Example application"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "git://github.com/example/myapp.git;branch=main;protocol=https"
SRCREV = "0123456789abcdef0123456789abcdef01234567"
inherit autotools pkgconfig
That recipe may not define do_configure(), do_compile(), or do_install()
explicitly at all.
That is because the autotools class already provides the standard logic for
those stages.
If the project mostly follows the expected Autotools pattern, the recipe can stay very small.
Customising Inherited Behaviour
Inheriting a class does not mean you lose control.
You can still customise behaviour in a few common ways:
- set variables that the class uses
- append to or prepend task functions
- override a task completely if necessary
For example, you might add extra configure options:
EXTRA_OECONF += "--enable-tools"
Or extend a task:
do_install:append() {
install -d ${D}${sysconfdir}/myapp
install -m 0644 ${WORKDIR}/myapp.conf ${D}${sysconfdir}/myapp/myapp.conf
}
This is usually better than replacing the whole task, because it keeps the class behaviour intact and only adds the project-specific part you need.
Prefer Extending Over Replacing
If a class already provides working behaviour, try to extend it before replacing it.
For example, this is often a good approach:
do_install:append() {
install -m 0755 extrascript ${D}${bindir}/extrascript
}
Whereas replacing the full do_install() task should normally be a deliberate
decision:
do_install() {
...
}
Inspecting the Environment with bitbake -e
When a recipe behaves differently from what you expected, a useful tools is:
bitbake -e mytool
This shows all the expanded BitBake environment variables for that recipe.
This is different from the bitbake-getvar in the variables lesson
in that it returns all of the variables not just the specific one you may be looking for.
It helps you answer questions such as:
- what is the final value of
S? - which class set this variable?
- what does
EXTRA_OECONFexpand to? - which overrides are active?
The output is large, but it is one of the best ways to understand how BitBake arrived at the final metadata for a recipe.
Looking at Task Work Directories
When you need to inspect what happened during a task, the recipe work area is often the next place to look.
You will typically find useful files under paths like:
tmp/work/<machine>/<recipe>/<version>/
Important subdirectories often include:
temp/for task logs and run scripts- the unpacked source tree
- staged install output before packaging
This is where you can inspect:
- the exact command script BitBake generated for a task
- the log from a failed compile or install
- the patched source tree as BitBake saw it
Task Logs
When a task fails, the logs in temp/ are usually far more useful than the short
error message printed at the end of the build.
For example, you may find files such as:
log.do_compile
run.do_compile
log.do_install
run.do_install
The log.* files show what happened.
The run.* files show the generated shell script or command flow BitBake used for
that task.
If you are ever unsure whether the problem is in your recipe or inside the upstream build system, these files often answer that very quickly.
A Helpful Mental Model
When reading a recipe, it helps to think in layers:
- What metadata is written directly in this recipe?
- Which classes does it inherit?
- Which tasks come from those classes?
- Which variables modify that behaviour?
- Which task is actually failing or producing the wrong result?
This mental model makes Yocto much less mysterious.
Instead of seeing a recipe as a block of magic, you begin to see it as:
- metadata
- inherited behaviour
- task execution
- packaged output
A Practical Debug Example
Suppose a recipe inherits cmake, but the build fails during configuration.
A sensible debugging path might be:
- Run
bitbake -c configure myapp - Inspect
tmp/work/.../temp/log.do_configure - Check
bitbake -e myappto see the final values ofS,B, and related variables - Confirm that the source tree really uses CMake and not some other build system
- Add or adjust the variables that the
cmakeclass expects
That process is much more effective than immediately rewriting the recipe from scratch.
Summary
BitBake tasks describe the stages of the build.
Classes provide reusable implementations and defaults for common build patterns.
Together, they are what make Yocto recipes compact, flexible, and composable.
If you understand:
- the common task sequence
- where inherited behaviour comes from
- how to inspect task logs and the expanded environment
then you have a much stronger foundation for writing and debugging recipes.
Check your understanding
Quick quiz: tasks and classes
Check your understanding of task flow and inherited behaviour.