Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/aarch64-ebclfsa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ jobs:
- name: Bazel Build (basic)
run: |
bazel build --lockfile_mode=error --config=aarch64-linux-ebclfsa -- //:language_and_standards_tests //:feature_verification_tests
- name: Guardrail (no legacy features)
run: |
bazel test --lockfile_mode=error --config=aarch64-linux-ebclfsa -- //:guardrail_tests
3 changes: 3 additions & 0 deletions .github/workflows/aarch64-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ jobs:
- name: Bazel Build (basic)
run: |
bazel build --config=aarch64-linux -- //:language_and_standards_tests //:feature_verification_tests
- name: Guardrail (no legacy features)
run: |
bazel test --config=aarch64-linux -- //:guardrail_tests
3 changes: 3 additions & 0 deletions .github/workflows/autosd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ jobs:
- name: Bazel Build (basic)
run: |
bazel build --lockfile_mode=error --config=x86_64-linux-autosd10 -- //:language_and_standards_tests //:feature_verification_tests
- name: Guardrail (no legacy features)
run: |
bazel test --lockfile_mode=error --config=x86_64-linux-autosd10 -- //:guardrail_tests
3 changes: 3 additions & 0 deletions .github/workflows/x86_64-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
- name: Bazel Build (basic)
run: |
bazel build --lockfile_mode=error --config=x86_64-linux -- //:language_and_standards_tests //:feature_verification_tests
- name: Guardrail (no legacy features)
run: |
bazel test --config=x86_64-linux -- //:guardrail_tests
Comment on lines +42 to +44
- name: Bazel Test
run: |
bazel test --config=x86_64-linux -- //:language_and_standards_tests //:feature_verification_tests
Expand Down
9 changes: 9 additions & 0 deletions docs/tests_and_validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ generated toolchains and platforms.
Defines the `feature_verification_tests` and `language_and_standards_tests`
test suites that aggregate the individual test targets.

`tests/guardrails/`

Holds the explicit-feature regression guardrail
(`no_legacy_features_guard_test`), aggregated by the `guardrail_tests` suite in
`tests/BUILD`. It fails as a test if the active toolchain regresses to relying
on Bazel's implicit legacy C++ features (i.e. loses the `no_legacy_features`
contract). See `tests/guardrails/README.md` for details and how to interpret
failures.

`tests/feature_verification/`

C++ targets that verify toolchain features such as preprocessor defines,
Expand Down
11 changes: 11 additions & 0 deletions tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,14 @@ test_suite(
"//feature_verification:whole_archive_test",
],
)

# ============================================================================
# Test Suite for Explicit-Feature Regression Guardrails
# ============================================================================

test_suite(
name = "guardrail_tests",
tests = [
"//guardrails:no_legacy_features_guard_test",
],
)
24 changes: 24 additions & 0 deletions tests/guardrails/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Regression guardrail for the explicit-feature (`no_legacy_features`) model."""

load(":legacy_feature_guard.bzl", "no_legacy_features_guard_test")

# Validates the C++ toolchain resolved for the current --config. Run it under
# each platform config (x86_64-linux, aarch64-linux, x86_64-qnx, ...) to cover
# the whole toolchain matrix.
no_legacy_features_guard_test(
name = "no_legacy_features_guard_test",
visibility = ["//visibility:public"],
)
81 changes: 81 additions & 0 deletions tests/guardrails/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Explicit-Feature Guardrail

This directory contains an automated guardrail that protects the
**explicit-feature model** established by the toolchain migration. Both the
Linux and QNX toolchains enable the `no_legacy_features` marker so that Bazel
injects **none** of its implicit legacy C/C++ features — every flag comes from a
feature this repository defines explicitly (see
[docs/features.md](../../docs/features.md) and
[docs/migration_guide.md](../../docs/migration_guide.md)).

The guardrail fails the test if that contract regresses.

## What it validates

`no_legacy_features_guard_test` (defined in
[`legacy_feature_guard.bzl`](legacy_feature_guard.bzl)) resolves the C++
toolchain configured for the current `--config` and checks that:

1. the `no_legacy_features` marker is still **enabled**, and
2. none of Bazel's implicit legacy features have become active again. It probes
a set of legacy feature names that this repository intentionally never
defines (`legacy_compile_flags`, `legacy_link_flags`, `sysroot`); if any is
enabled, Bazel has re-injected its legacy feature set.

The verdict is deterministic (derived from the resolved toolchain
configuration) and is surfaced as the **test's exit status**, so it needs no
target hardware and runs for whichever toolchain the active `--config` selects.

## How to run it

```bash
cd tests

# Any toolchain in the matrix — run it as a test. The test binary is a plain
# host-runnable script, so cross-target configs work without target hardware.
bazel test --config=x86_64-linux //:guardrail_tests
bazel test --config=aarch64-linux //:guardrail_tests
bazel test --config=x86_64-qnx //:guardrail_tests
```

CI runs the guardrail for every platform config (see the workflows under
`.github/workflows/`).
Comment on lines +41 to +42

## Interpreting a failure

A regression is reported as a normal **test failure**; the test log names the
exact problem, for example:

```
EXPLICIT-FEATURE GUARDRAIL FAILED
Problems detected:
- the `no_legacy_features` marker is NOT enabled in the resolved toolchain
configuration; Bazel will inject its implicit legacy C++ features.
```
or

```
Problems detected:
- Bazel legacy feature(s) are enabled again: sysroot.
```

To fix it:

- Re-add / re-enable the `no_legacy_features` feature (`enabled = True`) in the
toolchain config templates under `templates/linux/` and `templates/qnx/`, and
keep it in each template's `features` list.
- Provide any behavior you need through an **explicit** feature rather than
relying on a Bazel legacy default. See
[docs/migration_guide.md](../../docs/migration_guide.md) for the supported
extension mechanisms.

## Maintenance

- The list of probed legacy feature names lives in the
`_DEFAULT_FORBIDDEN_LEGACY_FEATURES` constant in
[`legacy_feature_guard.bzl`](legacy_feature_guard.bzl). Only add names that
Bazel injects as legacy features **and** that this repository does not define
itself, otherwise the probe would collide with a legitimate explicit feature.
- The guardrail is intentionally narrow: it checks the explicit-feature contract
only and does not replace the functional feature-verification tests under
`tests/feature_verification/`.
147 changes: 147 additions & 0 deletions tests/guardrails/legacy_feature_guard.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Guardrail protecting the explicit-feature (`no_legacy_features`) contract.

The Linux and QNX toolchains deliberately enable the `no_legacy_features`
marker so that Bazel injects none of its implicit legacy C++ features. This
rule resolves the *active* C++ toolchain and reports a **test failure** if that
contract regresses, either because:

* the `no_legacy_features` marker is no longer enabled, or
* one of Bazel's implicit legacy features (which this repository never defines)
has become active again.

The contract is evaluated deterministically from the resolved toolchain
configuration and surfaced as the test's exit status, so it needs no target
hardware and is exercised with `bazel test`.
"""

load("@rules_cc//cc:defs.bzl", "cc_common")
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cpp_toolchain", "use_cc_toolchain")

# The explicit-feature marker that must stay enabled on every toolchain.
_CONTRACT_MARKER = "no_legacy_features"

# Bazel auto-injects these "legacy" features only when a cc_toolchain does not
# declare `no_legacy_features`. This repository intentionally defines none of
# them, so if any becomes enabled the toolchain has regressed to relying on
# Bazel's legacy C++ feature behavior. Names here are chosen to NOT collide
# with the toolchain's own explicit feature names.
_DEFAULT_FORBIDDEN_LEGACY_FEATURES = [
"legacy_compile_flags",
"legacy_link_flags",
"sysroot",
]

def _impl(ctx):
cc_toolchain = find_cpp_toolchain(ctx)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features,
unsupported_features = ctx.disabled_features,
)

marker_enabled = cc_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = _CONTRACT_MARKER,
)

leaked = [
name
for name in ctx.attr.forbidden_legacy_features
if cc_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = name,
)
]

problems = []
if not marker_enabled:
problems.append(
"the `{}` marker is NOT enabled in the resolved toolchain configuration; ".format(_CONTRACT_MARKER) +
"Bazel will inject its implicit legacy C++ features.",
)
if leaked:
problems.append(
"Bazel legacy feature(s) are enabled again: {}.".format(", ".join(leaked)),
)

script = ctx.actions.declare_file(ctx.label.name + ".sh")

if problems:
message = [
"=======================================================================",
"EXPLICIT-FEATURE GUARDRAIL FAILED",
"=======================================================================",
"Target: {}".format(str(ctx.label)),
"",
"Problems detected:",
]
message.extend([" - " + p for p in problems])
message.extend([
"",
"Expected contract (see docs/migration_guide.md):",
" * The cc_toolchain keeps the `{}` feature (enabled = True) in its".format(_CONTRACT_MARKER),
" `features` list.",
" * None of Bazel's implicit legacy C++ features are active.",
"",
"How to fix:",
" * Re-add / re-enable `{}` in the toolchain config templates".format(_CONTRACT_MARKER),
" under templates/linux/ and templates/qnx/.",
" * Provide any needed behavior through an explicit feature instead of",
" relying on Bazel legacy defaults.",
" * See tests/guardrails/README.md for details.",
"=======================================================================",
])

# Surface the verdict as a test failure (exit 1) rather than a build
# failure. The heredoc delimiter is single-quoted so the message is
# emitted verbatim (no shell expansion of backticks/braces).
content = (
"#!/usr/bin/env bash\n" +
"cat >&2 <<'GUARDRAIL_EOF'\n" +
"\n".join(message) + "\n" +
"GUARDRAIL_EOF\n" +
"exit 1\n"
)
else:
content = (
"#!/usr/bin/env bash\n" +
"echo 'GUARDRAIL OK: {} enabled; no Bazel legacy features leaked ({}).'\n".format(
_CONTRACT_MARKER,
str(ctx.label),
) +
"exit 0\n"
)

ctx.actions.write(output = script, content = content, is_executable = True)
return DefaultInfo(executable = script)

no_legacy_features_guard_test = rule(
implementation = _impl,
doc = "Fails if the active C++ toolchain regresses to Bazel legacy feature behavior.",
attrs = {
"forbidden_legacy_features": attr.string_list(
default = _DEFAULT_FORBIDDEN_LEGACY_FEATURES,
doc = "Bazel legacy feature names that must never be enabled.",
),
"_cc_toolchain": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
},
toolchains = use_cc_toolchain(),
fragments = ["cpp"],
test = True,
)
Loading