DR-010-Int: Standard umbrella targets for S-CORE modules#

  • Date: 2026-10-09

Standard umbrella targets for S-CORE modules
status: proposed
context: Integration
decision: Option 1
version: 1

Context / Problem#

An S-CORE module (e.g. score_baselibs, score_communication, score_logging) consists of many Bazel packages and targets; score_baselibs alone has on the order of 150 cc_library rules. There is no convention that tells a consumer which of those targets are shipping libraries, shipping executables, unit tests or integration tests. As a consequence:

  • reference_integration enumerates module targets by hand to build the stack and to run the module-scoped validation stage of Integration scope in refere... (dec_rec__int__scope_reference_integration).

  • Platform-specific targets (QNX-only, Linux-only, select()-guarded) are reconstructed by each consumer.

  • Without an explicit list of shipping targets, it is hard to check whether a coverage figure covers all shipping code.

  • A newcomer cannot tell from the build files what a module provides.

This decision record defines where and how a module declares its shipping C++ libraries, C++ binaries, C++ unit tests and C++ component integration tests, so that consumers reach each set through one well-defined entry point.

Motivation#

  • Unified entry to a module. One label per set, identical in every module, instead of enumerating sub-targets. This is the Bazel equivalent of using a package as a whole, as in Python import os or a CMake Foo::Foo target.

  • Variants handled once. Platform-specific targets are resolved in the module, not in every consumer.

  • Table of contents. Reading the umbrella shows what the module ships and what it deliberately does not.

  • Generic CI. bazel build //score:libs //score:bins and bazel test //score:unit_tests work the same for every module. Wildcards such as //... also pick up examples, tooling and mocks.

  • Separate test levels. Distinct unit and integration test suites allow separate CI stages and per-level results and coverage, the granularity at which verification evidence is collected.

  • Auditable coverage scope. The coverage setup in reference_integration (code_root_path, excluded tests) selects what is instrumented, but is not an authoritative list of shipping code. The umbrellas provide that reference set; they do not by themselves guarantee full coverage.

  • Stable across refactorings. Internal packages can move or split without breaking consumers of the umbrellas.

Goals and Requirements#

  • Single entry point: Each of the four sets is reachable in the same way in every module.

  • Explicit shipping surface: It is auditable which targets ship and which do not (mocks, fakes, examples, tooling).

  • Platform variants: Consumers do not re-implement platform selection.

  • Ownership: The sets are maintained by the module team, in sync with the module’s BUILD files.

  • Reusability: Usable by reference_integration, other consumers and the module’s own CI.

  • Low maintenance: Changing a target requires as few changes in as few repositories as possible.

  • Extensibility: A later decision on module-level shared libraries is not blocked.

Non-Goals#

  • Shared libraries (e.g. one cc_shared_library per module) are left to a separate decision record; the options are only evaluated on whether they enable it.

  • Rust targets need a separate decision, which may extend the labels defined here or introduce separately named targets. Coverage claims for a whole module must still account for shipping Rust code outside these umbrellas.

  • Packaging of third-party dependencies, Feature Integration Tests (they remain in reference_integration per Integration scope in refere... (dec_rec__int__scope_reference_integration)), and changes to build flags, toolchains or existing module targets.

Options Considered#

Option 2: Module teams maintain their umbrella targets in reference_integration#

The same four sets are defined in reference_integration, one package per module (e.g. modules/score_baselibs/BUILD), owned by the module team via CODEOWNERS and referencing external labels such as @score_baselibs//score/concurrency:concurrency.

Pros:

  • No change in the module repositories and no agreement of module teams needed, so it can start immediately

  • All definitions in one place, adaptable independently of module releases

Cons:

  • The list lives in another repository; drift is only detected when reference_integration updates the module, and adding a target needs pull requests in two repositories

  • All referenced component targets must be public

  • Not usable in the module’s own CI; other consumers would have to depend on reference_integration

  • A later shared library would be owned by reference_integration, not by the module

Option 3: Tag targets in the module and collect them with bazel query#

There is no aggregate target. Each target carries a classifying tag (score-lib, score-bin, score-unit-test, …), and consumers collect the sets, e.g.:

bazel query 'attr(tags, "\bscore-lib\b", @score_baselibs//score/...)'
bazel test --test_tag_filters=score-unit-test @score_baselibs//score/...

Pros:

  • No central list; a tagged target is picked up automatically

  • Uses existing Bazel features only

Cons:

  • A query result is not a label and cannot be used in deps; consumers need a generation step

  • Platform-specific sets require cquery per platform

  • Without a CI check, missing or misspelled tags silently drop targets (the same applies to forgotten entries in Option 1 lists; both need a check)

  • The shipping surface is only visible by running a query

  • Every target has to be tagged once (macros can set defaults)

  • Shared libraries are only possible via generated targets

Comparison#

Table 86 Umbrella target options comparison#

Criterion

Option 1 (in module)

Option 2 (in reference_integration)

Option 3 (tags + query)

Dependable Bazel label

Yes (in deps only as cc_library)

Only inside reference_integration

No, needs generation

Drift risk

Low (same PR, mandatory CI check)

High (cross-repository)

Low with a CI check, silent loss without

Platform variants

Resolved once in the module

Resolved once

cquery per platform

Usable in module CI / by other consumers

Yes / Yes

No / Via reference_integration

Yes / With own tooling

Component visibility

Unchanged (filegroup: linked components stay public)

Public

Public for generated targets

Effort

Low, one-time plus list upkeep

Upkeep in a second repository

Tag every target, plus tooling

Enables shared libraries

Yes, owned by the module

Yes, owned by reference_integration

Only via generated targets

Evaluation#

Option 1 puts the definition of what a module ships where that knowledge exists, and is the only option that meets all goals: one uniform entry point for reference_integration, other consumers and the module’s own CI, platform variants resolved once, and a libs form that fits each module’s shape. Its main cost, keeping the lists up to date, is limited because they change in the same pull request as the code and the mandatory drift check catches forgotten targets. Its main risk is adoption: it needs agreement and a change in every module repository.

Option 2 needs no module team agreement and can start immediately, but moves the list away from the code, which reintroduces drift, forces targets to become public and helps only reference_integration.

Option 3 can detect drift as well as Option 1 when combined with a CI check. The deciding difference is that a query result is not a label: it cannot be built, tested or linked as one target, and platform variants need cquery. Tags or queries remain useful to implement the Option 1 drift check.

Rollout: Modules adopt Option 1 one by one. Until a module provides its umbrella targets, reference_integration may keep an interim definition for it in the style of Option 2. That definition is removed once the module ships its own score/BUILD umbrellas, so there is never more than one authoritative list per module.

Recommendation: Option 1. Each S-CORE module provides the standard C++ targets //score:libs, //score:bins, //score:unit_tests and //score:integration_tests in score/BUILD. Shared libraries will be addressed in a separate decision record that builds on //score:libs.