32blogby Studio Mitsu

How to Write Yocto Recipes: A .bb File Guide

Write Yocto recipes from scratch. Covers Hello World, GitHub source fetching, CMake and Autotools with inherit classes.

by omitsu11 min read

This article contains affiliate links.

On this page

A Yocto recipe is a .bb file that tells BitBake where to fetch source code, how to build it, and where to install it — the core mechanism for adding any software to your embedded Linux image.

This guide walks you through creating Yocto recipes from the ground up, with working code examples at every step. By the end, you'll be comfortable writing recipes for local source files, GitHub projects, and CMake/Autotools builds.

What Is a Yocto Recipe?

A recipe is a .bb file that describes how to build and install a specific piece of software. The Yocto Development Tasks Manual covers the full specification, but at its core, a recipe answers four questions:

  • Where to get the source code (SRC_URI)
  • How to build it (do_compile)
  • Where to install it (do_install)
  • What license it uses (LICENSE)

Recipe File Naming

Recipe filenames follow the pattern packagename_version.bb:

text
hello-world_1.0.bb      # hello-world version 1.0
myapp_2.3.1.bb          # myapp version 2.3.1
busybox_1.36.1.bb       # busybox version 1.36.1

BitBake automatically extracts the package name (PN) and version (PV) from the filename. See the Yocto Reference Manual — Variables Glossary for the full list of built-in variables.

Basic Recipe Structure

text
# hello-world_1.0.bb

# ===== Metadata =====
SUMMARY = "Hello World sample"
DESCRIPTION = "A minimal example recipe for learning Yocto"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

# ===== Source acquisition =====
SRC_URI = "file://hello.c"

# ===== Source directory =====
S = "${WORKDIR}"

# ===== Build =====
do_compile() {
    ${CC} ${CFLAGS} ${LDFLAGS} -o hello hello.c
}

# ===== Install =====
do_install() {
    install -d ${D}${bindir}
    install -m 0755 hello ${D}${bindir}
}

Key Variables

Package Metadata

VariableDescriptionExample
SUMMARYShort one-line description"Hello World sample"
DESCRIPTIONDetailed description"A minimal learning example..."
LICENSELicense identifier (SPDX)"MIT", "GPL-2.0-only", "Apache-2.0"
LIC_FILES_CHKSUMLicense file checksumfile://LICENSE;md5=xxx
SRC_URIWhere to get the source"file://", "git://", "https://"
SWhere source is unpacked$/git

Build and Install Variables

VariableDescriptionExpands to
${WORKDIR}Working directory for this build/tmp/work/.../hello-world/1.0-r0/
${D}Staging root for installation.../image/
${bindir}/usr/bin path/usr/bin
${libdir}/usr/lib path/usr/lib
${sysconfdir}/etc path/etc
${CC}Cross-compilerarm-poky-linux-gnueabi-gcc

Building a Hello World Recipe

Let's build the simplest possible recipe and confirm it works end-to-end.

Create a Custom Layer

Always put your recipes in your own layer — never in Poky's layers directly.

bash
cd ~/yocto/poky
source oe-init-build-env build

# Create a new layer
cd ..
bitbake-layers create-layer meta-mylayer

# Add it to the build
cd build
bitbake-layers add-layer ../meta-mylayer

See the layer creation guide for details on layer management.

Create the Directory Structure and Source

bash
mkdir -p ../meta-mylayer/recipes-example/hello-world/files

Write files/hello.c:

c
// files/hello.c
#include <stdio.h>

int main(void) {
    printf("Hello from Yocto!\n");
    printf("This app was built with a custom recipe.\n");
    return 0;
}

Write the Recipe

Create hello-world_1.0.bb:

text
# hello-world_1.0.bb
SUMMARY = "Hello World - Yocto recipe learning example"
DESCRIPTION = "A minimal application demonstrating how to write a custom Yocto recipe"

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

SRC_URI = "file://hello.c"

S = "${WORKDIR}"

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

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

Build and Include in Your Image

bash
# Build the recipe individually
bitbake hello-world

# Success looks like:
NOTE: Tasks Summary: Attempted 123 tasks of which 120 didn't need to be rerun

Add to conf/local.conf to include it in the image:

text
IMAGE_INSTALL:append = " hello-world"

Rebuild and test in QEMU:

bash
bitbake core-image-minimal
runqemu qemux86-64 nographic

# After boot:
root@qemux86-64:~# hello
Hello from Yocto!
This app was built with a custom recipe.

Fetching Source from GitHub

In real projects, you'll usually fetch source from an external repository rather than shipping it locally.

Example: Adding nlohmann/json

text
# nlohmann-json_3.11.3.bb
SUMMARY = "JSON for Modern C++"
DESCRIPTION = "A header-only JSON library for C++"
HOMEPAGE = "https://github.com/nlohmann/json"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE.MIT;md5=f969127d7b7ed0a8a63c2bbeae002588"

# Fetch from GitHub using HTTPS
SRC_URI = "git://github.com/nlohmann/json.git;protocol=https;branch=develop"

# Pin to a specific commit — always do this for reproducibility
SRCREV = "9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03"

S = "${WORKDIR}/git"

inherit cmake

EXTRA_OECMAKE = "-DJSON_BuildTests=OFF"

SRC_URI Patterns

Source typeSRC_URI format
Local filefile://hello.c
Git (HTTPS)git://github.com/user/repo.git;protocol=https;branch=main
Git (SSH)git://git@github.com/user/repo.git;protocol=ssh;branch=main
HTTP/HTTPS tarballhttps://example.com/file.tar.gz
Patch filefile://fix-build.patch

Using Inherit Classes

Yocto ships with classes that encapsulate common build patterns. Using inherit saves you from writing boilerplate do_compile and do_install tasks.

ClassUse CaseWhat It Automates
cmakeCMake projectscmake → make → make install
autotoolsAutoconf projectsconfigure → make → make install
mesonMeson projectsmeson → ninja → install
setuptools3Python packagessetuptools / wheel install
systemdsystemd servicesService enablement

CMake Project Example

text
# myapp-cmake_1.0.bb
SUMMARY = "My CMake App"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "git://github.com/user/myapp.git;protocol=https;branch=main"
SRCREV = "abc123def456..."
S = "${WORKDIR}/git"

inherit cmake

EXTRA_OECMAKE = "-DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release"

Autotools Project Example

text
# myapp-autotools_1.0.bb
SUMMARY = "My Autotools App"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://COPYING;md5=..."

SRC_URI = "https://example.com/myapp-1.0.tar.gz"
SRC_URI[sha256sum] = "def456..."

inherit autotools

EXTRA_OECONF = "--disable-docs"

systemd Services

To automatically start your application at boot, use inherit systemd. For detailed configuration, see the systemd service guide.

Common Errors and Fixes

LIC_FILES_CHKSUM mismatch

text
ERROR: License checksum mismatch...

Cause: The MD5 hash in your recipe doesn't match the actual license file.

Fix: Recalculate the correct hash:

bash
md5sum LICENSE

do_fetch failed

text
ERROR: Fetcher failure: Unable to find file...

Cause: Wrong path in SRC_URI, or network issue when fetching from Git.

Fix: Double-check your SRC_URI and SRCREV. Make sure the commit hash exists in the repository.

Nothing PROVIDES 'hello-world'

text
ERROR: Nothing PROVIDES 'hello-world'

Cause: The layer containing your recipe isn't in bblayers.conf.

Fix: Add the layer:

bash
bitbake-layers add-layer ../meta-mylayer

SRC_URI checksum mismatch

text
ERROR: SRC_URI checksum mismatch...

Cause: Checksum for an HTTP/HTTPS download isn't set or doesn't match.

Fix: Calculate the correct checksum:

bash
sha256sum downloaded-file.tar.gz

Managing Dependencies

Real-world recipes often depend on other packages. Yocto distinguishes between build-time and runtime dependencies.

DEPENDS vs RDEPENDS

VariableWhen it mattersExample
DEPENDSBuild time — libraries/headers needed to compileDEPENDS = "openssl zlib"
RDEPENDS:${PN}Runtime — packages needed on the targetRDEPENDS:${PN} = "libssl"
text
# myapp_1.0.bb — with dependencies
SUMMARY = "My App with Dependencies"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://myapp.c"
S = "${WORKDIR}"

# Build-time: need OpenSSL headers and libraries to compile
DEPENDS = "openssl"

# Runtime: need OpenSSL shared libraries on the target
RDEPENDS:${PN} = "openssl"

do_compile() {
    ${CC} ${CFLAGS} ${LDFLAGS} -o myapp myapp.c -lssl -lcrypto
}

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

Using require and include

You can split recipe logic across multiple files:

  • require — includes another file; fails if not found
  • include — includes another file; silently skips if not found
text
# myapp_1.0.bb
require myapp-common.inc

SRC_URI = "https://example.com/myapp-1.0.tar.gz"
SRC_URI[sha256sum] = "abc123..."

This is commonly used in Poky's own recipes to share metadata across versions.

FAQ

What is the difference between SRCREV and AUTOREV?

SRCREV pins your recipe to a specific Git commit hash, ensuring reproducible builds across machines and CI environments. AUTOREV (set as SRCREV = "${AUTOREV}") fetches the latest commit on each build. The BitBake User Manual explicitly discourages AUTOREV for production use — it makes builds non-deterministic and can introduce untested changes.

How do I add multiple source files in SRC_URI?

List them separated by spaces or use line continuation with backslashes:

text
SRC_URI = "file://main.c \
           file://utils.c \
           file://utils.h \
           file://config.json \
           "

All file:// entries should be placed in a files/ directory next to the recipe, or in a directory matching the recipe name.

Can I write recipe logic in Python?

Yes. BitBake recipes support Python in two ways: inline with ${@expression} and as full Python functions with python do_taskname(). For example:

text
# Set a variable using Python
DATEVAR = "${@time.strftime('%Y%m%d', time.gmtime())}"

python do_configure:prepend() {
    bb.note("Configuring with custom Python logic")
}

The BitBake User Manual covers Python tasks in detail.

What is the difference between inherit and require?

inherit loads a .bbclass file, which adds standardized build behavior (like cmake or autotools). require / include loads another .bb or .inc file to share recipe metadata. Think of inherit as "use this build system" and require as "import shared recipe settings."

How do I debug a recipe that fails to build?

Use devshell to drop into an interactive shell inside the recipe's build environment:

bash
bitbake -c devshell hello-world

This opens a terminal with all environment variables (cross-compiler, sysroot paths) already set. You can manually run make, inspect files, and test fixes. See the build error debugging guide for a systematic approach to common failures.

How do I add configuration files to /etc?

Use ${sysconfdir} (which expands to /etc) in do_install:

text
do_install() {
    install -d ${D}${sysconfdir}/myapp
    install -m 0644 ${WORKDIR}/myapp.conf ${D}${sysconfdir}/myapp/
}

Don't forget to include the config file in SRC_URI as well.

What is the difference between do_install and do_deploy?

do_install puts files into the target rootfs staging area (${D}). do_deploy puts files into the deploy directory — used for boot images, bootloader binaries, or artifacts that aren't part of the rootfs but are needed for flashing. Most recipes only need do_install.

Wrapping Up

Here's what to remember about writing Yocto recipes:

  • Recipes are .bb files that describe a complete build process — fetch, build, install
  • Core variables: SRC_URI, do_compile, do_install, DEPENDS, RDEPENDS
  • Always put your recipes in a custom layer, not in Poky
  • Use inherit to automatically handle CMake/Autotools/Meson projects
  • Add packages to an image with IMAGE_INSTALL:append
  • Always pin SRCREV to a specific commit hash for reproducibility
  • Use DEPENDS for build-time and RDEPENDS for runtime dependencies

The Yocto Development Tasks Manual is the authoritative reference for recipe writing. Once you're comfortable with the basics, here's where to go next:

To go deeper into embedded Linux, these books are excellent references.

Related articles: