32blogby StudioMitsu
Archive8 min read

How to Write Yocto Recipes: A Complete .bb File Guide

Learn to write Yocto recipes from scratch: Hello World to GitHub source fetching, CMake/Autotools inheritance, systemd services, and adding packages to your Linux image.

"I want to bundle my own application into a Yocto Linux image."

"What exactly is a recipe? I have no idea how to write a .bb file."

"How do I build a GitHub project with Yocto?"

This guide answers all three. I'll walk you through creating Yocto recipes from the ground up, with working code examples at every step.

Once you understand how recipes work, you can package virtually any software into a Yocto image. That's when embedded Linux development really opens up.

Prerequisites

  • You understand Yocto fundamentals (BitBake, layers)
  • You've successfully built core-image-minimal with Poky
  • You're comfortable with basic Linux commands

New to Yocto? Start with the Yocto Beginner's Guide first.

What You'll Learn

  • The structure of a Yocto recipe (.bb file)
  • Building a Hello World recipe from scratch
  • Key variables: SRC_URI, do_compile, do_install
  • Fetching source from GitHub
  • Using CMake, Autotools, and Meson via inherit
  • Adding packages to your Linux image

What Is a Yocto Recipe?

A recipe is a .bb file that describes how to build and install a specific piece of software. Think of it as a self-contained build script that 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:

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. No need to set them explicitly.

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"MIT", "GPLv2", "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

Pay close attention to ${D}. In do_install, you always prepend ${D} to the install path. This installs files into the staging area, not the host system. BitBake handles moving them to the final image.

Hands-On 1: Building a Hello World Recipe

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

Step 1: Create a Custom Layer

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

bash
# Initialize the build environment
$ 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

# Verify
$ bitbake-layers show-layers
layer                 path
=========================================================
meta                  /home/user/yocto/poky/meta
meta-poky             /home/user/yocto/poky/meta-poky
meta-yocto-bsp        /home/user/yocto/poky/meta-yocto-bsp
meta-mylayer          /home/user/yocto/meta-mylayer

Step 2: Create the Directory Structure

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

# Expected structure:
# meta-mylayer/
# ├── conf/
# │   └── layer.conf
# ├── COPYING.MIT
# ├── README
# └── recipes-example/
#     └── hello-world/
#         ├── hello-world_1.0.bb
#         └── files/
#             └── hello.c

Step 3: Write the Source Code

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;
}

Step 4: Write the Recipe

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"
AUTHOR = "Your Name <your@email.com>"
HOMEPAGE = "https://example.com"

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

# Source comes from the local files/ directory
SRC_URI = "file://hello.c"

S = "${WORKDIR}"

do_compile() {
    # Use the Yocto-provided cross-compiler
    ${CC} ${CFLAGS} ${LDFLAGS} -o hello hello.c
}

do_install() {
    # Create /usr/bin in the staging area
    install -d ${D}${bindir}
    # Install the binary with executable permissions
    install -m 0755 hello ${D}${bindir}
}

Step 5: Build the Recipe

bash
$ bitbake hello-world

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

If you hit an error, check the log files under tmp/work/x86_64-linux/hello-world/.

Step 6: Include It in Your Image

Add this to conf/local.conf:

bash
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.

That's a complete round-trip: write a recipe, build it, and run it on your custom Linux system.

Hands-On 2: 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=f5f7c71504da070bcf4f090205ce1080"

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

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

# Git clones go to ${WORKDIR}/git
S = "${WORKDIR}/git"

inherit cmake

EXTRA_OECMAKE = "-DJSON_BuildTests=OFF"

Always pin SRCREV to a specific commit hash. Avoid AUTOREV — it breaks reproducible builds.

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
python3nativePython scriptssetup.py 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"

With inherit cmake, BitBake handles cross-compilation cmake invocation, build, and installation automatically.

Autotools Project Example

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

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

inherit autotools

EXTRA_OECONF = "--disable-docs"

Registering as a systemd Service

For embedded devices, you often want your application to start automatically at boot.

text
# myservice_1.0.bb
SUMMARY = "My System Service"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://myservice.c \
           file://myservice.service"

S = "${WORKDIR}"

inherit systemd

# Specify the systemd unit file
SYSTEMD_SERVICE:${PN} = "myservice.service"
SYSTEMD_AUTO_ENABLE = "enable"

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

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

    # Install the systemd unit file
    install -d ${D}${systemd_system_unitdir}
    install -m 0644 myservice.service ${D}${systemd_system_unitdir}
}

Common Errors and Fixes

LIC_FILES_CHKSUM mismatch

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

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'

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
$ bitbake-layers show-layers   # verify it's listed

SRC_URI checksum mismatch

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

Summary

Here's what to remember about writing Yocto recipes:

  • Recipes are .bb files that describe a complete build process
  • Core variables: SRC_URI, do_compile, do_install
  • 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

Once you're comfortable writing recipes, you can package anything into a Yocto image. The next natural steps are deploying to real hardware and building a complete custom distro layer.

Next Steps


This article reflects the state of Yocto Project as of January 2026. Procedures may change as new versions are released.