core-image-yocto

The Yocto Project is an open-source collaboration that provides templates, tools, and methods to create custom Linux-based systems for embedded hardware, regardless of the underlying architecture. It isn't a single embedded Linux distribution — it's a build system that helps you create your own.

Contents

  1. Why Yocto?
  2. Core Components
  3. BitBake: The Build Engine
  4. Layers & the Layer Model
  5. Recipes & Metadata
  6. Classes & Tasks
  7. Getting Started: Build Workflow
  8. Customizing an Image
  9. SDK & Application Development
  10. Debugging & Common Pitfalls
  11. Command Cheat Sheet
  12. External Learning Resources

Why Yocto?

Building a custom embedded Linux image by hand — cross-compiling a toolchain, kernel, bootloader, and root filesystem for a specific board — is slow and error-prone. Yocto standardizes this into a reproducible, metadata-driven build process so you can target new hardware, swap components, and maintain long-term builds without starting from scratch.

Core Components

Component Role
PokyThe reference distribution — a working combination of BitBake + OpenEmbedded-Core metadata used as a starting point.
BitBakeThe task-execution/build engine that parses recipes and resolves dependencies to build packages and images.
OpenEmbedded-Core (OE-Core)Core metadata (recipes/classes) shared across OpenEmbedded-based distributions, including Yocto.
MetadataRecipes (.bb), append files (.bbappend), configuration (.conf), and classes (.bbclass) that describe what to build.
BSP LayersBoard Support Package layers containing machine-specific configuration, kernel, and bootloader recipes.

BitBake: The Build Engine

BitBake reads recipes and their dependency graph, then executes a series of tasks (fetch, unpack, patch, configure, compile, install, package) for each recipe in the correct order, using a scheduler similar in spirit to make but distribution-aware and cross-compilation-first.

bitbake-basics.sh
# source the build environment (creates/enters build/ dir)
source oe-init-build-env

# build a full image for the configured MACHINE
bitbake core-image-minimal

# build just one recipe/package
bitbake busybox

# see the full task list bitbake would run, without running them
bitbake -g core-image-minimal   # generates pn-depends.dot / task-depends.dot

# force re-execution of a specific task
bitbake -c compile -f busybox
bitbake -c cleansstate busybox   # wipe shared-state cache for a recipe

# jump into a recipe's work directory to debug interactively
bitbake -c devshell busybox

Layers & the Layer Model

A layer is simply a structured directory of recipes and configuration that can be stacked on top of others. This layered model is what lets you cleanly add a BSP, a middleware stack, or your own product customizations without touching upstream metadata.

layers.sh
# create a new layer skeleton with the right structure
bitbake-layers create-layer meta-mylayer

# add it to bblayers.conf so BitBake picks it up
bitbake-layers add-layer ../meta-mylayer

# list currently enabled layers and their priority
bitbake-layers show-layers

# see which layer/recipe provides a given package
bitbake-layers show-recipes busybox
bitbake-layers show-appends

Recipes & Metadata

A recipe (.bb file) describes how to fetch, configure, build, and package a single piece of software. Recipes can be extended non-destructively with .bbappend files in another layer, which is the standard way to customize upstream recipes.

myapp_1.0.bb
SUMMARY = "A simple example application"
DESCRIPTION = "Demonstrates a minimal Yocto recipe for a C program"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "git://github.com/example/myapp.git;branch=main;protocol=https"
SRCREV = "abcdef1234567890abcdef1234567890abcdef12"

S = "${WORKDIR}/git"

DEPENDS = "zlib"

inherit cmake

do_install:append() {
    install -d ${D}${bindir}
    install -m 0755 ${B}/myapp ${D}${bindir}/myapp
}

FILES:${PN} = "${bindir}/myapp"
myapp_%.bbappend
# placed in meta-mylayer/recipes-myapp/myapp/myapp_%.bbappend
# extends the upstream myapp recipe without editing it directly

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

SRC_URI += "file://0001-fix-custom-config.patch"

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

Classes & Tasks

Classes (.bbclass) capture shared build logic — like autotools.bbclass, cmake.bbclass, or kernel.bbclass — so recipes can inherit common behavior instead of repeating boilerplate. Every recipe is built up from a fixed sequence of tasks.

Task What Happens
do_fetchDownloads source from SRC_URI (git, http, local files, etc.) into the download cache.
do_unpackUnpacks fetched sources into the working directory (${WORKDIR}).
do_patchApplies any patches listed in SRC_URI.
do_configureRuns the project's configure step (autotools, cmake, meson, etc.).
do_compileCross-compiles the source using the generated toolchain.
do_installInstalls build output into ${D}, a staging "destination" root.
do_packageSplits installed files into one or more binary packages (ipk/deb/rpm).
do_rootfs(Image-level) Assembles all selected packages into the final root filesystem.

Getting Started: Build Workflow

A typical first build — targeting the QEMU emulator so you don't even need real hardware — looks like this:

first_build.sh
# 1. clone poky (the reference distro / build system)
git clone -b kirkstone git://git.yoctoproject.org/poky
cd poky

# 2. set up the build environment (creates build/conf/local.conf)
source oe-init-build-env

# 3. (edit build/conf/local.conf to set MACHINE, e.g. qemux86-64)

# 4. kick off the build - this compiles the toolchain, kernel, and rootfs
bitbake core-image-minimal

# 5. boot the resulting image under QEMU
runqemu qemux86-64

The first build can take hours since it compiles an entire cross-toolchain and every package from source — subsequent builds are much faster thanks to shared state (sstate) caching, which reuses previously built task output whenever its inputs haven't changed.

Customizing an Image

Custom images are just recipes that inherit core-image and declare which packages to install, typically placed in your own layer under recipes-core/images/.

myproduct-image.bb
SUMMARY = "Custom product image with SSH, Python, and myapp"
LICENSE = "MIT"

inherit core-image

IMAGE_FEATURES += "ssh-server-openssh package-management"

IMAGE_INSTALL:append = " \
    myapp \
    python3 \
    python3-pip \
    htop \
    nano \
"

IMAGE_ROOTFS_EXTRA_SPACE = "1048576"

SDK & Application Development

Yocto can also generate a standalone cross-toolchain (an "extensible SDK" or classic SDK) so application developers can build software for the target without needing the full BitBake build environment.

sdk.sh
# build a relocatable SDK matching your image
bitbake core-image-minimal -c populate_sdk

# or the extensible SDK (includes devtool, faster iteration)
bitbake core-image-minimal -c populate_sdk_ext

# install the generated SDK (produces environment-setup-* script)
./tmp/deploy/sdk/poky-glibc-x86_64-core-image-minimal-*.sh

# use it to cross-compile an app against the target sysroot
source /opt/poky/*/environment-setup-*
$CC myapp.c -o myapp

# devtool: iterate on a recipe/source without a full rebuild
devtool modify myapp
devtool build myapp
devtool deploy-target myapp root@192.168.1.50

Debugging & Common Pitfalls

Command Cheat Sheet

Command What It Does
source oe-init-build-env [dir]Set up/enter a BitBake build environment.
bitbake <target>Build a recipe or image target.
bitbake -c <task> <target>Run a specific task (e.g. compile, devshell, menuconfig) for a target.
bitbake -e <target> | grep ^VAR=Inspect the final expanded value of a variable for a recipe.
bitbake-layers show-layersList all layers currently configured in the build.
devtool modify <recipe>Extract a recipe's source into a local workspace for iterative development.
runqemu <machine>Boot a built image in the QEMU emulator.
wic list imagesList available disk image (.wic) definitions for flashing to media.

Quiz

You need to customize an existing upstream recipe (add a patch and an extra config file) without editing it directly. What's the correct Yocto mechanism?

External Learning Resources

The official docs are dense but authoritative — pairing them with a few community resources makes the learning curve much more manageable:

← Back to Tech