| .vscode | ||
| .zed | ||
| config | ||
| src | ||
| tests | ||
| .clangd | ||
| .gitignore | ||
| CMakeLists.txt | ||
| CMakePresets.json | ||
| README.md | ||
| run.sh | ||
cpp_template
Template for a C++23 application built from named modules, with one library and one GoogleTest binary per module.
Requirements
| Component | Version | Debian package |
|---|---|---|
| Clang | 22 | clang-22 |
| CMake | 4.3 or newer | cmake |
| Ninja | any | ninja-build |
| spdlog | any | libspdlog-dev |
| FTXUI | any | libftxui-dev |
| GoogleTest | any | libgtest-dev |
| Xlib | any | libx11-dev |
| libopcua | any | libopcua-dev |
CMake 4.3 is required because the CMAKE_EXPERIMENTAL_CXX_IMPORT_STD UUID in
CMakeLists.txt is bound to that version. Ninja is required because module
dependency scanning does not work with the Makefile generators.
LVGL 9.5 and the lv:: C++ wrapper are not packaged for Debian and are fetched
from git at configure time, so the first configure needs network access and
git.
Layout
CMakeLists.txt dependencies, warning flags, subdirectories
src/CMakeLists.txt the app executable
src/modules/CMakeLists.txt one static library per module
src/modules/*.cppm module interface units
tests/CMakeLists.txt one test executable per module
tests/*_test.cpp the tests
config/lv_conf.h LVGL configuration
Modules and their dependencies:
| Module | Library depends on | Content |
|---|---|---|
applog |
spdlog | logger setup, level filtered log functions |
cpuload |
nothing | CPU utilization sampled from /proc/stat, load thresholds |
tui |
math, cpuload, FTXUI, pthread |
live CPU dashboard |
gui |
applog, math, cpuload, lv::lv, X11 |
the same dashboard as an LVGL window |
opcua |
libopcua |
OPC UA server implementation |
Build
cmake --preset default
cmake --build --preset default
./build/default/app
The application writes app.log into the working directory it is started from.
The LVGL window is skipped when DISPLAY is unset.
Run the tests
The test binaries are built together with the application, so a plain build is enough to prepare them.
Run everything through CTest:
ctest --preset default
The preset already sets --output-on-failure. Without presets the same run is:
ctest --test-dir build/default --output-on-failure
Every TEST is registered as its own CTest case by gtest_discover_tests, so
CTest reports 35 cases rather than 5 binaries. Useful variants:
ctest --test-dir build/default -N # list the cases, run none
ctest --test-dir build/default -R StartsOutInAGoodState # regex over the case names
ctest --test-dir build/default -j 8 # run in parallel
ctest --test-dir build/default --rerun-failed # only what failed last time
The regex of -R matches substrings.
A single module can also be tested by calling its binary directly, which gives access to the GoogleTest options:
./build/default/tests/applog_test --gtest_filter='AppLogTest.Writes*'
./build/default/tests/tui_test --gtest_list_tests
./build/default/tests/gui_test --gtest_repeat=10 --gtest_shuffle
./build/default/tests/opcua_test --gtest_list_tests
The five binaries are cpuload_test, applog_test, tui_test, gui_test
and opcua_test.
applog_test creates and deletes app.log in its working directory.
gui_test covers the label formatting and the DISPLAY guard of gui_demo,
not the LVGL event loop, which cannot run unattended. The same split applies to tui_test: it renders
frames at a fixed size through tui::render_dashboard and checks the
non-interactive path of tui_demo, while the event loop itself is not covered.
Tests can be excluded from the build entirely:
cmake --preset default -DBUILD_TESTING=OFF
Adding a module
-
Write
src/modules/<name>.cppmwithexport module <name>;. -
Add the library to
src/modules/CMakeLists.txt:add_library(<name> STATIC) target_sources(<name> PUBLIC FILE_SET CXX_MODULES FILES <name>.cppm ) target_link_libraries(<name> PRIVATE project_options) set_property(TARGET <name> PROPERTY CXX_MODULE_STD ON)The file set has to be
PUBLIC, otherwise the module cannot be imported by other targets.CXX_MODULE_STDis a target property and is not inherited, so it is set on every target, including the test executables. -
Link the new library wherever the module is imported: add
<name>to thetarget_link_librariesofappwhenmain.cppimports it, or to the module library that imports it. -
Write
tests/<name>_test.cppand register it intests/CMakeLists.txt:add_module_test(<name>_test <name>)The helper links the module library,
GTest::gtest_mainand the shared warning flags, and registers the cases with CTest.
Editor integration
.vscode/ and .zed/ contain configure, build and run tasks plus a clangd
setup with --experimental-modules-support. clangd reads
build/default/compile_commands.json, so the project has to be configured once
before diagnostics are correct.
Notes on modules
import std; is still experimental in CMake and prints a warning at generate
time. Header libraries without module support, currently LVGL and lv::, are
included in the global module fragment of the module that wraps them;
CMAKE_CXX_SCAN_FOR_MODULES is switched off around their FetchContent call
because scanning them only costs build time.