|
| 1 | +# ******************************************************************************* |
| 2 | +# Copyright (c) 2026 Contributors to the Eclipse Foundation |
| 3 | +# |
| 4 | +# See the NOTICE file(s) distributed with this work for additional |
| 5 | +# information regarding copyright ownership. |
| 6 | +# |
| 7 | +# This program and the accompanying materials are made available under the |
| 8 | +# terms of the Apache License Version 2.0 which is available at |
| 9 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# SPDX-License-Identifier: Apache-2.0 |
| 12 | +# ******************************************************************************* |
| 13 | + |
| 14 | +"""Guardrail protecting the explicit-feature (`no_legacy_features`) contract. |
| 15 | +
|
| 16 | +The Linux and QNX toolchains deliberately enable the `no_legacy_features` |
| 17 | +marker so that Bazel injects none of its implicit legacy C++ features. This |
| 18 | +rule resolves the *active* C++ toolchain and reports a **test failure** if that |
| 19 | +contract regresses, either because: |
| 20 | +
|
| 21 | +* the `no_legacy_features` marker is no longer enabled, or |
| 22 | +* one of Bazel's implicit legacy features (which this repository never defines) |
| 23 | + has become active again. |
| 24 | +
|
| 25 | +The contract is evaluated deterministically from the resolved toolchain |
| 26 | +configuration and surfaced as the test's exit status, so it needs no target |
| 27 | +hardware and is exercised with `bazel test`. |
| 28 | +""" |
| 29 | + |
| 30 | +load("@rules_cc//cc:defs.bzl", "cc_common") |
| 31 | +load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cpp_toolchain", "use_cc_toolchain") |
| 32 | + |
| 33 | +# The explicit-feature marker that must stay enabled on every toolchain. |
| 34 | +_CONTRACT_MARKER = "no_legacy_features" |
| 35 | + |
| 36 | +# Bazel auto-injects these "legacy" features only when a cc_toolchain does not |
| 37 | +# declare `no_legacy_features`. This repository intentionally defines none of |
| 38 | +# them, so if any becomes enabled the toolchain has regressed to relying on |
| 39 | +# Bazel's legacy C++ feature behavior. Names here are chosen to NOT collide |
| 40 | +# with the toolchain's own explicit feature names. |
| 41 | +_DEFAULT_FORBIDDEN_LEGACY_FEATURES = [ |
| 42 | + "legacy_compile_flags", |
| 43 | + "legacy_link_flags", |
| 44 | + "sysroot", |
| 45 | +] |
| 46 | + |
| 47 | +def _impl(ctx): |
| 48 | + cc_toolchain = find_cpp_toolchain(ctx) |
| 49 | + feature_configuration = cc_common.configure_features( |
| 50 | + ctx = ctx, |
| 51 | + cc_toolchain = cc_toolchain, |
| 52 | + requested_features = ctx.features, |
| 53 | + unsupported_features = ctx.disabled_features, |
| 54 | + ) |
| 55 | + |
| 56 | + marker_enabled = cc_common.is_enabled( |
| 57 | + feature_configuration = feature_configuration, |
| 58 | + feature_name = _CONTRACT_MARKER, |
| 59 | + ) |
| 60 | + |
| 61 | + leaked = [ |
| 62 | + name |
| 63 | + for name in ctx.attr.forbidden_legacy_features |
| 64 | + if cc_common.is_enabled( |
| 65 | + feature_configuration = feature_configuration, |
| 66 | + feature_name = name, |
| 67 | + ) |
| 68 | + ] |
| 69 | + |
| 70 | + problems = [] |
| 71 | + if not marker_enabled: |
| 72 | + problems.append( |
| 73 | + "the `{}` marker is NOT enabled in the resolved toolchain configuration; ".format(_CONTRACT_MARKER) + |
| 74 | + "Bazel will inject its implicit legacy C++ features.", |
| 75 | + ) |
| 76 | + if leaked: |
| 77 | + problems.append( |
| 78 | + "Bazel legacy feature(s) are enabled again: {}.".format(", ".join(leaked)), |
| 79 | + ) |
| 80 | + |
| 81 | + script = ctx.actions.declare_file(ctx.label.name + ".sh") |
| 82 | + |
| 83 | + if problems: |
| 84 | + message = [ |
| 85 | + "=======================================================================", |
| 86 | + "EXPLICIT-FEATURE GUARDRAIL FAILED", |
| 87 | + "=======================================================================", |
| 88 | + "Target: {}".format(str(ctx.label)), |
| 89 | + "", |
| 90 | + "Problems detected:", |
| 91 | + ] |
| 92 | + message.extend([" - " + p for p in problems]) |
| 93 | + message.extend([ |
| 94 | + "", |
| 95 | + "Expected contract (see docs/migration_guide.md):", |
| 96 | + " * The cc_toolchain keeps the `{}` feature (enabled = True) in its".format(_CONTRACT_MARKER), |
| 97 | + " `features` list.", |
| 98 | + " * None of Bazel's implicit legacy C++ features are active.", |
| 99 | + "", |
| 100 | + "How to fix:", |
| 101 | + " * Re-add / re-enable `{}` in the toolchain config templates".format(_CONTRACT_MARKER), |
| 102 | + " under templates/linux/ and templates/qnx/.", |
| 103 | + " * Provide any needed behavior through an explicit feature instead of", |
| 104 | + " relying on Bazel legacy defaults.", |
| 105 | + " * See tests/guardrails/README.md for details.", |
| 106 | + "=======================================================================", |
| 107 | + ]) |
| 108 | + |
| 109 | + # Surface the verdict as a test failure (exit 1) rather than a build |
| 110 | + # failure. The heredoc delimiter is single-quoted so the message is |
| 111 | + # emitted verbatim (no shell expansion of backticks/braces). |
| 112 | + content = ( |
| 113 | + "#!/usr/bin/env bash\n" + |
| 114 | + "cat >&2 <<'GUARDRAIL_EOF'\n" + |
| 115 | + "\n".join(message) + "\n" + |
| 116 | + "GUARDRAIL_EOF\n" + |
| 117 | + "exit 1\n" |
| 118 | + ) |
| 119 | + else: |
| 120 | + content = ( |
| 121 | + "#!/usr/bin/env bash\n" + |
| 122 | + "echo 'GUARDRAIL OK: {} enabled; no Bazel legacy features leaked ({}).'\n".format( |
| 123 | + _CONTRACT_MARKER, |
| 124 | + str(ctx.label), |
| 125 | + ) + |
| 126 | + "exit 0\n" |
| 127 | + ) |
| 128 | + |
| 129 | + ctx.actions.write(output = script, content = content, is_executable = True) |
| 130 | + return DefaultInfo(executable = script) |
| 131 | + |
| 132 | +no_legacy_features_guard_test = rule( |
| 133 | + implementation = _impl, |
| 134 | + doc = "Fails if the active C++ toolchain regresses to Bazel legacy feature behavior.", |
| 135 | + attrs = { |
| 136 | + "forbidden_legacy_features": attr.string_list( |
| 137 | + default = _DEFAULT_FORBIDDEN_LEGACY_FEATURES, |
| 138 | + doc = "Bazel legacy feature names that must never be enabled.", |
| 139 | + ), |
| 140 | + "_cc_toolchain": attr.label( |
| 141 | + default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| 142 | + ), |
| 143 | + }, |
| 144 | + toolchains = use_cc_toolchain(), |
| 145 | + fragments = ["cpp"], |
| 146 | + test = True, |
| 147 | +) |
0 commit comments