You want to bundle your own application into a Yocto Linux image. You need to build a GitHub project with Yocto. But how do you write a .bb file?
This guide walks 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.
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.
Basic Recipe Structure
# 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
| Variable | Description | Example |
|---|---|---|
SUMMARY | Short one-line description | "Hello World sample" |
DESCRIPTION | Detailed description | "A minimal learning example..." |
LICENSE | License identifier (SPDX) | "MIT", "GPL-2.0-only", "Apache-2.0" |
LIC_FILES_CHKSUM | License file checksum | file://LICENSE;md5=xxx |
SRC_URI | Where to get the source | "file://", "git://", "https://" |
S | Where source is unpacked | $/git |
Build and Install Variables
| Variable | Description | Expands 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-compiler | arm-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.
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
mkdir -p ../meta-mylayer/recipes-example/hello-world/files
Write files/hello.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:
# 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
# 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:
IMAGE_INSTALL:append = " hello-world"
Rebuild and test in QEMU:
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
# 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"
S = "${WORKDIR}/git"
inherit cmake
EXTRA_OECMAKE = "-DJSON_BuildTests=OFF"
SRC_URI Patterns
| Source type | SRC_URI format |
|---|---|
| Local file | file://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 tarball | https://example.com/file.tar.gz |
| Patch file | file://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.
| Class | Use Case | What It Automates |
|---|---|---|
cmake | CMake projects | cmake → make → make install |
autotools | Autoconf projects | configure → make → make install |
meson | Meson projects | meson → ninja → install |
setuptools3 | Python packages | setuptools / wheel install |
systemd | systemd services | Service enablement |
CMake Project Example
# 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
# 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
ERROR: License checksum mismatch...
Cause: The MD5 hash in your recipe doesn't match the actual license file.
Fix: Recalculate the correct hash:
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:
bitbake-layers add-layer ../meta-mylayer
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:
sha256sum downloaded-file.tar.gz
Wrapping Up
Here's what to remember about writing Yocto recipes:
- Recipes are
.bbfiles 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
inheritto automatically handle CMake/Autotools/Meson projects - Add packages to an image with
IMAGE_INSTALL:append - Always pin
SRCREVto a specific commit hash for reproducibility
Once you're comfortable writing recipes, you can package anything into a Yocto image. Here's where to go next:
- Creating and managing layers — layer architecture for your recipes
- bbappend practical guide — customizing existing recipes
- Building Linux for Raspberry Pi — deploy to real hardware
To go deeper into embedded Linux, these books are excellent references.