Simple Recipes

How to write a simple Yocto recipe to fetch, install, and package software

With the project structure in place, the next step is to create a recipe of your own.

A recipe is the file that tells BitBake how to:

  • fetch the source
  • unpack it
  • configure it
  • compile it
  • install it
  • package it

These are ‘tasks’ and are represented in recipes as do_something(), e.g. do_install() is the task responsible for installing software into the staging area ready for the final image.

In practice, a recipe is just metadata that describes the build process for one piece of software.

What is a Recipe

A recipe is normally a file ending in .bb.

For example:

mytool_1.0.bb

The filename matters - it is <RECIPE_NAME>_<VERSION>.bb:

  • mytool is the recipe name
  • 1.0 is the recipe version

Yocto uses that metadata, together with the variables inside the file, to decide how the software should be built.

Where the Recipe Lives

Your recipe should live in one of your own layers.

For example:

This keeps your software metadata separate from vendor and upstream layers.

A Minimal Recipe

Here is a very simple recipe that installs a file into your image:

SUMMARY = "My first example recipe"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://mytool"

S = "${WORKDIR}"

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

This is enough to package a single local binary or script.

Core Recipe Variables

There are a few variables you will see in almost every recipe.

SUMMARY

A short description of the recipe.

LICENSE

The declared licence for the software. This must be one of the SPDX licence identifiers

LIC_FILES_CHKSUM

A checksum used to verify the referenced licence file.

SRC_URI

Where the source comes from.

S

The directory in the working tree containing the unpacked source used for the build.

LICENSE and LIC_FILES_CHKSUM

Every recipe must declare its licence.

For example:

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

The checksum exists so BitBake can detect if the licence file changes.

This is important for licence tracking and compliance, and it is one of the first things BitBake will complain about if you omit it.

SRC_URI

SRC_URI tells BitBake where to get the source.

For simple local files:

SRC_URI = "file://mytool"

For a tarball:

SRC_URI = "https://example.com/releases/mytool-1.0.tar.gz"

For a Git repository:

SRC_URI = "git://github.com/example/mytool.git;branch=main;protocol=https"
SRCREV = "0123456789abcdef0123456789abcdef01234567"

This is one of the most important recipe variables, because it controls what source BitBake fetches and stages for the build.

WORKDIR and S

BitBake places fetched and unpacked source in the working area for the recipe.

Two important variables are:

WORKDIR

The working directory for the recipe. This is created by BitBake.

S

The source directory BitBake should use when building.

For local file recipes, it is common to set:

S = "${WORKDIR}"

For unpacked source archives, S is often set automatically, but not always.

If BitBake is building from the wrong source directory, S is one of the first things to check.

Installing Files with do_install()

The do_install() task copies files into the package staging area.

For example:

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

In this example:

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

So the final file is staged under /usr/bin/mytool.

Common Path Variables

Some of the most useful install path variables are:

  • ${bindir} for /usr/bin
  • ${sbindir} for /usr/sbin
  • ${sysconfdir} for /etc
  • ${libdir} for /usr/lib
  • ${datadir} for /usr/share

Using these variables makes your recipe more portable and consistent with the rest of the build system.

Package Contents

By default, BitBake places files into packages based on standard locations.

Often, if you install to normal places like ${bindir}, you do not need to do anything extra.

Sometimes you need to extend the package contents explicitly:

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

${PN} means the main package name If you have not explicitly defined a package name, it will be the same name as the recipe.

This is a list of files that have been added to the system by the recipe - allowing for searches and package management.

A Practical First Example

Suppose you have a simple shell script called hello-demo.sh.

Your file tree might look like this:

The script in files/hello-demo.sh might contain:

#!/bin/sh
echo "Hello from Yocto"

And the recipe:

SUMMARY = "Simple demo script"
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
}

That is a perfectly valid first recipe.

Building the Recipe Standalone

Once the layer is enabled in bblayers.conf, you can build the recipe with:

bitbake hello-demo

If the recipe builds successfully, BitBake will create the package output and make it available for installation into an image.

Building just the recipe you are working on is common while you are developing it. Once you know it builds cleanly, then it can be built automatically as part of the standard image build.

Adding the Package to an Image

To include the new package in an image:

IMAGE_INSTALL:append = " hello-demo"

Then rebuild the image:

bitbake my-image

This is the point where the relationship between recipes, packages, and images becomes very clear:

  • the recipe builds hello-demo
  • the package is emitted
  • the image installs that package

Recipe Versions

You can have multiple versions of the same recipe by having different version numbers in the filename, e.g.

  • hello-demo_1.0.bb
  • hello-demo_1.1.bb
  • hello-demo_2.0.bb

By default, BitBake will use the highest version number for the build. If you want to specify a version to use, BitBake will look for a variable, PREFERRED_VERSION_<packagename> = XXX.

In your image file configuration, you can write:

PREFERRED_VERSION_hello-demo = "1.1"

Extending Existing Recipes with .bbappend

Sometimes you do not need a new recipe.

If an upstream, vendor, or community layer already provides the recipe, and you only need to adjust it for your project, use a .bbappend file in your own layer.

A .bbappend file extends or adjusts an existing recipe without modifying the original recipe file. This lets you modify how the original recipe behaves while keeping your changes in your own layer (and part of the reason why it is good to have the split between your layers and third-party layers).

For example, if an upstream layer provides:

busybox_1.36.0.bb

you can create:

busybox_1.36.0.bbappend

or, more commonly:

busybox_%.bbappend

Wildcards

The character ‘%’ in BitBake is a wildcard character and can match anything within the filename or version sections of packages.

Matching Versions

This is the most common use case where you want the % to match different versions of a package.

linux-yocto_%.bbappend

will match all versions of linux-yocto.

linux-yocto_6.%.bbappend

will match all 6.x versions of linux-yocto.

linux-yocto_6.6.%.bbappend

will match all 6.6.x versions of linux-yocto.

Matching Names

As an example, there are many versions of the Linux kernel - especially as manufacturers tweak their kernel sources instead of pushing the changes up to the mainstream kernel source. This now means as you add various layers you may end up with:

  • linux-yocto
  • linux-boundary
  • linux-kontron
  • linux-qoriq
  • linux-toradex
  • linux-variscite

(These are examples from adding a single layer - meta-freescale-3rdparty)

If your change was applicable to all the kernels, you could have an append of:

linux-%.bbappend 

Wildcards in PREFERRED_VERSION

You can also have wildcards in PREFERRED_VERSION, e.g.

PREFERRED_VERSION_hello-demo = "1.%"

Would mean that you wanted to have the highest version of the 1.x releases.

When to Use a .bbappend

Use a .bbappend when:

  • the base recipe already exists
  • you only need to tweak or extend it
  • the behaviour is specific to your project, machine, or distro

Typical examples include:

  • adding a patch
  • overriding a file
  • adding configuration fragments
  • appending dependencies
  • changing install behaviour
  • adding package content

When to Create a New Recipe Instead

Use a new recipe when:

  • the software does not already have a recipe
  • you are introducing a new application, library, or tool
  • you are packaging your own source tree
  • the behaviour is fundamentally different, not just a tweak

Use a .bbappend when the recipe already exists and you only need to modify it.

Example: Appending a Recipe

Suppose an upstream recipe installs a default configuration file that you want to replace.

You might create:

meta-my-distro/
└── recipes-core/
    └── base-files/
        ├── base-files_%.bbappend
        └── files/
            └── issue

And the append file might contain:

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

This tells BitBake to also search your local files/ directory when resolving file://... entries.

Why FILESEXTRAPATHS Matters

Many append files need extra local files:

  • replacement config files
  • patches
  • service files
  • scripts

Without updating FILESEXTRAPATHS, BitBake may not know where to find those files.

This is one of the most common reasons a new .bbappend appears not to work.

Example: Adding a Patch

If you want to patch an upstream recipe, your append might look like this:

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI:append = " file://fix-startup.patch"

And your patch would live under:

recipes-example/example/files/fix-startup.patch

Using Wildcards in Append Files

You will often see:

foo_%.bbappend

This means the append should apply regardless of the exact version of the recipe.

That is often convenient, because you do not need to rename your append file every time the upstream recipe version changes.

However, it is sometimes better to target a specific version if:

  • the append depends on version-specific behaviour
  • the patch only applies to one release
  • you want upgrades to fail obviously rather than silently

How to Decide Where a Change Belongs

A useful mental checklist is:

  1. Does the recipe already exist?
  2. Is this a small extension or override?
  3. Is the change board-specific, distro-specific, or application-specific?

If the recipe exists and you are only extending it, use a .bbappend in the layer that matches the responsibility of the change.

Common Mistakes

Forgetting to Add the Layer

If your layer is not in bblayers.conf, BitBake will not find the recipe.

Installing to the Wrong Place

If you install files outside the standard paths, BitBake may not package them the way you expect unless you update FILES:${PN}.

Using Host Paths

Do not write recipes that install directly to paths on your host. Always install through ${D}.

Forgetting the Licence Checksum

Missing or incorrect LIC_FILES_CHKSUM is one of the most common errors in new recipes.

Editing Upstream Metadata Directly

This makes upgrades painful and hides what is actually project-specific.

Forgetting FILESEXTRAPATHS

Your append may parse correctly but still fail to find the files you intended to add.

Using a .bbappend for Something That Should Be a New Recipe

If you are adding a completely new software component, write a proper recipe instead of trying to force it into an append.

How to Inspect What Happened

Useful commands include:

bitbake hello-demo
bitbake -e hello-demo | less

And useful variables to inspect include:

  • WORKDIR
  • S
  • D
  • PACKAGES
  • FILES:${PN}

If the output is not what you expected, these usually show where the logic went wrong.

A Good First Milestone

Your first recipe does not need to compile a complex C application.

A good first milestone is simply:

  • create a recipe in your own layer
  • fetch a local file
  • install it into ${bindir}
  • build it successfully
  • add it to an image

Once that works, moving on to source archives, Git fetches, patches, and build systems becomes much easier.

Summary

A recipe tells BitBake how to fetch, install, and package software.

The main ideas from this lesson are:

  • a .bb file defines how one software component is built
  • SRC_URI tells BitBake where the source comes from
  • S controls the source directory used for the build
  • do_install() stages files into ${D}
  • path variables like ${bindir} keep recipes portable
  • images install the packages produced by recipes
  • .bbappend files extend existing recipes without changing upstream metadata
  • use FILESEXTRAPATHS when an append adds local files

Quick quiz: first recipe

A short review of the basic recipe-building workflow.

Question 1 A recipe should install a script shipped in your layer, but nothing needs compiling. Which part of the recipe still matters immediately?
Question 2 Why is installing directly to `/usr/bin` on the host wrong inside `do_install()`?
Question 3 You run `bitbake hello-demo`, but BitBake says nothing provides it. What is the most likely first check?
Question 4 A vendor recipe already exists and you only need to add one patch plus one config file from your own layer. What is the best pattern?
Question 5 Your `.bbappend` parses, but BitBake cannot find the extra patch file you referenced. Which variable is the most likely missing piece?
Question 6 Which change is the clearest sign that you should write a new recipe instead of a `.bbappend`?