Building source code#
The running example consists of a small C++ service and its client. Both are built on three S-CORE modules:
mw::com from score_communication for the inter-process communication, the CRC32 calculation
from score_baselibs and mw::log from score_logging for logging. The sources are located in the
reference integration:
File |
Content |
|---|---|
Service interface and version check, shared by service and consumer |
|
Creation, CRC32 check and comparison of the reported S-CORE version |
|
Generates a C++ header with the module versions from |
|
The service (provider) |
|
mw::com configuration of the service |
|
mw::log configuration of the service |
|
deploy/platform_info/platform_info_test.cpp, gen_platform_info_test.py |
Unit tests of the version check and of the header generator |
The client (consumer) |
Service interface#
An mw::com service is described by a C++ interface template. The platform info service has a single field,
platform_info, i.e. a value that the service sets and that clients subscribe to (WithNotifier):
1// Fixed-size, zero-terminated strings: the value lives in shared memory.
2struct ModuleVersion
3{
4 std::array<char, kNameSize> name;
5 std::array<char, kVersionSize> version;
6};
7
8struct PlatformInfo
9{
10 std::array<char, kTimestampSize> known_good_timestamp;
11 std::uint32_t module_count;
12 std::array<ModuleVersion, kMaxModules> modules;
13 std::uint32_t crc32;
14};
15
16template <typename Trait>
17class PlatformInfoInterface : public Trait::Base
18{
19 public:
20 using Trait::Base::Base;
21
22 typename Trait::template Field<PlatformInfo, score::mw::com::WithNotifier> platform_info_{*this, "platform_info"};
23};
24
25using PlatformInfoSkeleton = score::mw::com::AsSkeleton<PlatformInfoInterface>;
26using PlatformInfoProxy = score::mw::com::AsProxy<PlatformInfoInterface>;
The same interface yields both sides of the communication: the skeleton is used by the service to provide the field,
the proxy is used by the client to access it. Because mw::com exchanges the data via shared memory,
PlatformInfo only contains fixed-size types.
The service implementation is short: it creates a logger, creates the skeleton for the instance specifier
score/platform/info, publishes the value and offers the service until it is stopped:
1score::mw::com::runtime::InitializeRuntime(score::string_manipulation::GetArguments(argc, argv));
2auto& logger = score::mw::log::CreateLogger("PINF", "platform info");
3
4auto instance_specifier = score::mw::com::InstanceSpecifier::Create(std::string{kInstanceSpecifier});
5auto skeleton = PlatformInfoSkeleton::Create(std::move(instance_specifier).value());
6
7const auto platform_info = MakePlatformInfo();
8skeleton->platform_info_.Update(platform_info);
9skeleton->OfferService();
10logger.LogInfo() << "platform_info_service: offering " + std::string{kInstanceSpecifier} + " for S-CORE " +
11 ToString(platform_info);
The client does the opposite: it searches the service via service discovery, creates a proxy, subscribes to the field and checks the received value:
1auto found = PlatformInfoProxy::FindService(instance_specifier.value());
2auto proxy = PlatformInfoProxy::Create(handles.front());
3
4proxy->platform_info_.Subscribe(1U);
5proxy->platform_info_.GetNewSamples(
6 [&info](score::mw::com::SamplePtr<PlatformInfo> sample) noexcept { info = *sample; }, 1U);
7
8if (!score::platform::info::IsValid(info.value())) // CRC32 check with score_baselibs
9{
10 return Fail("the S-CORE version reported by the platform fails the CRC32 check");
11}
12const auto differences = score::platform::info::Compare(score::platform::info::MakePlatformInfo(), info.value());
The snippets omit the error handling and the timeouts; see the source files for the complete implementation.
Both programs log via the mw::log logger with the context id PINF. How these logs are configured and collected
is described in the collecting logs chapter.
mw::com configuration#
Which service type, binding and instance belong to the instance specifier score/platform/info is not compiled
into the binaries, but defined in the mw::com configuration
etc/mw_com_config.json:
1"serviceInstances": [
2 {
3 "instanceSpecifier": "score/platform/info",
4 "serviceTypeName": "/score/platform/PlatformInfo",
5 "instances": [
6 {
7 "instanceId": 1,
8 "asil-level": "QM",
9 "binding": "SHM",
10 "fields": [
11 { "fieldName": "platform_info", "numberOfSampleSlots": 2, "maxSubscribers": 5 }
12 ]
13 }
14 ]
15 }
16]
mw::com reads ./etc/mw_com_config.json relative to the working directory, or the file passed with the
command line option --service_instance_manifest. Service and client use the same file, so they agree on the
service definition.
Bazel targets#
The Bazel targets of the service are defined in deploy/platform_info/BUILD:
1# The S-CORE module versions the platform is built from, as compile-time constants.
2genrule(
3 name = "platform_info_data",
4 srcs = ["//:known_good.json"],
5 outs = ["platform_info_data.h"],
6 cmd = "$(execpath :gen_platform_info) $(execpath //:known_good.json) $@",
7 tools = [":gen_platform_info"],
8)
9
10# Service interface and version check: shared by service and consumer.
11cc_library(
12 name = "platform_info_interface",
13 srcs = [
14 "platform_info.cpp",
15 ":platform_info_data",
16 ],
17 hdrs = ["platform_info.h"],
18 visibility = ["//showcases:__subpackages__"],
19 deps = [
20 "@score_baselibs//score/hash/code/crc:crc_ieee",
21 "@score_communication//score/mw/com",
22 ],
23)
24
25cc_test(
26 name = "platform_info_test",
27 srcs = ["platform_info_test.cpp"],
28 deps = [
29 ":platform_info_interface",
30 "@googletest//:gtest_main",
31 ],
32)
33
34cc_binary(
35 name = "platform_info_service",
36 srcs = ["platform_info_service.cpp"],
37 deps = [
38 ":platform_info_interface",
39 "@score_baselibs//score/string_manipulation/arguments",
40 "@score_communication//score/mw/com",
41 "@score_logging//score/mw/log",
42 ],
43)
The genrule
platform_info_datagenerates the headerplatform_info_data.hfromknown_good.json. Bazel regenerates it wheneverknown_good.jsonchanges, so service and consumer always carry the versions they were built from.The cc_library
platform_info_interfacecontains the shared interface. Its dependencies reference targets of other Bazel modules with the@<module>//<package>:<target>syntax, here mw::com and the CRC32 calculation.The cc_test
platform_info_testcontains the unit tests of the library, written with GoogleTest.The cc_binary
platform_info_serviceproduces the executable of the service. It additionally depends on mw::log fromscore_logging.
The consumer is defined in the same way in showcases/standalone/BUILD and depends on the shared interface library:
1cc_binary(
2 name = "platform_info_consumer",
3 srcs = ["platform_info_consumer.cpp"],
4 deps = [
5 "//deploy/platform_info:platform_info_interface",
6 "@score_baselibs//score/string_manipulation/arguments",
7 "@score_communication//score/mw/com",
8 "@score_logging//score/mw/log",
9 ],
10)
Toolchains and platforms#
Before building the binaries, it is helpful to introduce two fundamental Bazel concepts: toolchains and platforms.
A complete explanation is beyond the scope of this small tutorial, but the following brief overview is sufficient:
A Bazel toolchain specifies which compiler and linker are used (e.g., GCC, QCC, Ferrocene).
A Bazel platform defines the target CPU architecture and OS (e.g., x86_64 Linux or x86_64 QNX).
All S-CORE modules use the same toolchains, provided by
score_bazel_cpp_toolchains (GCC and QCC) and
score_toolchains_rust (Ferrocene),
and the platforms defined in score_bazel_platforms.
In the module template, they are registered in the MODULE.bazel block S-CORE Toolchains (DO NOT MODIFY), e.g.:
1gcc = use_extension("@score_bazel_cpp_toolchains//extensions:gcc.bzl", "gcc")
2
3gcc.toolchain(
4 name = "score_gcc_x86_64_toolchain",
5 target_cpu = "x86_64",
6 target_os = "linux",
7 use_default_package = True,
8 version = "12.2.0",
9)
The template contains such toolchains for Linux (x86_64, aarch64), QNX SDP 8.0 (x86_64, aarch64), Red Hat AutoSD and Elektrobit corbos Linux for Safety Applications. The file score.bazelrc/score_toolchain.bazelrc combines each platform with its toolchains into a build configuration:
1build:score-linux-x86_64 --config=_score_common
2build:score-linux-x86_64 --extra_toolchains=@score_gcc_x86_64_toolchain//:x86_64-linux-gcc_12.2.0
3build:score-linux-x86_64 --platforms=@score_bazel_platforms//:x86_64-linux-gcc_12.2.0-posix
4
5build:score-qnx-x86_64 --config=_score_common
6build:score-qnx-x86_64 --platforms=@score_bazel_platforms//:x86_64-qnx-sdp_8.0.0-posix
7build:score-qnx-x86_64 --extra_toolchains=@score_qcc_x86_64_toolchain//:x86_64-qnx-sdp_8.0.0
8build:score-qnx-x86_64 --extra_toolchains=@score_toolchains_rust//toolchains/ferrocene:ferrocene_x86_64_pc_nto_qnx800
The reference integration uses the same setup, but names its configurations after the reference images:
linux-x86_64, linux-aarch64, qnx-x86_64, qnx-aarch64, autosd-x86_64 and eb-aarch64
(see its .bazelrc).
There, the shared configuration _common also sets build settings of the integrated modules, e.g.:
build:_common --@score_communication//score/memory/shared/flags:use_typedshmd=False
build:_common --@score_baselibs//score/json:base_library=nlohmann
These options must be set in the repository that builds the binaries, because Bazel builds all dependent modules within the configuration of the root module.
Tip
The CI/CD pipeline uses its own QNX license when building the code with QNX. If you want to build the source code with
the QNX compiler locally, you need a QNX license, e.g. the free community license QNX Everywhere, installed at
/opt/score_qnx/license/licenses, and access to the QNX SDP download. The setup is described in the
QNX integration guide
of score_bazel_cpp_toolchains. Running the QNX images in QEMU additionally needs the host setup described in the
QNX QEMU runner README.
Building the binaries#
Always use an explicit build configuration. In the reference integration:
Linux x86_64:
1bazel build --config=linux-x86_64 //deploy/platform_info:platform_info_service //showcases/standalone:platform_info_consumer
QNX x86_64 cross-compilation:
1bazel build --config=qnx-x86_64 //deploy/platform_info:platform_info_service //showcases/standalone:platform_info_consumer
In your own module, use the configurations of the template instead, e.g.
bazel build --config=score-linux-x86_64 //score/....
After a successful build, the binaries can be found in the build folder:
1bazel-bin/deploy/platform_info/platform_info_service
2bazel-bin/showcases/standalone/platform_info_consumer
Running the example#
The Linux binaries can be started directly on the host. mw::com needs the directory for its service discovery,
the service is started in the background in its directory, so that it finds its configuration in etc/,
and the consumer gets the deployed mw::com configuration and its own logging configuration:
1mkdir -p /tmp/mw_com_lola/service_discovery
2cd deploy/platform_info
3../../bazel-bin/deploy/platform_info/platform_info_service &
4MW_LOG_CONFIG_FILE=../../showcases/standalone/configs/platform_info_consumer.json \
5 ../../bazel-bin/showcases/standalone/platform_info_consumer --service_instance_manifest etc/mw_com_config.json
6kill %1
The service logs the S-CORE version it reports, and the consumer confirms that it found the service and that the versions match. The output looks like this (shortened):
... ECU1 PINS PINF log info verbose 1 platform_info_service: offering score/platform/info for S-CORE known_good 2026-08-28T09:19:12Z
score_baselibs 41f5a4c92163dc1f597116094ed34881ddee7fa3
...
... ECU1 PINC PINF log info verbose 1 platform_info_consumer: found the platform info service
... ECU1 PINC PINF log info verbose 1 platform_info_consumer: platform runs S-CORE known_good 2026-08-28T09:19:12Z
...
... ECU1 PINC PINF log info verbose 1 platform_info_consumer: S-CORE version check passed (8 modules match)
PINS and PINC are the application ids of service and consumer, PINF is the context id of their messages.
Further lines, e.g. messages with the context id lola from mw::com or status output of the logging client such as
TryOpenClientConnection, can be ignored here; the collecting logs chapter explains the logging.
If the service is not running, or reports a different S-CORE version, the consumer exits with 1 and names the reason,
e.g. the module whose version differs.
Running the unit tests#
The unit tests of the version check and of the header generator run on the host:
bazel test --config=linux-x86_64 //deploy/platform_info:platform_info_test //deploy/platform_info:gen_platform_info_test
Now it is time to make sure that every change is built and tested automatically.