SPLED is a Software Product Line (SPL) demonstrating variant-based configuration management. Each variant (Disco, Sleep, Spa, etc.) compiles into separate binaries using shared components with different feature configurations.
Key architectural concepts:
- Variants: Product configurations in
variants/<VariantName>/containingconfig.txt(KConfig features),parts.cmake(component selection), andconfig.cmake(CMake settings) - Components: Modular C code in
components/<name>/with CMakeLists.txt using SPL-specific macros - Build Kits:
prod(production C code) vstest(C++ with GoogleTest for unit testing) - Feature Configuration: KConfig system (
KConfigfile) drives conditional compilation via CMake variables
Always run .\build.ps1 -install when:
- First cloning the repository
- Switching branches (dependencies may have changed)
- After pulling updates
On Linux/macOS or inside a devcontainer, use the peer script ./build.sh --install. build.sh is a true peer of build.ps1 with the same steps and flag names (Bash uses --flag, PowerShell uses -flag), so every workflow below has a build.sh equivalent.
Always start VS Code with: .\build.ps1 -startVSCode to ensure proper environment variables and Python virtual environment activation (.venv with Poetry dependencies).
VS Code users can build directly using the CMake extension via .vscode configuration files:
- Build Kit selection (
.vscode/cmake-kits.json): Choose betweenprod(production C code) andtest(GTest C++ unit tests) - Variant selection (
.vscode/cmake-variants.json): Select variant (Disco, Spa, Sleep, Base/Dev, IDEA/Sloemada) and build type (Debug/Release) - CMake settings (
.vscode/settings.json): Configures build directory patternbuild/${variant}/${buildKit}/${buildType}, Ninja generator, and passes BUILD_KIT/BUILD_TYPE variables to CMake - Use CMake extension's status bar to select kit/variant/build type, then build using CMake commands or tasks
# Interactive variant selection
.\build.ps1 -build
# Specific variant
.\build.ps1 -build -variants Disco
# Clean build
.\build.ps1 -build -variants Spa -clean
# Test build (includes unit tests)
.\build.ps1 -build -buildKit test -buildType DebugBuild outputs: build/<VariantName>/<BuildKit>/<BuildType?>/
Tests are Python-based using pytest for build validation and report checks:
# Run all tests
.\build.ps1 -selftests
# Filtered tests
.\build.ps1 -selftests -filter "Disco"
# Specific markers (see pytest.ini)
.\build.ps1 -selftests -marker "build_debug"Component unit tests: GTest/GMock in components/*/test/*.cc files, run via test build kit.
View reports: Use tasks "Open variant test report" / "Open variant coverage report" from VS Code.
CI runs on GitHub Actions (.github/workflows/ci.yml) for every push/PR to develop and release/*, plus a nightly schedule. A PR only merges when all checks are green — branch protection enforces this, so a red build blocks the merge by definition. Don't add manual "remember to check CI" notes to docs or PRs; the gate is automatic.
Jobs:
determine-gate— computes thegate_*quality-gate marker once (by event/branch) and shares it with both build jobs vianeeds.test-on-windows(windows-2025) —build.ps1 -installthen-selftests -marker <gate>.test-on-linux(ubuntu-24.04) —build.sh --installthen--selftests --marker <gate>.
CI is a thin wrapper: it only sets up the OS and calls the build scripts, so "green in CI" ⇔ "works locally". Runners are pinned to explicit images (never *-latest), so an OS/toolchain bump is always a reviewable change rather than a surprise.
Component CMakeLists.txt files use spl-core macros (not standard CMake):
# Add source files (production code)
spl_add_source(src/my_component.c)
# Add test files (GTest, only in test build kit)
spl_add_test_source(test/test_my_component.cc)
# Declare dependencies on other components
spl_add_required_interface(components/rte)
# Finalize component (must be last)
spl_create_component(LONG_NAME "My Component")Conditional dependencies use KConfig variables from config.txt:
if(AUTO_OFF STREQUAL "True")
spl_add_required_interface(components/auto_off)
endif()Variant's parts.cmake lists components with spl_add_component(components/<name>).
Features defined in KConfig (menuconfig syntax) generate CMake variables via config.txt:
CONFIG_BLINKING=y→ CMake variableBLINKING="True"# CONFIG_AUTO_OFF is not set→ CMake variableAUTO_OFF="False"
Edit feature config: .\build.ps1 -command ".venv\Scripts\poetry run guiconfig" (requires KCONFIG_CONFIG env var set to variant's config.txt).
Check feature values in source code via generated autoconf.h header.
- No direct CMake invocation: Always use
build.ps1wrapper (handles variant selection, environment, Poetry, etc.) - Component isolation: Each component has own CMakeLists.txt, must declare all dependencies explicitly
- Test location: Python integration tests in
test/<VariantName>/, C++ unit tests incomponents/*/test/ - Dependency management: Python deps via Poetry (
pyproject.toml), C/C++ external deps fetched by CMake FetchContent
- RTE (Runtime Environment):
components/rte- shared interfaces, all components depend on it - Main entry:
components/main/src/main.c- calls OS scheduler - OS abstraction:
components/os- simple task scheduler (configurable period via KConfig) - Platform types:
components/platform_types- standard types (uint8_t, etc.)
Components communicate via RTE signals/runnable interfaces (see rte.h for patterns).
- build.ps1 - Entry point for all build/test operations (Windows)
- build.sh - Peer entry point for Linux/macOS/devcontainer
- .github/workflows/ci.yml - Windows + Linux CI, thin wrapper over the build scripts
- CMakeLists.txt - Includes variant config and spl-core framework
- KConfig - Feature model definition
- variants/Disco/parts.cmake - Example component selection
- components/spled/CMakeLists.txt - Example conditional dependencies
- test/Disco/test_Disco.py - Example pytest structure using
SplBuildhelper