Build System Integration¶
The build-config command bridges the Syscribe feature model and your build system. It reads a named Configuration, resolves which features are selected, and emits a set of named build variables in the format your toolchain expects.
1. Annotating FeatureDefs with buildExports¶
Add a buildExports: list to any FeatureDef to declare what the build system should see when that feature is selected or deselected:
# model/Features/ABS.md
---
type: FeatureDef
id: FEAT-ABS
name: ABS
buildExports:
- var: ENABLE_ABS # build variable name
whenSelected: 1 # value when the feature is selected (default: 1)
whenDeselected: 0 # value when deselected; omit to suppress the var entirely
---
Key semantics:
var(required) — the variable name emitted into the build artifact.whenSelected(optional, default1) — the value assigned when the feature is selected in theConfiguration.whenDeselected(optional, absent by default) — the value assigned when the feature is deselected. If absent the variable is omitted from the output entirely when the feature is not selected.
A single FeatureDef may declare multiple entries, each with a different var:
buildExports:
- var: ENABLE_CAMERA
whenSelected: 1
whenDeselected: 0
- var: CAM_RESOLUTION
whenSelected: 1080
2. Adding buildVar to parameters¶
A FeatureDef parameter with buildVar: maps its bound value directly into a build variable when the feature is selected:
# model/Features/Radar.md
---
type: FeatureDef
id: FEAT-RADAR
name: Radar
parameters:
- name: maxRange
type: ScalarValues::Real
range: "0..200"
default: 100
buildVar: RADAR_MAX_RANGE # emitted when the feature is selected
---
When the Configuration has a parameterBindings: entry for this parameter, the bound value is used; otherwise the parameter default: is the fallback.
3. Configuration buildOverrides¶
Use buildOverrides: on a Configuration to set a variable unconditionally. buildOverrides wins over everything — it is applied last and always replaces any value contributed by buildExports or buildVar.
# model/Configs/CONF-PREMIUM-001.md
---
type: Configuration
id: CONF-PREMIUM-001
name: Premium
status: approved
featureModel: Features
features:
Features::ABS: true
Features::Radar: true
parameterBindings:
Features::Radar.maxRange: 150
buildOverrides:
DEBUG_BUILD: 0 # suppress debug regardless of any feature
---
buildOverrides also resolves E050 conflicts: when two selected features both declare the same variable with different whenSelected values, adding a buildOverrides entry silences the error and emits the override value.
4. Running build-config¶
# CMake cache file
syscribe -m model/ build-config --config CONF-PREMIUM-001 --format cmake
# C/C++ header
syscribe -m model/ build-config --config CONF-PREMIUM-001 --format c-header
# GNU Makefile include
syscribe -m model/ build-config --config CONF-PREMIUM-001 --format makefile
# Shell environment (source in CI)
syscribe -m model/ build-config --config CONF-PREMIUM-001 --format env
# JSON (usable by any build system or script)
syscribe -m model/ build-config --config CONF-PREMIUM-001 --format json
# Kconfig (Zephyr, Linux kernel)
syscribe -m model/ build-config --config CONF-PREMIUM-001 --format kconfig
Example cmake output¶
# Generated by: syscribe build-config --config CONF-PREMIUM-001 --format cmake
# Configuration: CONF-PREMIUM-001 — "Premium"
set(ENABLE_ABS 1)
set(RADAR_MAX_RANGE 150)
Example c-header output¶
/* Generated by: syscribe build-config --config CONF-PREMIUM-001 --format c-header */
/* Configuration: CONF-PREMIUM-001 — "Premium" */
#ifndef SYSCRIBE_CONFIG_H
#define SYSCRIBE_CONFIG_H
#define ENABLE_ABS 1
#define RADAR_MAX_RANGE 150
#endif /* SYSCRIBE_CONFIG_H */
Example kconfig output¶
# Generated by: syscribe build-config --config CONF-PREMIUM-001 --format kconfig
# Configuration: CONF-PREMIUM-001 — "Premium"
CONFIG_ENABLE_ABS=y
CONFIG_RADAR_MAX_RANGE=150
Variables are always emitted in alphabetical order for reproducible diffs.
5. The --all-configs CI matrix pattern¶
Pass --all-configs to generate build variables for every Configuration in the model, emitted as a JSON array. This is useful for driving a CI matrix:
Output:
[
{
"config": "CONF-BASE-001",
"name": "Base",
"vars": { "ENABLE_ABS": 0 }
},
{
"config": "CONF-PREMIUM-001",
"name": "Premium",
"vars": { "ENABLE_ABS": 1, "RADAR_MAX_RANGE": 150 }
}
]
When a configuration has an E050 conflict the entry gets an "error" field instead of an empty "vars".
GitHub Actions matrix example¶
jobs:
generate-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.gen.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- name: Generate config matrix
id: gen
run: |
MATRIX=$(syscribe -m model/ build-config --all-configs | jq -c '[.[] | {config: .config}]')
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
build:
needs: generate-matrix
runs-on: ubuntu-latest
strategy:
matrix:
include: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- name: Build for ${{ matrix.config }}
run: |
syscribe -m model/ build-config --config ${{ matrix.config }} --format cmake \
> build/${{ matrix.config }}.cmake
cmake -C build/${{ matrix.config }}.cmake .
6. The --prefix option¶
Prepend a string to every emitted variable name to avoid namespace collisions:
Output:
7. Validation: E050 and W050¶
E050 — conflicting variable from two selected features¶
E050 is part of the default validate pass. It fires when two or more selected features both declare the same buildExports variable name with different whenSelected values and the conflict is not resolved by a buildOverrides: entry on the Configuration.
To resolve: add buildOverrides: on the Configuration with the authoritative value, or rename one of the variables.
W050 — selected feature with no build mapping (not currently emitted)¶
W050 is catalogued as an opt-in warning for a selected feature that declares neither buildExports: nor any parameter with buildVar: (no build-system representation). In the current release, however, neither validate nor build-config prints it: the check runs inside the build-variable resolver but its findings are not surfaced, so validate --deny W050 has no effect. To find features that contribute nothing to the build, inspect the build-config output for missing variables, or add buildExports: to every feature that should drive the build.