Yocto 02: How to Create a Custom Meta Layer
Building a Yocto distribution only from ready-made meta-layers is like baking brownies from a box — quick and decent, but rarely the perfect dessert. To really match your taste, you’ll need to add your own ingredients with custom layers and recipes.
This tutorial is a continuation of the Yocto series. Did you setup your raspberry pi distribution already? If not, it’s highly recommended to go through the first article of the series, describing the setup in detail.
Yocto Theory#
Before getting into the practical details, let’s first talk the theory. To understand the operations done in the following subsections, two crucial Yocto concepts have to be introduced, recipes and meta-layers.
recipes#
A Yocto recipe is like a kitchen recipe in that it lays out, step by step, how to gather and prepare the ingredients (what to fetch, patch, configure, build, package). It’s a script interpreted by Yocto’s chef and the build engine - bitbake
. Recipes usually are named after the package they describe and have a *.bb
(bb for bitbake) extension. A typical recipe instructs where to fetch the resources from, be it a git repository or a remotely hosted tar archive, then how to unpack them, apply patches, configure the build system, compile the sources, and finally package the results. The recipe itself does not perform these actions, it only describes them. It is BitBake that interprets the recipe and executes the corresponding tasks.
You’re likely asking what kind of resources can be fetched and build, am I right? Well, almost anything. Anything that is downloadable of copyable (as resources stored on a local disk are valid, too) and available under a url or a file path will do. A library, a whole application, a gallery of images (rather unusual for an embedded system), even the full Linux kernel. The build step is optional, as all those things can be pre-build binaries, just make sure that they are in fact compatible with the target system CPU architecture.

A very unique characteristic of Yocto is that recipes can be stacked together, or in the language of the framework - appended. Imagine a basic brownie recipe: the flour, milk, and chocolate that make up the dough, along with the oven temperature and baking time, are all listed there. That’s a complete recipe for a Basic Brown Brownie. Now, imagine a brown brownie with cherries on top. It’s almost the same, with only a single thing added. How should that change be applied to the original recipe? In the world of Yocto, if the original recipe were on a paper page, a single line of "add cherries on top of the dough" would be written on a sticky note, attached at the bottom of the page. Each further tweak gets its own note, stacked in order, until the updated recipe is complete.
Those sticky notes share the same name as the original recipe, but with the extension *.bbappend
.
meta-layers#
Recipes are usually grouped into larger chapters based on the type of treat: brownies and donuts fit well into Desserts, while Wiener Schnitzel alongside the potato salad belongs in a chapter on German Dinners. In Yocto, these chapters are called meta-layers, each devoted to a single domain such as networking features, multimedia, or providing support for a programming language like Python. However, meta-layers are more than just chapters in a cookbook. They not only group recipes into logical domains, but also define how changes stack: the order of the layers and their priority decides the order of application of the *.bbappend
files - our sticky notes. In case of two meta-layers defining a recipe with the same name, only the one with the higher priority is parsed. The configuration of each meta-layer is stored in a conf/layer.conf file and must contain variables:
BBPATH
: the root directory of the meta-layer.BBFILES
: Defines the location for all recipes in the layer.BBFILE_COLLECTIONS
: name of the layer, to whichmeta-
is prepended. This name is used to refer to the layer in the other Yocto components. For exampleraspberrypi
in case ofmeta-raspberrypi
layer.BBFILE_PATTERN
: Expands immediately during parsing to provide the directory of the layer.BBFILE_PRIORITY
: Already covered priority, the higher the value, the more important the layer is and will overwrite layer of less importance.LAYERVERSION
: The version number for the layer.LAYERDEPENDS
: Lists all layers on which this layer depends (if any).LAYERSERIES_COMPAT
: Lists the Yocto Project releases for which the current version is compatible.
More about the meta-layers and their configuration is available at the official Yocto documentation
Meta-layers contain other files than just the recipes and the *.bbappend
files. They also hold *.bbclass
files, as well as machine and distribution configuration files. These are important topics, but they’re out of scope for now - I’ll cover them another time. For this article, let’s focus only on recipes. One particular category deserves special mention: the image recipe. This type of recipe defines which packages are included in the final flashable image.
Image recipe#
An image recipe is like a wedding banquet menu - a full multi-course plan that lists which dishes (packages) will be served, but leaves the cooking steps to the individual recipes (*.bb
recipes). The recipe determines the contents of the final image, which is adjusted by appending package names to the IMAGE_INSTALL
variable. More on that in the Practice section.
Practice#
Now, that the basic Yocto terms are covered, let’s move into the actual work. Below subsections assumes the knowledge from the previous tutorial on setting up the RPI environment. Start by sourcing the bitbake environment from the poky
meta-layer:
source sources/poky/oe-init-build-env
Create a meta-layer#
A meta layer can be created in at least two ways, either with the use of a bitbake-layers
script or manually, by creating a new directory with an adequate structure of the <layer-name>/conf/. In the case of the latter, the best is to start by copying an existing meta-layer.
bitbake-layers#
bitbake-layers create-layer --help NOTE: Starting bitbake server... usage: bitbake-layers create-layer [-h] [--add-layer] [--layerid LAYERID] [--priority PRIORITY] [--example-recipe-name EXAMPLERECIPE] [--example-recipe-version VERSION] layerdir Create a basic layer positional arguments: layerdir Layer directory to create optional arguments: -h, --help show this help message and exit --add-layer, -a Add the layer to bblayers.conf after creation --layerid LAYERID, -i LAYERID Layer id to use if different from layername --priority PRIORITY, -p PRIORITY Priority of recipes in layer --example-recipe-name EXAMPLERECIPE, -e EXAMPLERECIPE Filename of the example recipe --example-recipe-version VERSION, -v VERSION Version number for the example recipe
bitbake-layers
is rather simple to get around, use --help
if confused. Before setting up a meta-layer, figure out its name, location and the priority. The the tutorial purpose the layer will be called meta-custom and located in sources. Below command assumes that build is working directory, as it’s the default behavior after sourcing the bitbake environment.
bitbake-layers create-layer ../sources/meta-custom/ --add-layer --example-recipe-name custom-image
The command creates below file structure and the new layer is already appended to the build/conf/bblayers.conf.
meta-custom/ ├── conf │ └── layer.conf ├── COPYING.MIT ├── README └── recipes-custom-image └── custom-image └── custom-image_0.1.bb
manual setup#
The same file structure can be achieved manually, however my advice is to name the directories slightly differently, moreover, in our case the license file is not a necessity as the LICENSE
variable in the image recipe can simply be set to "CLOSED"
. The easier to read file structure is as below:
meta-custom/ ├── conf │ └── layer.conf ├── README └── recipes-custom └── images └── custom-image_0.1.bb
layer.conf#
layer.conf stores the configuration of the meta layer; by default bitbake-layers
generates it in below form, it’s a good example to follow:
# 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 += "meta-custom" BBFILE_PATTERN_meta-custom = "^${LAYERDIR}/" BBFILE_PRIORITY_meta-custom = "6" LAYERDEPENDS_meta-custom = "core" LAYERSERIES_COMPAT_meta-custom = "scarthgap"
In case of the --priority
parameter being passed to the bitbake-layers create-layer
command, the BBFILE_PRIORITY_meta-custom
variable is adjusted, however by default it’s set to 6.
The Image recipe#
Finally, let’s practice the image recipe. Here, the final list of the packages present on the operating system is put together with either of two variables IMAGE_FEATURES
and IMAGE_INSTALL
. They are quite similar to each other, the difference being that IMAGE_INSTALL
lists single packages, whilst IMAGE_FEATURES
manages groups of packages as a single feature may involve multiple packages - those are managed by the Yocto itself. To create an actual image from the image recipe, one of the directives inherit image
or inherit core-image
has to be used, here, the choice is inherit core-image
as it provided the openssh feature.
What should a basic image for a raspberry pi device have? It all depends on the usecase of the device, but there are must-haves like an ssh server, enabling remote communication with the device - this is a feature. A package that likely will turn out useful is python3
- containing the Python 3 language. A bit less useful may be a C library such as mosquitto
or an audio server - pipewire
. All three of them are packages, that can be appended to the IMAGE_INSTALL
variable. The customization of images is tremendously well described in the official Yocto Project documentation, so go there to learn more! An image file with all of those changes applied would look like below:
SUMMARY = "simple image recipe" DESCRIPTION = "simple image recipe" LICENSE = "CLOSED" python do_display_banner() { bb.plain("*******************************************************************"); bb.plain("* *"); bb.plain("* A custom recipe: ssh server, python, pipewire and mosquitto *"); bb.plain("* *"); bb.plain("*******************************************************************"); } addtask display_banner before do_build inherit core-image IMAGE_FEATURES += "ssh-server-openssh" IMAGE_INSTALL:append = " python3" IMAGE_INSTALL:append = " mosquitto" IMAGE_INSTALL:append = " pipewire"
The append instructions is new here. Just like its name says, it appends to the variable it follows, meaning that after all the appends are parsed the IMAGE_INSTALL
variable has a value of ` python3 mosquitto pipewire`.
Build the image#
Assuming that the image recipe name is indeed custom-image.bb
and that the layer is already added to the bblayer.conf
file, to build the image, running below command should be enough
bitbake custom-image
A successfully run build command should send below output to the stdout:
$ bitbake custom-image Loading cache: 100% |#########################################################################################################################################################################################################| Time: 0:00:04 Loaded 4746 entries from dependency cache. NOTE: Resolving any missing task queue dependencies Build Configuration: BB_VERSION = "2.8.0" BUILD_SYS = "x86_64-linux" NATIVELSBSTRING = "universal" TARGET_SYS = "aarch64-poky-linux" MACHINE = "raspberrypi4-64" DISTRO = "poky" DISTRO_VERSION = "5.0.11" TUNE_FEATURES = "aarch64 crc cortexa72" TARGET_FPU = "" meta meta-poky meta-yocto-bsp = "HEAD:792d18b4cb2451b00280641403e6eaf37bd6e53f" meta-raspberrypi = "HEAD:8e9ec2685a902038d1d6ad20f0821ee5655432a9" meta-oe meta-multimedia meta-networking meta-python = "HEAD:e8fd97d86af86cdcc5a6eb3f301cbaf6a2084943" meta-custom = "<unknown>:<unknown>" Sstate summary: Wanted 1180 Local 0 Mirrors 0 Missed 1180 Current 2044 (0% match, 63% complete)########################################################################################################## | ETA: 0:00:01 Initialising tasks: 100% |####################################################################################################################################################################################################| Time: 0:00:11 NOTE: Executing Tasks ******************************************************************* * * * A custom recipe: ssh server, python, pipewire and mosquitto * * * ******************************************************************* NOTE: Tasks Summary: Attempted 6628 tasks of which 4471 didn't need to be rerun and all succeeded.
Now the image is available at tmp/deploy/images/raspberrypi4-64/custom-image-raspberrypi4-64.rootfs.wic.bz2. Flash it, upload it, do whatever you need to make it run on your device!