Creating Yocto Recipes

How to create a practical Yocto recipe for a small C application and add it to an image

Recipes are the normal way to add your own software to a Yocto build.

An image recipe decides which packages belong in the final root filesystem. A normal software recipe decides how one component is fetched, built, installed, and packaged so an image can use it.

This lesson builds a small C application recipe from local files in your own layer. That keeps the example small enough to inspect, while still covering the same structure you use for larger applications.

What a Recipe Does

A recipe is a .bb file read by BitBake.

It describes:

  • where the source comes from
  • which licence applies
  • which build-time and runtime dependencies are needed
  • how to configure and compile the software
  • which files are installed into the package staging area
  • how those files become packages

The recipe does not install directly into the final image. It installs into ${D}, BitBake packages that staged output, and the image build later installs the selected package.

Where Recipes Live

Keep your own recipes in your own layer.

For an application recipe, a common layout is:

The filename follows this pattern:

<recipe-name>_<version>.bb

For example, hello-recipe_1.0.bb gives you:

  • recipe name: hello-recipe
  • recipe version: 1.0

The layer must also be enabled in conf/bblayers.conf. If the layer is not in BBLAYERS, BitBake will not see the recipe at all.

Recipe File Structure

Most simple recipes have the same basic shape:

SUMMARY = "Short one-line description"
DESCRIPTION = "Longer description of what the recipe builds."
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://hello-recipe.c"
S = "${WORKDIR}"

DEPENDS = ""
RDEPENDS:${PN} = ""

do_compile() {
    ${CC} ${CFLAGS} ${LDFLAGS} ${S}/hello-recipe.c -o hello-recipe
}

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

The exact tasks may come from a class such as cmake, autotools, or meson. For this small example, the recipe defines the compile and install steps directly.

Key Variables

You will see these variables often when reading or writing recipes.

SUMMARY

A short one-line description. This appears in package metadata and is useful when searching recipes.

DESCRIPTION

A longer explanation of what the software does. Use it when SUMMARY is not enough.

LICENSE

The licence expression for the software. Use SPDX identifiers where possible, such as MIT, BSD-3-Clause, or GPL-2.0-only.

LIC_FILES_CHKSUM

A checksum for the licence text. BitBake uses this to notice if the licence file changes.

SRC_URI

The source inputs for the recipe. This can point to local files, patches, archives, or source repositories.

SRCREV

The exact revision to fetch when SRC_URI uses source control, especially Git.

S

The source directory used by configure and compile tasks after fetch and unpack have completed.

DEPENDS

Build-time recipe dependencies. Use this when headers, libraries, tools, or native utilities are needed during the build.

RDEPENDS

Runtime package dependencies. Use RDEPENDS:${PN} for packages needed on the target when the main package is installed.

For a Git-based recipe, SRC_URI and SRCREV usually look like this:

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

S = "${WORKDIR}/git"

Pinning SRCREV makes the build reproducible. Avoid using a moving branch head for production builds.

Important Recipe Tasks

BitBake runs recipes as a graph of tasks. These are the main ones to understand when creating a recipe:

do_fetch

Retrieves everything listed in SRC_URI.

do_unpack

Unpacks archives or stages fetched files into the recipe work directory.

do_configure

Prepares the source tree for compilation. Build classes often provide this.

do_compile

Builds the software.

do_install

Copies built files into ${D}, the package staging directory.

do_package

Splits the staged files into binary packages.

You can run a task directly while debugging:

bitbake -c fetch hello-recipe
bitbake -c unpack hello-recipe
bitbake -c compile hello-recipe
bitbake -c install hello-recipe

Use the task name without the do_ prefix on the command line.

Complete Worked Example

Create the recipe directory in your own layer:

mkdir -p meta-my-software/recipes-apps/hello-recipe/files

Create files/hello-recipe.c:

#include <stdio.h>

int main(void)
{
    puts("Hello from a Yocto recipe");
    return 0;
}

Create files/hello-recipe.conf:

message=Hello from /etc/hello-recipe/hello-recipe.conf

Now create hello-recipe_1.0.bb:

SUMMARY = "Small C application recipe example"
DESCRIPTION = "Builds and installs a small C application plus one configuration file."
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = " \
    file://hello-recipe.c \
    file://hello-recipe.conf \
"

S = "${WORKDIR}"

do_compile() {
    ${CC} ${CFLAGS} ${LDFLAGS} ${S}/hello-recipe.c -o hello-recipe
}

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

    install -d ${D}${sysconfdir}/hello-recipe
    install -m 0644 ${WORKDIR}/hello-recipe.conf ${D}${sysconfdir}/hello-recipe/hello-recipe.conf
}

The important install paths are:

  • ${D}${bindir}/hello-recipe, which becomes /usr/bin/hello-recipe on the target
  • ${D}${sysconfdir}/hello-recipe/hello-recipe.conf, which becomes /etc/hello-recipe/hello-recipe.conf on the target

Because the files are installed into standard locations, the main package usually collects them automatically. If you install into unusual locations, you may need to extend FILES:${PN}.

Build and Test the Recipe

First, make sure the layer is enabled:

bitbake-layers show-layers

If your layer is missing, add it:

bitbake-layers add-layer ../meta-my-software

Then build the recipe by itself:

bitbake hello-recipe

To inspect a single stage:

bitbake -c install hello-recipe

Look in the recipe work directory for staged output:

tmp/work/<machine>/hello-recipe/1.0/image/

You should see paths like:

usr/bin/hello-recipe
etc/hello-recipe/hello-recipe.conf

Add the Recipe to an Image

Building hello-recipe proves the package can be produced. It does not add the package to an image by itself.

To add it to an image recipe:

IMAGE_INSTALL:append = " hello-recipe"

Then rebuild the image:

bitbake my-image

For a quick local test, you can also add the package from conf/local.conf:

CORE_IMAGE_EXTRA_INSTALL += "hello-recipe"

Keep long-term product package lists in image recipes or package groups rather than relying on local.conf.

After booting the image, test the files:

hello-recipe
cat /etc/hello-recipe/hello-recipe.conf

Troubleshooting

Nothing Provides the Recipe

If BitBake reports that nothing provides hello-recipe, check:

  • the layer is listed by bitbake-layers show-layers
  • the recipe is under a directory matched by the layer’s BBFILES
  • the filename is hello-recipe_1.0.bb, not hello-recipe.bb
  • the recipe is not marked as incompatible by overrides or machine settings

Fetch or Unpack Fails

If do_fetch or do_unpack fails, check:

  • local files listed in SRC_URI are under files/
  • each local file uses the file:// prefix
  • Git URLs include the right protocol and branch
  • SRCREV names a real commit when fetching from Git

Licence Check Fails

If BitBake complains about licence metadata, check:

  • LICENSE is set
  • LIC_FILES_CHKSUM points to a real licence file
  • the checksum matches the file content
  • the licence identifier is accurate for the software

For early local examples using the common MIT licence text, the shared ${COMMON_LICENSE_DIR}/MIT path is acceptable. For real project source, point at the licence file shipped with that source when possible.

Compile Fails

If do_compile fails, inspect:

tmp/work/<machine>/hello-recipe/1.0/temp/log.do_compile

Common causes include:

  • source files are not where S says they are
  • missing build dependencies in DEPENDS
  • using host tools instead of Yocto-provided tools
  • forgetting ${CC}, ${CFLAGS}, or ${LDFLAGS} in a hand-written compile command

Package Builds but Files Are Missing

If the recipe builds but files do not appear in the image, check the flow in order:

  1. Did do_install() stage the files under ${D}?
  2. Did do_package put them into the expected package under packages-split/?
  3. Did the image install that package with IMAGE_INSTALL or a package group?
  4. Are you checking the rebuilt image, not an older deployed image?

The staged install tree is usually under:

tmp/work/<machine>/hello-recipe/1.0/image/

The package split output is usually under:

tmp/work/<machine>/hello-recipe/1.0/packages-split/

Summary

A recipe is practical build metadata for one software component.

For a simple C application, the important habits are:

  • keep the recipe and local files in your own layer
  • declare licence and source inputs clearly
  • set S to the directory BitBake should build from
  • use DEPENDS for build-time requirements and RDEPENDS:${PN} for target runtime requirements
  • compile with Yocto’s compiler and flags
  • install only into ${D} using paths such as ${bindir} and ${sysconfdir}
  • add the resulting package to an image before expecting it on the target

Once that flow is clear, larger recipes mostly add more source inputs, inherited classes, dependencies, and packaging details.

Quick quiz: creating recipes

Check the main ideas from the worked recipe example.

Question 1What is the main purpose of a `.bb` recipe?
Question 2Where should `do_install()` copy files?
Question 3When is `SRCREV` normally needed?
Question 4A recipe builds, but the executable is not in the final image. What should you check first?
Question 5Which task turns files staged by `do_install()` into binary packages?