Overview
Trait tokens carry hardware, system and boot mechanism facts through the build engine, eg hw:bluetooth:broadcom. They let layers and build configs express characteristic-aware logic - such as selecting a crypto cipher based on acceleration, conditionally pulling in a firmware layer, or gating a layer entirely on a hardware requirement - without enumerating specific device classes.
Trait tokens flow through the same provider mechanism as plain label tokens (X-Env-Layer-Provides, X-Env-Layer-RequiresProvider) and are evaluated by the same has() function used in trigger when= clauses, conflict expressions, and conditional layer requires (see layer/ for the full metadata reference).
Token Taxonomy
Provider tokens fall into two classes, distinguished by the presence of a colon.
Label tokens - plain identifiers with no colon (systemd, debian-base, rpi-device, network-activator). Arbitrary strings with no registry involvement. Any layer may declare a label token, and a conflict between two layers declaring the same label is always an error.
Trait tokens - colon-separated, namespaced identifiers (hw:bluetooth, boot:gpt, hw:soc:bcm2712). Must be defined in the trait registry. Declaring one triggers automatic hierarchy expansion and Triggers: resolution (see Trait Expansion).
The colon is the sole discriminator. The engine uses its presence to decide whether to validate against the registry and whether to perform expansion. Trait token names are deliberately shell-incompatible - colons are not valid in shell variable names, which makes it impossible to reference them in hooks or scripts. This is intentional and by design.
Namespaces
The registry currently covers two namespaces:
hw:*-
Hardware presence facts - silicon capabilities, onboard peripherals, accessible interfaces. A fact about what the hardware is and has, independent of whether any software in the build uses it.
boot:*-
Boot mechanism facts - how the device boots, what the boot chain requires.
Traits are intended to be simple, easy to read and write, and to understand. Using them to over-specify characteristics can be dangerous. While the registry will grow over time, premature namespace proliferation is as harmful as the wrong name.
Trait Expansion
Declaring a trait token triggers two automatic expansions, both resolved against the registry.
Hierarchical parent inference
Declaring hw:X:Y automatically also declares hw:X. Declaring hw:wlan:broadcom therefore also declares hw:wlan. Layer authors can gate on either the generic token (has('hw:wlan')) or the vendor-specific one (has('hw:wlan:broadcom')). Inferred parents are associated with the same declaring layer as the child.
Triggers: expansion
A registry entry may carry a Triggers: rule that pushes a value onto another token - either unconditionally (the trait equivalent of an implies: cascade) or conditionally, once a build’s accumulated token set makes the condition true. This captures cross-branch derivations that hierarchy alone can’t express. For example, declaring hw:soc:bcm2712 cascades unconditionally into a microarchitecture fact (hw:uarch:cortex-a76), an architecture-extension fact (hw:arch:arm:aes-accel), and an execution-capability fact (hw:exec:kernel:aarch64) - three independent branches, all derivable from the SoC identity without the layer author enumerating them:
X-Env-Trait-bcm2712-Desc: Broadcom BCM2712 - Raspberry Pi 5 / CM5 (quad Cortex-A76)
X-Env-Trait-bcm2712-Valid: bool
X-Env-Trait-bcm2712-Triggers:
when=y set (hw:uarch:cortex-a76)=y
when=y set (hw:arch:arm:aes-accel)=y
when=y set (hw:exec:kernel:aarch64)=y
A when= clause need not be the y sentinel - it can be a genuine, multi-token condition, evaluated to a fixed point against the build’s accumulated trait set:
X-Env-Trait-lpa44-Desc: 44-bit large physical address extension
X-Env-Trait-lpa44-Valid: bool
X-Env-Trait-lpa44-Triggers: when=has('hw:arch:arm:cortex-a72') and has('hw:arch:aarch64') set (hw:mmu:lpa44)=y
|
Important
|
A trait |
Triggered tokens are themselves subject to hierarchical parent inference. hw:pcie is declared directly by device layers rather than implied by SoC, since the same SoC can appear on boards that expose PCIe to the user and boards that don’t - interface and peripheral accessibility belongs on the device, capabilities intrinsic to silicon belong on the SoC.
The Trait Registry
The registry is a set of DEB822 files scanned from one or more trait/ directories - the built-in root first, then a user SRCROOT if supplied (-S/--srcroot). Within each directory the top-level *.deb822 scan is single-level, not recursive.
|
Important
|
A file meant to be reached only via |
File format
Each file is one DEB822 stanza. A stanza can define several sibling local names, each built from X-Env-Trait-<local>- fields - the same shape that layer files use for X-Env-Var-<name>-. There is no Name:/Parent: field - hierarchy comes purely from position in the Include: chain, not from the local name itself. A local name is always a single, colon-free segment. The full dotted-colon token is assembled by prefixing with whichever ancestor’s Include: pulled the file in.
| Field | Meaning |
|---|---|
|
Base/default value (no suffix, same pattern as |
|
Human-readable description. Required. |
|
Validation type - any type the shared validator engine supports ( |
|
Comma-separated list of full trait tokens that must also be active whenever this token is directly declared. Necessity-only validation - see Requires: vs Triggers:. |
|
One |
|
Comma-separated list of files, resolved relative to the current file, that define this token’s children. |
Real example - trait/hw/periph.deb822 and trait/hw/periph/storage.deb822:
X-Env-Trait-storage-Desc: Onboard or directly attached storage
X-Env-Trait-storage-Valid: bool
X-Env-Trait-storage-Include: periph/storage.deb822
X-Env-Trait-nvme-Desc: NVMe storage (soldered or socket-attached)
X-Env-Trait-nvme-Valid: bool
X-Env-Trait-nvme-Requires: hw:pcie:bus
hw:storage:nvme requires PCIe functionality, so declaring NVMe without hw:pcie:bus presence is a contradiction and is rejected.
Requires: vs Triggers:
These are deliberately two different fields rather than one field with a flag, so necessity and derivation can never be confused:
Requires:-
Pure validation. "This token cannot be active without that one also being active." Checked against all activated tokens in the index.
Triggers:-
Derivation/push.
when=y set (target)=valuefires unconditionally whenever the declaring token is active. A genuinely conditional rule (when=<expr>) fires once the expression evaluates true against the build’s accumulated token set, evaluated to a fixed point alongside every other trigger in the tree. The value is validated against the target trait’sValid:type.Triggers:are routinely cross-targeting (an SoC token setting a sibling clock-speed token, as below).
X-Env-Trait-freq-Desc: clock frequency in MHz
X-Env-Trait-freq: 1500
X-Env-Trait-freq-Valid: int:600-3000
X-Env-Trait-turbo-Desc: turbo mode, bumps the clock to 3000
X-Env-Trait-turbo-Valid: bool
X-Env-Trait-turbo-Triggers: when=y set (hw:freq)=3000
Only use an unconditional derivation when the relationship is genuinely both necessary and sufficient. A real device example - trait/hw/device/rpi/sbc/pi5.deb822 cascades a single device identity token into every fact that’s always true of a Pi 5:
X-Env-Trait-pi5-Desc: Raspberry Pi 5 - BCM2712 platform (quad Cortex-A76)
X-Env-Trait-pi5-Valid: bool
X-Env-Trait-pi5-Triggers:
when=y set (hw:soc:bcm2712)=y
when=y set (hw:pcie:bus)=y
when=y set (hw:storage:sd)=y
when=y set (hw:eeprom:rpi)=y
when=y set (hw:wlan:broadcom)=y
when=y set (hw:bluetooth:broadcom)=y
when=y set (hw:ethernet)=y
when=y set (boot:gpt)=y
when=y set (boot:rpi:dtbootpartn)=y
$ rpi-image-gen config --tr hw:device:rpi:pi5
hw:device:rpi:pi5
Desc: Raspberry Pi 5 - BCM2712 platform (quad Cortex-A76)
Type: bool
File: trait/hw/device/rpi/sbc/pi5.deb822
Activates:
boot:gpt
boot:rpi
boot:rpi:dtbootpartn
hw:arch
hw:arch:arm
hw:arch:arm:aes-accel
hw:arch:arm:sha-accel
hw:bluetooth
hw:bluetooth:broadcom
hw:crypto
hw:crypto:aes-accel
hw:crypto:sha-accel
hw:device
hw:device:rpi
hw:eeprom
hw:eeprom:rpi
hw:ethernet
hw:exec
hw:exec:kernel
hw:exec:kernel:aarch64
hw:exec:user
hw:exec:user:aarch32
hw:exec:user:aarch64
hw:isa
hw:isa:armv8
hw:pcie
hw:pcie:bus
hw:soc
hw:soc:bcm2712
hw:soc:pcie
hw:storage
hw:storage:sd
hw:uarch
hw:uarch:cortex-a76
hw:wlan
hw:wlan:broadcom
Duplicate definitions are a hard error
A real (colon-containing) trait token may only ever be defined once, in one file, across every trait/ directory in scope - there is no mechanism to reconcile a second definition. A duplicate will always result in an error. It is not possible to override a built-in token by redeclaring it - the only supported override point is the build config file’s trait: section (see Build Config Overrides). This is the only way a trait can enabled/disabled/set outside of declaring it via a DEB822 stanza.
Bare namespace nodes (eg, hw, boot) carry no declaration fact of their own - only an Include: list. Therefore a different source root may legitimately redefine one to extend it with new children (the OEM mechanism below). Two top-level files in the same root both defining the same namespace node is still an error.
OEM Custom Tokens
An OEM drops a file into their own SRCROOT/trait/ directory. Standard built-in tokens are already loaded, in registry-scan order, before any user directory is scanned - no explicit include of the built-in registry is needed:
# SRCROOT/trait/myboard.deb822
X-Env-Trait-acme-Desc: Custom carrier board tokens
X-Env-Trait-acme-Valid: bool
X-Env-Trait-acme-Include: acme/can.deb822,acme/gps.deb822
# SRCROOT/trait/acme/can.deb822
X-Env-Trait-can-Desc: CAN bus controller on this carrier board
X-Env-Trait-can-Valid: bool
acme/can.deb822 and acme/gps.deb822 must live under a subdirectory (SRCROOT/trait/acme/), not flat alongside myboard.deb822 - see the top-level-scan warning in The Trait Registry. A Triggers: rule may reference any already-loaded built-in token as its target, eg a carrier-identity token cascading into hw:can/hw:gps the same way a device token cascades into its own facts.
Declaring Traits on Layers
X-Env-Layer-Provides
Trait tokens are declared in X-Env-Layer-Provides alongside plain label tokens:
# X-Env-Layer-Name: rpi5
# X-Env-Layer-Category: device
# X-Env-Layer-Desc: Raspberry Pi 5 specific device layer
# X-Env-Layer-Version: 2.0.1
# X-Env-Layer-Requires: rpi-device-base,rpi-linux-2712
# X-Env-Layer-Provides: rpi-device,hw:device:rpi:pi5
Declaring hw:device:rpi:pi5 resolves its full hierarchy and Triggers: cascade into all children (and everything those in turn cascade into), injecting the full expanded set into the provider index.
|
Important
|
A bare |
X-Env-Layer-RequiresProvider
Declares that a token must be present somewhere in the build. The engine validates its existence but imposes no ordering constraint - trait tokens have no position in the build sequence, so this is the only field that makes sense for them:
# X-Env-Layer-RequiresProvider: hw:bluetooth
The build is rejected at validation time if no layer (and no config override) provides hw:bluetooth or a descendant that implies it.
AfterProvider restriction
Trait tokens are not permitted in X-Env-Layer-AfterProvider - rejected at parse time with a clear error. Traits express presence, not build-order position. Use RequiresProvider instead.
Trait Presence Checks: has()
has('token') returns true if the named token is present in the fully-expanded provider index. not has('token') returns true if it is absent. Both forms are valid anywhere a conditional expression is accepted - trigger when= clauses, conflict expressions, and conditional layer Requires:.
has() resolves against the expanded provider index, so has('hw:wlan') is true whether the device declared hw:wlan directly, whether it was inferred because the device declared hw:wlan:broadcom, or whether it arrived via another token’s Triggers: cascade - the evaluator sees the complete expanded set. has() also works with plain label tokens (has('systemd')), which are indexed alongside trait tokens in the same provider index.
has() is an engine metadata-only construct - it is not available in hooks, scripts, or any shell context.
In trigger when= clauses
A trigger can derive a variable’s value purely from which trait is active, so the variable auto-adapts to whatever hardware or config-level state applies, instead of the layer author enumerating every device/variant combination:
# X-Env-Var-storage_type-Triggers:
# when=has('hw:storage:sd') set IGconf_device_storage_type=sd policy=lazy
# when=has('hw:storage:emmc') set IGconf_device_storage_type=emmc policy=lazy
This suits a module that ships with one storage medium by default (asserted unconditionally by its own device-identity trait, eg hw:device:rpi:cm5 cascading to hw:storage:emmc) but where a carrier board or config override can substitute another. A Triggers: rule can just as well set a second, purely informational variable from the same trait - eg deriving a human-facing variant label from hw:storage:sd - so that field reflects trait state rather than being the thing that drives it.
In conflict expressions
has()/not has() in a Conflicts: expression can validate that a declared IGconf_* value is actually backed by the trait state, or catch an impossible combination of traits with no IGconf_* reference at all:
# X-Env-Var-storage_type-Conflicts:
# IGconf_device_storage_type == 'nvme' and not has('hw:storage:nvme')
# IGconf_device_storage_type == 'sd' and not has('hw:storage:sd')
# X-Env-Var-variant-Conflicts: has('hw:storage:emmc') and has('hw:storage:sd')
The first form rejects a selection the trait state doesn’t support - eg choosing nvme storage when nothing asserted hw:storage:nvme. The second is a pure trait-vs-trait check: two mutually-exclusive storage traits both being active is a hardware impossibility, typically from a config override asserting one trait without suppressing the other. If a trait already carries a Requires: on another trait (eg an NVMe token requiring a PCIe token), there’s no need to also duplicate that check here - the registry’s own Requires: validation (see Requires: vs Triggers:) will already cover it, earlier in the pipeline. Conflict expressions run after variable resolution and after the provider index is built, so both IGconf_* values and has() are available.
In conditional layer Requires:
Pull a layer into the build only when its required trait is present:
# X-Env-Layer-Requires: bt-firmware when=has('hw:bluetooth'),rpi-boot-firmware when=has('boot:rpi:vc-firmware')
The condition is appended to the layer name with when=, separated by a space. Unconditional and conditional entries can be mixed freely in the same comma-separated field. See Conditional Requires in the layer metadata reference for the full syntax, the mixed-field form, and the single-pass/first-order evaluation limitation.
|
Important
|
As with trait |
Build Config Overrides
The trait: top-level key in a build config YAML is the single override point, taking precedence over anything declared by the registry or by layers:
trait:
hw:bluetooth: false # carrier board omits BT module
hw:acme:can: true # carrier board adds a custom CAN interface
hw:freq: 1800 # explicit typed override for a non-boolean trait
false-
Removes the named token from the provider index, and any of its children by prefix match -
hw:storage: falseafter a layer declareshw:storage:nvmeremoves both. Suppressing an ancestor does not require separately suppressing each child. true-
Injects the named boolean token as a provider, exactly as if a layer had declared it.
All trait values are validated against their declared type, exactly as if a Triggers: rule had assigned it (hw:freq: 1800 above).
This is the canonical way to represent carrier-board customisations that have no dedicated layer, ie trait: hw:bluetooth: false would be a documented policy decision visible in the config file.
CLI: rpi-image-gen config --trait
List every token in the loaded registry:
rpi-image-gen config --trait
Expand a single token to show its description, type, defining file, and full resolved Triggers:/hierarchy closure:
rpi-image-gen config --trait hw:soc:bcm2712
hw:soc:bcm2712
Desc: Broadcom BCM2712 - Raspberry Pi 5 / CM5 (quad Cortex-A76)
Type: bool
File: trait/hw/soc/broadcom.deb822
Activates:
hw:arch
hw:arch:arm
hw:arch:arm:aes-accel
hw:arch:arm:sha-accel
hw:crypto
hw:crypto:aes-accel
hw:crypto:sha-accel
hw:exec
hw:exec:kernel
hw:exec:kernel:aarch64
hw:exec:user
hw:exec:user:aarch32
hw:exec:user:aarch64
hw:isa
hw:isa:armv8
hw:soc
hw:uarch
hw:uarch:cortex-a76
To include tokens from a custom SRCROOT, pass -S/--srcroot:
rpi-image-gen config -S ./my-board --trait