Skip to content

Build Your Own BSP Layer

This tutorial builds an integration project that consumes meta-tolomeo as a source layer. You scaffold it from the DAVE Yocto project template, wire the kas configuration that binds meta-tolomeo and its upstream dependencies into one build, derive your own distribution and image, and verify the result with a build.

meta-tolomeo supplies the distribution and the ToloMEO application stack, including the services that give the device access to ToloMEO Cloud features. The machine configuration is yours alone. This tutorial therefore takes you as far as a verified build against meta-tolomeo's QEMU reference machine, then hands off to your own machine with meta-tolomeo-qemu as the worked example.


What each side provides

This boundary determines which files you write and which you only reference.

meta-tolomeo provides You provide
tolomeo.inc — the distribution base acme.inc and acme-devel.conf / acme-prod.conf deriving from it
CVE check, CycloneDX/VEX, SWUpdate, secure boot and hardening classes and includes The DISTRO_FEATURES selection your product needs
tolomeo-image-common.inc, packagegroup-tlm-*, ToloMEO Cloud service recipes acme-image-common.inc and acme-image-devel.bb deriving from them
A kas fragment per layer, declaring repositories, config blocks and passthrough variables kas/base.yml, kas/common.yml, the target files and kas-ci/
Storage and secure-boot machine includes, plus a reference machine to build against Your machine configurations, board recipes and WIC (.wks) partition layouts

Note

meta-tolomeo-distro ships only conf/distro/include/. The concrete tolomeo-devel and tolomeo-prod distributions live in meta-tolomeo-qemu as reference distributions. Do not inherit them — derive your own from tolomeo.inc, exactly as they do.


Prerequisites

  • Build the ToloMEO Reference Image completed. It teaches the devcontainer and kas workflow this tutorial assumes, and its populated sstate cache speeds up Step 8.
  • Docker 20.10+ (Get Docker) with at least 16 GB RAM allocated
  • VS Code 1.60+ with the Dev Containers extension
  • Copier 9.0 or newer
  • An SSH key configured for GitLab (GitLab SSH guide), with read access to the DAVE Yocto project template
  • 50 GB free disk space
  • An empty GitLab (or GitHub) project, created without a README or any other initial file

Install Copier in an isolated environment if you prefer:

python3 -m venv venv
source venv/bin/activate
pip install copier

Choose your names

Every name in the project derives from a single project slug. This tutorial uses acme; substitute your own throughout.

Item Value Where it appears
Project slug acme everywhere below
Repository meta-acme Copier repo_name
Layer name prefix meta-tolomeo-acme Copier meta_layer
Layers meta-tolomeo-acme-app, -bsp, -distro repository root
Distributions acme-devel, acme-prod meta-tolomeo-acme-distro/conf/distro/
Images acme-image-devel, acme-image-prod meta-tolomeo-acme-app/recipes-core/images/
Machines (yours, later) acme-<soc>-devel, acme-<soc>-prod meta-tolomeo-acme-bsp/conf/machine/

The meta-tolomeo- prefix on meta_layer marks the layers as ToloMEO integration layers in bblayers.conf and LAYERDEPENDS; the repository keeps the shorter product name.


Step 1 — Generate the project

Run Copier against the template. Supplying the answers on the command line keeps the run reproducible:

copier copy git@gitlab.com:DAVEEmbeddedSystems/public/project-templates/yocto-template-project.git \
    meta-acme --trust \
    -d project_name="Acme Edge Gateway" \
    -d repo_name="meta-acme" \
    -d meta_layer="meta-tolomeo-acme" \
    -d description="ToloMEO integration layer for the Acme Edge Gateway" \
    -d yocto_version="scarthgap" \
    -d kas_version="4.8.2" \
    -d secure_boot_type="none" \
    -d firmware_name="Acme-edge-gateway-firmware" \
    -d build_version="1.0.0+26.07" \
    -d author_name="Your Name" \
    -d author_email="you@example.com" \
    -d gitlab_org="YourCompany"

secure_boot_type selects which NXP development PKI to ship: none, hab4 (i.MX 6/⅞, RSA-2048) or ahab (i.MX 9, ECC secp384r1). firmware_name and build_version are recorded in local.conf as FIRMWARE_NAME and BUILD_VERSION.

Any omitted answer is prompted for. Copier then initialises the Git repository, creates the default branch and adds the origin remote.

The result:

meta-acme/
├── meta-tolomeo-acme-app/       # image and application recipes
├── meta-tolomeo-acme-bsp/       # machine configurations and board recipes — yours
├── meta-tolomeo-acme-distro/    # distribution configuration
├── kas/
│   ├── base.yml                 # layer stack, with poky and meta-openembedded already pinned
│   └── common.yml               # build paths and mirrors
├── kas-ci/
│   └── common.yml               # CI variant of common.yml
├── .devcontainer/               # development environment
├── devel-keys/                  # development signing material, plus secure-boot PKI if selected
├── docs/                        # documentation scaffold
├── .gitlab-ci.yml
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── renovate.json                # keeps the pins in base.yml current
└── zensical.toml

Linter configuration (.oelint.cfg, .markdownlint.yml, .yamllint, .gitleaks.toml and friends) is generated alongside these.

Each layer already has a conf/layer.conf with the correct collection name, BBFILE_PRIORITY and LAYERSERIES_COMPAT.

The generated BBFILE_PRIORITY is 10, above meta-tolomeo's -app, -bsp, -distro and -nxp layers, which are all 9. meta-tolomeo-qemu is also 10, so raise yours if you ever need to override a recipe from that layer specifically.

cd meta-acme

meta-tolomeo's kas fragments default the U-Boot FIT and SWUpdate signing directories to ${BSPDIR}/keys, which resolves to your repository root. The template ships development keys in devel-keys/; point keys at them:

ln -s devel-keys keys

If you chose hab4 or ahab, devel-keys/secure-boot/ also holds the NXP development PKI, which the symlink puts at the ${BSPDIR}/keys/secure-boot path that SIG_DATA_PATH expects. The generated .gitignore already excludes keys, so the symlink will not be committed.

Warning

The keys in devel-keys/ are public and exist only so that a build completes. Never ship a device signed with them. See Generate a Signed Update Package for producing your own key material.


Step 3 — Start the development environment

The template's devcontainer is built on the official kas image, so no host-side Yocto tooling is needed.

  1. Open the project in VS Code: File → Open Folder, select meta-acme.
  2. VS Code detects .devcontainer/devcontainer.json and offers Reopen in Container. Accept it. Alternatively, use the Command Palette → Dev Containers: Reopen in Container.
  3. The first container build takes several minutes. Its postCreateCommand installs the pre-commit hooks, so the linters CI runs are active locally too.

Verify the environment from a terminal inside the container:

kas --version

For cache configuration and resource tuning, see Configure the Dev Environment.


Step 4 — Declare the layer stack in kas/base.yml

kas/base.yml declares which repositories are part of the build, which of their layers are enabled, and which settings apply to every target. The template generates most of it: poky and meta-openembedded are pinned, your three layers are declared, and BSPDIR and the build-metadata blocks are set. What you add is meta-tolomeo.

4.1 Include the meta-tolomeo kas fragments

Each meta-tolomeo layer ships a kas fragment declaring what it needs. Include those fragments rather than copying their contents:

---
header:
  version: 14

  includes:
    - repo: meta-tolomeo
      file: meta-tolomeo-app/kas/common.yml
    - repo: meta-tolomeo
      file: meta-tolomeo-bsp/kas/common.yml
    - repo: meta-tolomeo
      file: meta-tolomeo-distro/kas/common.yml

What each one brings in:

Fragment Contributes
meta-tolomeo-app/kas/common.yml Passthrough of DEFAULT_ROOT_PASSWORD and DEFAULT_ADMIN_PASSWORD from the environment
meta-tolomeo-bsp/kas/common.yml U-Boot FIT signing defaults (UBOOT_SIGN_KEYDIR, UBOOT_SIGN_KEYNAME) and their passthrough
meta-tolomeo-distro/kas/common.yml The meta-security, meta-swupdate, meta-vulnscout and meta-virtualization repositories, the SWUpdate signing defaults, the CVE database settings, and the build-version passthrough

Add meta-tolomeo-nxp/kas/common.yml as a fourth include only if your SoC is an NXP i.MX part.

Tip

The fragments own meta-tolomeo's dependencies, so a new upstream layer reaches your build the moment you bump the meta-tolomeo commit. Never restate their contents in your own files.

4.2 Review the upstream pins

poky and meta-openembedded arrive already pinned, on the branch matching your yocto_version answer and with the sub-layers meta-tolomeo needs enabled. Check the two commits against the meta-tolomeo release you are about to consume; they must be the ones it is tested with.

Each pin carries a # renovate: annotation, so the generated renovate.json raises a merge request when a newer commit is available and the pins stay current without hand-editing.

Three conventions are visible in those entries, and they apply to every repository you add later — meta-tolomeo next, and your SoC vendor's BSP layers in Step 10:

  • branch and commit together. The commit makes the build reproducible; the branch records the upstream line and gives a bump its target. A branch alone lets your builds drift.
  • path: sources/<name>. Fetched repositories all live under sources/, keeping the root readable.
  • Explicit layers:. Only the listed sub-layers are enabled. Omit the key to enable the repository root as a single layer, as a flat layer like meta-swupdate needs.

4.3 Pin meta-tolomeo

meta-tolomeo is just another pinned repository:

  meta-tolomeo:
    url: https://gitlab.com/DAVEEmbeddedSystems/public/tolomeo-public-projects/edge/tolomeo-edge/meta-tolomeo
    branch: scarthgap
    commit: <sha of the latest stable release>
    path: sources/meta-tolomeo
    layers:
      meta-tolomeo-app:
      meta-tolomeo-bsp:
      meta-tolomeo-distro:
      meta-tolomeo-qemu:

meta-tolomeo-bsp contributes no machines of its own. It provides the storage and secure-boot machine includes that your machine configurations will require, documented in Machine Configuration.

meta-tolomeo-qemu is enabled here because it provides tolomeo-qemux86-64, the reference machine you will build against in Step 8 to verify the wiring, and because its machine configurations are the worked example you will follow in Step 10. Keep it enabled afterwards — a QEMU target costs nothing and keeps a hardware-independent build available.

4.4 Your own layer entry

Your layers are already on disk, so their entry carries no url and no commit: kas treats a url-less repository as the working repository and resolves layer paths from its root. The template generates this entry and BSPDIR with it:

  meta-tolomeo-acme:
    layers:
      meta-tolomeo-acme-app:
      meta-tolomeo-acme-bsp:
      meta-tolomeo-acme-distro:

bblayers_conf_header:
  env: |
    BSPDIR := "${@os.path.abspath(os.path.dirname(d.getVar('FILE', True)) + '/../..')}"

The entry is keyed on your meta_layer answer, not the repository name, so it reads meta-tolomeo-acme even though the repository is meta-acme.

BSPDIR resolves to your repository root. It lets meta-tolomeo default its key directories to ${BSPDIR}/keys without knowing your layout, and your own recipes can use it the same way.

4.5 Build metadata and project defaults

The template generates the common-minimal block — disk-space safety nets, parallelism, rm_work — an empty block for your own settings, and a buildinfo block filled from your firmware_name and build_version answers:

local_conf_header:
  common-meta-tolomeo-acme: |-
    # --- Project-specific defaults ---

  buildinfo: |-
    FIRMWARE_NAME = "Acme-edge-gateway-firmware"
    BUILD_VERSION = "1.0.0+26.07"

The keys under local_conf_header are block names, not variables, and they are the override mechanism: kas merges blocks by name and the last definition wins. meta-tolomeo-qemu's fragment also defines buildinfo, and the generated one replaces it because base.yml is evaluated after its includes. Reuse a block name to override; pick a fresh one, prefixed with your slug, to add.

A vendor licence acknowledgement goes in its own block, for example common-nxp-eula: |- containing ACCEPT_FSL_EULA = "1".

Complete kas/base.yml
---
header:
  version: 14

  includes:
    - repo: meta-tolomeo
      file: meta-tolomeo-app/kas/common.yml
    - repo: meta-tolomeo
      file: meta-tolomeo-bsp/kas/common.yml
    - repo: meta-tolomeo
      file: meta-tolomeo-distro/kas/common.yml

repos:
  poky:
    url: https://git.yoctoproject.org/poky
    branch: scarthgap
    # renovate: depName=poky packageName=... currentValue=scarthgap+26.07
    commit: <pinned by the template>
    path: sources/poky
    layers:
      meta:
      meta-poky:

  meta-openembedded:
    url: https://git.openembedded.org/meta-openembedded
    branch: scarthgap
    # renovate: depName=meta-openembedded packageName=... currentValue=scarthgap+26.07
    commit: <pinned by the template>
    path: sources/meta-openembedded
    layers:
      meta-filesystems:
      meta-networking:
      meta-oe:
      meta-perl:
      meta-python:
      meta-webserver:

  meta-tolomeo:
    url: https://gitlab.com/DAVEEmbeddedSystems/public/tolomeo-public-projects/edge/tolomeo-edge/meta-tolomeo
    branch: scarthgap
    commit: <sha of the latest stable release>
    path: sources/meta-tolomeo
    layers:
      meta-tolomeo-app:
      meta-tolomeo-bsp:
      meta-tolomeo-distro:
      meta-tolomeo-qemu:

  meta-tolomeo-acme:
    layers:
      meta-tolomeo-acme-app:
      meta-tolomeo-acme-bsp:
      meta-tolomeo-acme-distro:

bblayers_conf_header:
  env: |
    BSPDIR := "${@os.path.abspath(os.path.dirname(d.getVar('FILE', True)) + '/../..')}"

local_conf_header:
  common-minimal: |-
    # --- Disk space safety nets ---
    # Stop scheduling new tasks if disk low, halt if critically low
    BB_DISKMON_DIRS ??= "\
        STOPTASKS,${TMPDIR},1G,100K \
        STOPTASKS,${DL_DIR},1G,100K \
        STOPTASKS,${SSTATE_DIR},1G,100K \
        STOPTASKS,/tmp,100M,100K \
        HALT,${TMPDIR},100M,1K \
        HALT,${DL_DIR},100M,1K \
        HALT,${SSTATE_DIR},100M,1K \
        HALT,/tmp,10M,1K"

    # Number of CPU cores
    BB_NUMBER_THREADS ?= "${@oe.utils.cpu_count()}"

    # Parsing benefits less from parallelism
    BB_NUMBER_PARSE_THREADS ?= "${@max(1, oe.utils.cpu_count() // 2)}"

    # Allow mild over-subscription inside recipes
    PARALLEL_MAKE ?= "-j ${@int(oe.utils.cpu_count() * 1.25)}"
    PARALLEL_MAKEINST ?= "${PARALLEL_MAKE}"

    # --- Save disk space by cleaning workdirs after successful tasks ---
    INHERIT += "rm_work"

    # Collect build stats (optional, helps analyze build bottlenecks)
    USER_CLASSES ?= "buildstats"

  common-meta-tolomeo-acme: |-
    # --- Project-specific defaults ---

  buildinfo: |-
    FIRMWARE_NAME = "Acme-edge-gateway-firmware"
    BUILD_VERSION = "1.0.0+26.07"

Step 5 — Review the build paths and mirrors

kas/common.yml holds where the build happens rather than what it builds. The template provides a working version:

---
header:
  version: 14
  includes:
    - kas/base.yml

local_conf_header:
  common-path: |-
    TMPDIR = "${TOPDIR}/tmp"

    # Central downloads directory (shared across builds)
    # DL_DIR ?= "/workspaces/yocto/downloads"

    # Central sstate cache directory (use only for production)
    # SSTATE_DIR ?= "/workspaces/yocto/sstate-cache"

    SSTATE_MIRRORS ?= "file://.* https://reed-cache01.tolomeo.io/sstate-cache/PATH;downloads=PATH \
                       file://.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH\
                       "

    BB_HASHSERVE = ""
    BB_SIGNATURE_HANDLER = "OEBasicHash"

Two points are worth understanding rather than accepting:

  • BB_HASHSERVE = "" and OEBasicHash belong with the mirrors. Yocto's default handler derives task signatures from the local hash-equivalence server's history, so they never match what a shared mirror was populated with. OEBasicHash makes signatures a pure function of the metadata, which is what produces cache hits. Change either and you lose the mirror.
  • TMPDIR is inside the build directory, and the shared download and sstate directories are commented out. Uncomment them once you have chosen where those caches live.

Optional: feature fragments

When a group of settings is meaningful on its own — provisioning parameters, secure-boot fuse values, a manufacturing profile — give it its own file under kas/ instead of growing base.yml:

# kas/secure-boot.yml
---
header:
  version: 14

local_conf_header:
  common-secure-boot: |-
    # ... your signing and fuse configuration ...
# kas/common.yml
header:
  version: 14
  includes:
    - kas/base.yml
    - kas/secure-boot.yml

kas-ci/common.yml then reuses the same fragment verbatim, so local and CI builds cannot drift apart.


Step 6 — Derive your distribution

A ToloMEO-based distribution takes two files: a shared include holding everything common to your product, and one thin configuration per build flavour.

Create meta-tolomeo-acme-distro/conf/distro/include/acme.inc:

# Copyright (C) 2026 Acme Corp
# Released under the MIT license (see COPYING.MIT for the terms)

require conf/distro/include/tolomeo.inc

# Additional DISTRO_FEATURES common to every Acme flavour
DISTRO_FEATURES += "ipv4 \
                    ipv6 \
                    "

# Update support features
DISTRO_FEATURES += "update-offline \
                    update-ota \
                    "

# Remove backends that conflict with a headless device
DISTRO_FEATURES:remove = "directfb \
                          x11 \
                          "

That single require is the whole inheritance mechanism. tolomeo.inc sets DISTRO_VERSION, adds tolomeo to DISTROOVERRIDES, selects systemd and RPM packaging, pulls in the upstream security-flag includes and registers the CycloneDX supplier map. Everything after it is yours.

Then meta-tolomeo-acme-distro/conf/distro/acme-devel.conf:

require conf/distro/include/acme.inc

DISTRO = "acme-devel"
DISTRO_NAME = "Acme Edge Gateway Development Distro"

# Development features
DISTRO_FEATURES += "ptest"

And acme-prod.conf, which adds the security and reporting stack:

require conf/distro/include/acme.inc
require conf/distro/include/cve-extra-exclusions.inc
require conf/distro/include/tolomeo-cve-check.inc
require conf/distro/include/tolomeo-cyclonedx.inc

DISTRO = "acme-prod"
DISTRO_NAME = "Acme Edge Gateway Production Distro"

# Security hardening features
DISTRO_FEATURES += "acl \
                    audit \
                    harden \
                    integrity \
                    pam \
                    security \
                    xattr \
                    "

# Strict QA checks for production
WARN_TO_ERROR_QA = "already-stripped \
                    compile-host-path \
                    install-host-path \
                    installed-vs-shipped \
                    ldflags \
                    pn-overrides \
                    rpaths \
                    staticdev \
                    unknown-configure-option \
                    useless-rpaths \
                    "

ERROR_QA:append = " ${WARN_TO_ERROR_QA}"

tolomeo-cve-check.inc and tolomeo-cyclonedx.inc come from meta-tolomeo-distro, cve-extra-exclusions.inc from poky. Keeping them out of the development flavour keeps those builds fast. See Export SBOM for what the pipeline produces.

Note

secure-boot is absent above because it needs machine-level signing configuration that does not exist yet. Add it with your machine — see Enable FIT Image Signing.


Step 7 — Derive your image

Images follow the same two-file pattern. Create meta-tolomeo-acme-app/recipes-core/images/include/acme-image-common.inc:

require recipes-core/images/include/tolomeo-image-common.inc

# Extra space for on-device development, in KB
IMAGE_ROOTFS_EXTRA_SPACE = "0"

tolomeo-image-common.inc installs the packagegroup-tlm-core, packagegroup-tlm-system-monitoring and packagegroup-tlm-system-update package groups, enables the SSH server and overlayfs-etc, and configures artifact collection — including QEMU artifact patterns, which is why the Step 8 build produces a bootable result.

Then meta-tolomeo-acme-app/recipes-core/images/acme-image-devel.bb:

# Copyright (C) 2026 Acme Corp
# Released under the MIT license (see COPYING.MIT for the terms)

SUMMARY = "Acme Edge Gateway development image"
DESCRIPTION = "Development image for the Acme Edge Gateway, including debugging and test tooling."

AUTHOR = "Acme Corp <devel@example.com>"
HOMEPAGE = "https://www.example.com"

SECTION = "base"
LICENSE = "MIT"

inherit core-image
require include/acme-image-common.inc

# nooelint: oelint.var.badimagefeature
IMAGE_FEATURES += "allow-empty-password \
                   allow-root-login \
                   empty-root-password \
                   post-install-logging \
                   serial-autologin-root \
                   tools-debug \
                   tools-sdk \
                   "

IMAGE_INSTALL += "packagegroup-core-tools-debug \
                  packagegroup-core-tools-profile \
                  parted \
                  python3-pip \
                  systemd-analyze \
                  "

export IMAGE_BASENAME = "acme-image-devel"

export IMAGE_BASENAME must match the recipe name: artifact collection, update-package generation and the deployment file names all key off it.


Step 8 — Verify the wiring with a build

Your machine comes in Step 10. Prove everything else first, so that when your own machine later fails to build you know the fault is in the machine and not the layer stack.

Build against tolomeo-qemux86-64, the QEMU reference machine in meta-tolomeo-qemu. It exercises the whole chain — repository pins, fragment includes, layer resolution, your distribution and image derivations — with no hardware involved. kas dump validates structure, but only a build catches a poky pin that disagrees with meta-tolomeo's or a missing layer dependency.

A target file composes kas/common.yml and states the three coordinates of a build. Create kas/tolomeo-qemux86-64_acme-devel_acme-image-devel.yml:

---
header:
  version: 14

  includes:
    - kas/common.yml

machine: tolomeo-qemux86-64
distro: acme-devel
target: acme-image-devel

The name <machine>_<distro>_<image>.yml makes the whole build matrix visible from ls kas/. Non-image targets — update-artifact generation, a bootloader, a provisioning image — use <machine>_<purpose>.yml, since the purpose implies the distribution.

Check that the configuration resolves before starting a long build. kas dump merges the includes and prints the effective configuration without fetching or building:

kas dump --resolve-refs kas/tolomeo-qemux86-64_acme-devel_acme-image-devel.yml

Check three things: every repository has a concrete commit, every expected layer appears under layers:, and your local_conf_header blocks are present with no unintended duplicates.

Then build:

kas build kas/tolomeo-qemux86-64_acme-devel_acme-image-devel.yml

kas fetches the repositories into sources/, generates build/conf/bblayers.conf and build/conf/local.conf, and runs BitBake. A cold build takes 2–4 hours; with the sstate mirrors reachable, most tasks are cache hits.

The build succeeded when BitBake reports:

NOTE: Tasks Summary: Attempted NNNN tasks of which NNNN didn't need to be rerun and all succeeded.

Your image is in build/tmp-glibc/deploy/images/tolomeo-qemux86-64/. To boot it, follow Run Standalone, using acme-image-devel-tolomeo-qemux86-64.rootfs.ext4.gz.

Inspect what kas generated — the clearest confirmation that the wiring is right:

cat build/conf/bblayers.conf

Your three layers appear as ${TOPDIR}/../meta-tolomeo-acme-*, meta-tolomeo's under ${TOPDIR}/../sources/meta-tolomeo/*, and the fragments' contributions — meta-security, meta-swupdate, meta-vulnscout, meta-virtualization — appear without your having named them.

With this build green, the only untested part of your project is the machine.


Step 9 — Wire the CI configuration

Note

If you host the project on GitLab, the generated .gitlab-ci.yml builds the Yocto project for you, provided the build infrastructure and a runner are available.

kas-ci/ mirrors kas/ but decomposes by axis rather than by build: one fragment per machine, one per distribution, so the pipeline can compose a matrix.

kas-ci/common.yml is the CI counterpart of kas/common.yml, and the template generates it. It includes the same kas/base.yml, so CI cannot drift from local builds, drops the local cache directories, and adds a commented block for injecting the CI job token into recipes that fetch from private repositories:

---
header:
  version: 14
  includes:
    - kas/base.yml

local_conf_header:
  common-path: |-
    TMPDIR = "${TOPDIR}/tmp"

    SSTATE_MIRRORS ?= "file://.* https://reed-cache01.tolomeo.io/sstate-cache/PATH;downloads=PATH \
                       file://.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH\
                       "

    BB_HASHSERVE = ""
    BB_SIGNATURE_HANDLER = "OEBasicHash"

  common-meta-tolomeo-acme-ci: |-
    # --- Private GitLab repository access ---
    # VAR:pn-<package> = "git://gitlab-ci-token:${CI_JOB_TOKEN}@<git-clone-url>;protocol=https;"

Uncomment that last line once per private dependency, replacing VAR with the variable the recipe reads its SRC_URI from and <git-clone-url> with the repository path. If you add feature fragments as in Step 5, include them here too so the two entry points stay identical.

One addition is worth making yourself — PREMIRRORS, in the same common-path block:

    # Fetch source downloads from the shared mirror before going upstream,
    # for faster and more reliable do_fetch.
    PREMIRRORS ?= "git://.*/.*    https://reed-cache01.tolomeo.io/downloads/ \
                   gitsm://.*/.*  https://reed-cache01.tolomeo.io/downloads/ \
                   http://.*/.*   https://reed-cache01.tolomeo.io/downloads/ \
                   https://.*/.*  https://reed-cache01.tolomeo.io/downloads/ \
                   ftp://.*/.*    https://reed-cache01.tolomeo.io/downloads/ \
                   "

Runners start with an empty download directory on every job, so trying the shared mirror before upstream makes do_fetch faster and far less flaky.

Then one fragment per machine:

# kas-ci/tolomeo-qemux86-64.yml
---
header:
  version: 14

  includes:
    - kas-ci/common.yml

machine: tolomeo-qemux86-64

And one per distribution, with no includes at all:

# kas-ci/acme-devel.yml
---
header:
  version: 14

distro: acme-devel

The pipeline combines them with kas's colon operator, merging left to right:

kas build kas-ci/tolomeo-qemux86-64.yml:kas-ci/acme-devel.yml

The distribution fragment omits includes precisely so it can be appended to any machine fragment. With m machines and n distributions you maintain m + n files instead of m × n.

The generated .gitlab-ci.yml already wires the DAVE Yocto CI component. Before the first pipeline run, add the CI/CD variables listed in the template's README.md. If your recipes fetch from private repositories, also grant your project's job token inbound access on each dependency project under Settings → CI/CD → Job token permissions.


Step 10 — Add your machine

The layer stack resolves, your distribution derives from tolomeo.inc, your image builds and CI is wired. What remains describes your hardware, so only you can write it.

Machine configurations go in meta-tolomeo-acme-bsp/conf/machine/, in the same two-level pattern as the distribution: a shared include/acme-<soc>.inc for the SoC and board, and one thin .conf per flavour. ToloMEO expects four parts:

# meta-tolomeo-acme-bsp/conf/machine/acme-<soc>-prod.conf

# 1. Your SoC vendor's base machine
require conf/machine/<vendor-base-machine>.conf

# 2. A storage profile — eMMC/block or NAND/UBI
require conf/machine/include/tolomeo-mmc-storage.inc

# 3. Secure boot support (inert unless the distro enables secure-boot)
require conf/machine/include/u-boot-secure-boot.inc

# 4. Your board's specifics
UBOOT_CONFIG = "emmc"
OVERLAYFS_ETC_DEVICE = "/dev/mmcblk2p5"
TLM_CERTS_DEVICE = "/dev/disk/by-partlabel/certs"

meta-tolomeo-qemu/conf/machine/tolomeo-qemux86-64.conf — the machine you just built against — implements exactly this structure. Read it as a worked example: what looks QEMU-specific in it (QB_MEM, QB_OPT_APPEND) fills the same slot your UBOOT_CONFIG and WIC layout will.

Machine Configuration documents the full contract: both storage profiles, the variables they set, which ones a machine must override, and what the secure-boot include does. Hardware bring-up — kernel, device tree, bootloader port, partition layout — sits outside that contract; for signing and fusing see Enable FIT Image Signing and Enable NXP Authenticated Boot.

Add the vendor BSP layers your SoC needs to repos: in kas/base.yml. For an NXP i.MX part that means meta-freescale, meta-freescale-distro, meta-imx and meta-arm, plus the meta-tolomeo-nxp/kas/common.yml include and an ACCEPT_FSL_EULA block.

Then add a target file and CI fragment per flavour:

kas/acme-<soc>-devel_acme-devel_acme-image-devel.yml
kas/acme-<soc>-prod_acme-prod_acme-image-prod.yml
kas-ci/acme-<soc>-devel.yml
kas-ci/acme-<soc>-prod.yml

Keep meta-tolomeo-qemu enabled once your own machines build. A QEMU target is a cheap way to test distribution and application changes without hardware, and the fastest way to tell a layer-stack regression from a board problem.

To pick up later template improvements, from the repository root:

copier update --trust

Copier reapplies only what changed, preserving your customisations. Review the diff before committing.


Next steps

  • Machine Configuration — the machine contract: storage profiles, secure-boot include, and the variables your board must set
  • kas Configuration — the complete file-by-file contract, fragment reference and naming conventions
  • Architecture — how the meta-tolomeo layers are structured and why
  • Configure Update Modes — enable OTA, local or encrypted updates in your distribution
  • Export SBOM — the CycloneDX and VEX output your production distribution produces
  • Configure Passwords — replace the development credentials