Main Function Generation
xrobot gen reads the selected configuration, the registrations in the entry source and the locked Module headers, and writes User/xrobot_main.hpp. The result is ordinary C++: one XRobotMain function that constructs static instances in configuration order and then runs the monitor loop.
Entry Source
Exactly one source file under User/ calls XROBOT_MAIN(). It includes the generated header, constructs the BSP objects, registers the objects configurations may use with XR_REGISTER(name, Type), and finally calls XROBOT_MAIN():
#include "xrobot_main.hpp"
extern "C" void app_main()
{
// ... platform init, construct BSP objects ...
XR_REGISTER(LED_R, LibXR::GPIO);
XR_REGISTER(spi1, LibXR::SPI);
XR_REGISTER(can1, LibXR::CAN);
XROBOT_MAIN();
}
Rules:
-
One name registers one type. To offer the same object as another type, declare a reference and register it separately:
LibXR::CAN& can1 = fdcan1;
XR_REGISTER(fdcan1, LibXR::FDCAN);
XR_REGISTER(can1, LibXR::CAN); -
Register object types, not reference types. Names must be unique and cannot be C++ keywords or macro names.
-
XR_REGISTERcannot appear inside#if/#ifdefblocks; the generator does not evaluate build options. -
Registered objects must be visible where
XROBOT_MAIN()is called and must live as long as the application. -
Only the registrations the selected configuration uses are passed to
XRobotMain, whose parameter types check them; the others expand tostatic_cast<void>(name), which checks no type and causes no unused-variable warning.
In STM32 BSPs these lines are written by libxr gen --xrobot; see Integrate with XRobot.
The Generated Header
xrobot gen # the selected configuration
xrobot gen -c User/RobotConfig/debug.yaml # select another configuration
xrobot gen --no-line-directives # leave out the #line directives
The generated header follows LibXR's .clang-format (Google based, column limit 90, Allman braces), so it reads like hand-written code. For BlinkLED and MadgwickAHRS, the main parts:
// Generated by `xrobot gen` from User/RobotConfig/doc_sample.yaml; do not edit by hand.
#pragma once
#include "BlinkLED.hpp"
#include "MadgwickAHRS.hpp"
#include "libxr.hpp"
#include "thread.hpp"
[[noreturn]] static inline void XRobotMain(LibXR::GPIO& LED_B, LibXR::RamFS& ramfs)
{
// blink_led: xrobot-org/BlinkLED (User/RobotConfig/doc_sample.yaml:2)
#line 2 "User/RobotConfig/doc_sample.yaml"
static BlinkLED blink_led(LED_B, 250);
#line 15 "User/xrobot_main.hpp"
// ahrs: xrobot-org/MadgwickAHRS (User/RobotConfig/doc_sample.yaml:7)
#line 11 "User/RobotConfig/doc_sample.yaml"
static const MadgwickAHRS::Param xr_ahrs_param = {
.beta = 0.033,
.gyro_topic_name = "bmi088_gyro",
.accl_topic_name = "bmi088_accl",
.quaternion_topic_name = "ahrs_quaternion",
.euler_topic_name = "ahrs_euler",
.task_stack_depth = 1536,
#line 11 "User/RobotConfig/doc_sample.yaml"
};
#line 7 "User/RobotConfig/doc_sample.yaml"
static MadgwickAHRS ahrs(ramfs, xr_ahrs_param);
#line 30 "User/xrobot_main.hpp"
for (;;)
{
ahrs.OnMonitor();
LibXR::Thread::Sleep(1000);
}
}
// XR_REGISTER marks a name for the generator; XRobotMain binds and type-checks it.
#define XR_REGISTER(name, ...) static_cast<void>(name)
#define XROBOT_MAIN() ::XRobotMain(LED_B, ramfs)
// xrobot: config "RobotConfig/doc_sample.yaml"
// xrobot: depends "../xrobot.lock"
// xrobot: entry "app_main.cpp"
// xrobot: depends "../Modules/xrobot-org/BlinkLED/BlinkLED.hpp"
// xrobot: depends "../Modules/xrobot-org/MadgwickAHRS/MadgwickAHRS.hpp"
// xrobot: digest 116fe9efdb1e4ad5856423cd4e2416ba021bcd6c0722d5a7f9e5f76eccc3b557
(The example lists the depends lines of two Module headers only; the real file lists the headers of every locked Module.)
- The first line names the configuration the header was generated from. The
#includelines follow, grouped as clang-format does: angle-bracket headers, then quoted headers, with a blank line between the groups and each group sorted by file name; they hold only the Modules in use, the headers ofconstexpr_includesand the headers the generated code needs. - A comment precedes each instance:
<id>: <owner/Repo>[<template arguments>] (<config path relative to the BSP root>:<line>), with one blank line between instances. When a compiler error lands in the header, the comment gives the position in the YAML. - Instances are function-local
staticobjects, constructed in configuration order whenXRobotMainruns. The constructors do all initialization. - Values passed to reference,
std::initializer_listor struct parameters are written asstatic const <type> xr_<instance id>_<parameter> = {...};right before the instance. They live as long as the instance, so a Module may keep a reference to them. Struct values have one field per line with a trailing comma, and nested values and lists that do not fit are broken per entry too; values written as C++ text in the YAML keep their tokens and only get new line breaks. Scalars, names of dependencies,nullptrand&nameare written in the argument itself. A name or expression bound to a reference parameter (such asMake()) is written asstatic const T& xr_... = expression;, so it still refers to the same object instead of copying it. - The
xr_prefix is reserved for the generator: instance ids andXR_REGISTERnames cannot start withxr_(norXR_orxrobot_), andgenreports an error. It also reports two instances that would produce the same static variable name; one of the ids then has to change. - The C++ compiler checks types and values by its rules of implicit conversion. Only when the Module has several constructors that could take the call is the parameter type written, so that the constructor the YAML's parameter names pick is the one called: scalars, pointers and references are then written
xrobot_generated::Implicit<type>(value), a function the header defines. It converts implicitly, so downcasts are still rejected and the compiler still warns about constants that change value. Other types passed by value getstatic_cast<type>(value). #line: every line of an instance and itsstaticvariables is mapped to the YAML line it comes from: the first line of astaticvariable to the line of its argument, each field of a struct to the line of the field's key, a closing brace to the line of the value it closes (GCC reports errors in an initializer there), the first line of the constructor call to the line of the instance, and each later line to the line of its first argument; when one YAML value takes several lines of code, each of them maps to the line of that value. A#lineis written only where the line the compiler counts by itself would differ, and one after the instance points back into the header. Paths are relative to the BSP root, as in the errors ofxrobot, so an editor opens the location of a compiler error from the project folder.--no-line-directivesleaves them out (xrobot setupaccepts it too).- The loop calls each instance's public
void OnMonitor()(where one exists) in configuration order, then sleepssettings.monitor_sleep_msmilliseconds. The return type ofOnMonitoris checked bygenfrom the Module header: one that is certainly notvoidis an error; when the header cannot tell (auto, a type alias, ausingdeclaration), astatic_assert(std::is_void_v<decltype(x.OnMonitor())>, "x.OnMonitor() must return void");for that instance precedes the loop.XROBOT_MAIN()does not return and runs in the calling thread. - The
// xrobot:lines at the end of the file record the configuration, every input generation read (lock, entry source, Module headers) and a digest of their content. LibXR's CMake matches them by line wherever they are, and the digest depends only on the inputs, not on the text of the header. Thedependslines of the Module headers are sorted by comparing the characters of their relative paths (uppercase before lowercase), so the order does not depend on the platform; with byte-identical inputs, Windows and Linux generate the same header and digest. - An unchanged result is not rewritten. Do not edit or commit the file.
Build Check
When the BSP sets XROBOT_MODULES_DIR, LibXR's CMake:
- fails configuration when
User/xrobot_main.hppis missing, suggestingxrobot setuporxrobot gen -c <config>; - prints
XRobot product: <config>during configuration; - recomputes the digest of the inputs' content on every build and fails if it differs from the one the header records or an input no longer exists, naming the
xrobot gen -c <config>command to run.
The check follows content: a new file time alone (after a git checkout or a copy of the project, for example) is no change, and the entry source counts only its XR_REGISTER calls, so changes to its other code need no regeneration. CMake only checks; it never regenerates. Run xrobot gen after changing a configuration, a Module or the registrations in the entry source; run xrobot setup after changing modules.yaml.
IDE
The generated file keeps the path the build uses. The language server uses the compile_commands.json produced by the BSP's actual build.