How to Integrate a New Module into the Reference Integration#
This guide explains how to add a new S-CORE module to the reference_integration repository and how to make its applications part of the reference images. It uses the running example of this guide, the platform info service and its consumer, as a concrete example. A more detailed step-by-step guide is available in the integration process how-to of the reference integration.
Background: Why known_good.json?#
Each S-CORE module lives in its own Git repository and evolves independently. The reference integration continuously validates that a specific combination of module versions builds and tests successfully together – the so-called known-good state.
Keeping this state in a dedicated JSON file instead of directly in Bazel’s
MODULE.bazel serves several purposes:
Single source of truth. All version pins, repository URLs, and integration metadata (test exclusions, language info, Bazel patches) are in one place and machine-readable. Tooling such as CI pipelines, workspace generators, and the Bazel module generator all consume the same data. The running example uses it as well: the platform info service reports exactly the versions of the
target_swmodules listed inknown_good.json.Separation of concerns.
known_good.jsonis the what (which commit of which module), while the generated.MODULE.bazelfile is the how (the Bazel-specific syntax). Keeping them separate avoids manual errors in Bazel syntax and keeps the intent readable without Bazel knowledge.Tooling support. Because the version data is structured JSON, it can be consumed by additional scripts in
scripts/known_good/:update_module_from_known_good.py– regenerates the BazelMODULE.bazelfiles.update_module_latest.py– updates the pinned commits to the latest state of the modules.override_known_good_repo.py– pins individual modules to a different commit (e.g. for testing a PR) without touching the canonical file.known_good_to_workspace_metadata.py– generates the workspace manager file (.gita-workspace.csv) used to clone all module repositories locally.
Reproducibility. Every entry is pinned to a full SHA-1 commit hash or to a released version, making the integrated state fully reproducible regardless of branch tip movements.
Overview#
There are two files that together define which modules are integrated:
known_good.json– the single source of truth for all module versions (Git hashes or versions, repository URLs, and metadata such as test configuration). This file is edited manually.bazel_common/score_modules_<category>.MODULE.bazel– generated fromknown_good.jsonby a script. Do not edit this file manually.
What ends up on the reference images is organized in three layers:
Layer |
Location |
Content |
|---|---|---|
1 – Image |
|
Boot, kernel, drivers, root filesystem skeleton |
2 – Platform |
|
The deployed S-CORE platform, e.g. |
3 – Showcases |
|
Applications that demonstrate and verify the platform, e.g. the |
A showcase must not ship the component it verifies: the platform info consumer brings no service of its own, so it only passes on an image that deploys the service. The layers and the contract between them are described in deploy/README.md.
The general workflow is:
Add the new module entry to
known_good.json.Run the generation script to update the
.MODULE.bazelfile.Deploy the platform services of the module and add showcases, so the module appears on the images and in the S-CORE CLI.
Verify the integration and add a platform integration test.
Step 1 – Add the module to known_good.json#
Open known_good.json and add your module under the appropriate category.
There are currently two categories:
target_sw– modules that are part of the target software stack (deployed on the target platform, e.g. C++ / Rust runtime components).tooling– build and development tooling modules.
A runtime module goes under target_sw. The hash is the full SHA-1 commit of the version to integrate:
"score_my_module": {
"repo": "https://github.com/eclipse-score/my_module.git",
"hash": "0000000000000000000000000000000000000000",
"metadata": {
"code_root_path": "//score/...",
"langs": ["cpp"]
}
}
Note
The platform info service of the running example is no S-CORE module, but a platform service of the reference
integration itself, so it has no entry in known_good.json. It depends on the entries of
score_communication and score_baselibs, and reports all target_sw entries as the S-CORE version.
Metadata fields explained#
Field |
Required |
Description |
|---|---|---|
|
yes |
HTTPS URL to the Git repository (ending in |
|
yes, or |
Full SHA-1 commit hash of the version to integrate (generates a |
|
yes, or |
Released version from the Bazel registry (generates a |
|
no |
List of Bazel label paths to |
|
no |
If |
|
no |
Bazel target pattern for the module’s source root.
Defaults to |
|
no |
Languages present in the module: |
|
no |
Additional flag values passed to test runs. |
|
no |
Bazel target patterns excluded from CI test runs (e.g. known-failing or platform-specific tests). |
|
no |
Rust coverage profile name. Defaults to |
Step 2 – Regenerate the .MODULE.bazel file#
After editing known_good.json, run the generation script and refresh the lockfile:
python3 scripts/known_good/update_module_from_known_good.py
bazel mod deps --lockfile_mode=update
This regenerates bazel_common/score_modules_target_sw.MODULE.bazel
with the correct bazel_dep and git_override blocks for every module
listed in known_good.json. For the example entry, the result looks like:
bazel_dep(name = "score_my_module")
git_override(
module_name = "score_my_module",
commit = "0000000000000000000000000000000000000000",
remote = "https://github.com/eclipse-score/my_module.git",
)
Commit the regenerated files together with the known_good.json change; CI checks that they are in sync.
Alternatively, use the VS Code task:
Terminal -> Run Task… -> “Switch Bazel modules to git_overrides”
Note
For local cross-module development, use --override-type local_path
(or the VS Code task “Switch Bazel modules to local_path_overrides”)
so Bazel picks up a local checkout instead of downloading from Git.
See the repository README for the full workspace setup procedure.
Step 3 – Deploy services and add showcases#
Deploy platform services (layer 2)#
Daemons and services that are part of the platform are packaged in deploy/<name>/BUILD with
pkg_files and placed under /usr/bin/<name>
on the target. The platform info service is deployed like this
(deploy/platform_info/BUILD):
pkg_files(
name = "platform_info_service_bin",
srcs = [":platform_info_service"],
attributes = pkg_attributes(mode = "0755"),
prefix = "usr/bin/platform_info",
strip_prefix = strip_prefix.files_only(),
)
pkg_files(
name = "platform_info_service_configs",
srcs = [
"etc/logging.json",
"etc/mw_com_config.json",
],
attributes = pkg_attributes(mode = "0644"),
prefix = "usr/bin/platform_info/etc",
strip_prefix = strip_prefix.files_only(),
)
The binary is placed as /usr/bin/platform_info/platform_info_service, its mw::com and mw::log configuration
in /usr/bin/platform_info/etc.
Both targets are added to the aggregate platform_bundle in
deploy/BUILD,
which every image consumes. Modules that ship only libraries, e.g. score_communication, have nothing to deploy.
Add a showcase (layer 3)#
If the module provides runnable example binaries, expose them to the
S-CORE CLI by adding files under showcases/standalone/.
showcases/standalone/platform_info.score.json – describes the examples
shown in the interactive CLI menu. The Platform Version Check starts the deployed service,
runs the consumer against it with its own logging configuration and returns the exit code of the consumer:
[
{
"name": "Platform Version Check",
"description": "A consumer must find the platform info service deployed by the platform and receive the S-CORE version it was built for",
"apps": [
{
"path": "/bin/sh",
"args": [
"-c",
"cd /usr/bin/platform_info || exit 1; ./platform_info_service >/dev/null 2>&1 & provider=$!; MW_LOG_CONFIG_FILE=/showcases/data/platform_info/etc/logging.json /showcases/bin/platform_info_consumer --service_instance_manifest /usr/bin/platform_info/etc/mw_com_config.json; rc=$?; kill $provider; wait $provider; exit $rc"
],
"env": {}
}
]
}
]
showcases/standalone/BUILD – add a score_pkg_bundle entry that
bundles the binary, the JSON config file and the logging configuration of the consumer,
which ends up as /showcases/data/platform_info/etc/logging.json:
score_pkg_bundle(
name = "platform_info",
bins = ["//showcases/standalone:platform_info_consumer"],
config_data = ["//showcases/standalone:platform_info.score.json"],
custom_layout = {
"//showcases/standalone/configs:platform_info_consumer.json": "etc/logging.json",
},
package_dir = "standalone",
)
For a binary of an integrated module, reference it with its module label instead,
e.g. bins = ["@score_my_module//score/examples:my_example"].
showcases/BUILD – reference the new _pkg_files target in the
top-level showcases bundle and in the showcases_bundle file group, which the QNX images consume,
so it ends up in every image:
score_pkg_bundle(
name = "showcases",
bins = ["//showcases/cli"],
other_package_files = [
"//showcases/standalone:comm_pkg_files",
...
"//showcases/standalone:platform_info_pkg_files", # added
],
package_dir = "showcases",
)
pkg_filegroup(
name = "showcases_bundle",
srcs = [
":showcases_pkg_files",
...
"//showcases/standalone:platform_info_pkg_files", # added
],
prefix = "showcases",
visibility = ["//visibility:public"],
)
The CLI auto-detects all *.score.json files at runtime, so no further
registration is needed.
Step 4 – Verify the integration#
Run a Bazel build for the new targets to confirm the integration is working:
bazel build --config=linux-x86_64 //deploy/platform_info:platform_info_service //showcases/standalone:platform_info_consumer
For a module from known_good.json, build its targets via the module label, e.g.
bazel build --config=linux-x86_64 @score_my_module//score/....
Add a platform integration test#
The S-CORE CLI ignores the exit codes of the started applications. To check the result automatically,
the example adds the platform integration test test_platform_version in
platform_integration_tests/platform_info.
It is a py_itf_test which starts the Linux image in Docker (or the QNX image in QEMU), runs the showcase via the CLI
and checks its output:
def test_platform_version(target):
output = run_showcases(target, "Platform Version Check")
assert "platform_info_consumer: S-CORE version check passed" in output
The test is added to the pit test suite in
platform_integration_tests/BUILD
and runs with:
bazel test --config=linux-x86_64 //platform_integration_tests:pit
How the test is linked to the requirement it verifies is described in the documentation generation chapter.
Note
The service and the consumer are also part of the showcase S-CORE Platform Run
(showcases/platform_run).
There, the launch manager starts the service as platform daemon and checks that the consumer exits successfully,
and the platform integration test platform_run collects and checks the DLT logs, see collecting logs.
Optional – Add Bazel patches#
If the module’s upstream BUILD files need adjustments (e.g. to expose
internal targets for integration testing), add patch files under
patches/<module_name>/ and reference them in the bazel_patches list
in known_good.json. Patching a module should be the exception, not the rule:
prefer fixing the issue in the module’s own repository.
Patches must be in unified diff format and are applied with patch -p1:
git diff HEAD > patches/my_module/001-fix-visibility.patch
Add an empty BUILD file in patches/my_module/, so that the patch files can be referenced by their
Bazel label, and reference them in known_good.json:
"score_my_module": {
"repo": "https://github.com/eclipse-score/my_module.git",
"hash": "0000000000000000000000000000000000000000",
"bazel_patches": [
"//patches/my_module:001-fix-visibility.patch"
],
"metadata": {
"code_root_path": "//score/...",
"langs": ["cpp"]
}
}
Testing the integration end-to-end with score_starter#
Once the module is integrated and the showcase configured, the full
end-to-end test is done via the interactive launcher score_starter
included in the repository root.
Start the launcher from the repository root:
./score_starter
A text menu appears listing all available image targets:
Use ↑ ↓ to navigate, Enter to run, q to quit Run QNX x86_64 QEMU Run QNX aarch64 QEMU > Run Linux x86_64 Docker Run Elektrobit corbos Linux for Safety Applications aarch64 QEMU Run AutoSD x86_64 Docker ExitSelect the target image with the arrow keys and press
Enter. For a quick local check, choose “Run Linux x86_64 Docker”. Bazel will build the full image (first run may take several minutes) and launch a Docker container with the S-CORE CLI.Note
The Linux image requires Docker to be running on the host. For QNX, additional toolchain licenses and QEMU networking setup are required – see the respective README files under
images/andrunners/.The S-CORE CLI starts automatically inside the container. It scans all
*.score.jsonfiles and presents a multi-select menu of all available examples. The new showcase Platform Version Check appears automatically.Select the example and confirm the selection. The CLI launches the selected example and streams its output to the terminal. For the platform version check, the expected output ends with:
platform_info_consumer: found the platform info service platform_info_consumer: platform runs S-CORE known_good 2026-08-28T09:19:12Z ... platform_info_consumer: S-CORE version check passed (8 modules match)
The same showcase can also be started without the menu:
/showcases/bin/cli --examples 'Platform Version Check'.