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_sw modules listed in known_good.json.

  • Separation of concerns. known_good.json is the what (which commit of which module), while the generated .MODULE.bazel file 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 Bazel MODULE.bazel files.

    • 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 from known_good.json by 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

images/<variant>

Boot, kernel, drivers, root filesystem skeleton

2 – Platform

deploy/

The deployed S-CORE platform, e.g. launch_manager, datarouter and the platform_info_service

3 – Showcases

showcases/

Applications that demonstrate and verify the platform, e.g. the platform_info_consumer

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:

  1. Add the new module entry to known_good.json.

  2. Run the generation script to update the .MODULE.bazel file.

  3. Deploy the platform services of the module and add showcases, so the module appears on the images and in the S-CORE CLI.

  4. 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

repo

yes

HTTPS URL to the Git repository (ending in .git).

hash

yes, or version

Full SHA-1 commit hash of the version to integrate (generates a git_override).

version

yes, or hash

Released version from the Bazel registry (generates a single_version_override).

bazel_patches

no

List of Bazel label paths to .patch files that are applied to the module source before building (patch_strip = 1).

pin_version

no

If true, the automatic update scripts leave the hash of this module untouched.

metadata.code_root_path

no

Bazel target pattern for the module’s source root. Defaults to //score/....

metadata.langs

no

Languages present in the module: "cpp", "rust", or both. Defaults to ["cpp", "rust"].

metadata.extra_test_config

no

Additional flag values passed to test runs.

metadata.exclude_test_targets

no

Bazel target patterns excluded from CI test runs (e.g. known-failing or platform-specific tests).

metadata.rust_coverage_config

no

Rust coverage profile name. Defaults to "ferrocene-coverage".

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.

  1. 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
        Exit
    
  2. Select 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/ and runners/.

  3. The S-CORE CLI starts automatically inside the container. It scans all *.score.json files and presents a multi-select menu of all available examples. The new showcase Platform Version Check appears automatically.

  4. 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'.