From f7cb2ab4b34ef77fb8beaa6e21d521dc64f5b70a Mon Sep 17 00:00:00 2001 From: Nikola Radakovic Date: Wed, 15 Apr 2026 14:58:25 +0100 Subject: [PATCH] Add feature injection Add feature injection to toolchain gcc extension. This option will allow users to add external defined features. --- docs/extension_api.md | 22 ++ docs/features.md | 15 +- docs/migration_guide.md | 18 +- extensions/gcc.bzl | 24 ++ rules/common.bzl | 11 + rules/gcc.bzl | 12 +- .../linux/cc_toolchain_config.bzl.template | 208 ++---------------- .../qnx/cc_toolchain_config.bzl.template | 22 ++ tests/MODULE.bazel.lock | 18 +- 9 files changed, 153 insertions(+), 197 deletions(-) diff --git a/docs/extension_api.md b/docs/extension_api.md index 627c8c7..c69f923 100644 --- a/docs/extension_api.md +++ b/docs/extension_api.md @@ -72,6 +72,8 @@ Flag and runtime attributes: - `extra_c_compile_flags` - `extra_cxx_compile_flags` - `extra_link_flags` +- `extra_known_features` +- `extra_enabled_features` - `ld_library_paths` - `runtime_ecosystem` - `use_base_constraints_only` @@ -93,6 +95,21 @@ attributes are: - `sha256`: sha256 of the archive - `strip_prefix`: extraction prefix for packaged archives +## Feature Injection + +`extra_known_features` and `extra_enabled_features` let a workspace add +rule-based `cc_feature` targets (defined outside this repository) to the +generated toolchain: + +- `extra_known_features` — registers external features so they can be toggled + per target via the `features` attribute or build-wide via `--features`. +- `extra_enabled_features` — registers *and* enables external features by + default. + +Sanitizers are the primary use case: they are defined by the +`score_cpp_policies` module and brought into the toolchain through these +attributes. See [Toolchain features](features.md#sanitizers-linux-opt-in). + ## Activation In A Workspace @@ -120,5 +137,10 @@ behavior explicitly, see the - The extension is intended for the root module. - When `use_default_package` is enabled, the version matrix can inject extra include and link flags required by non-standard sysroot layouts. +- Sanitizer features are not registered automatically. They are defined by the + `score_cpp_policies` module and made available through *feature injection* — + the `extra_known_features` / `extra_enabled_features` attributes on + `gcc.toolchain(...)`. See [Toolchain features](features.md#sanitizers-linux-opt-in) + for the injected feature names. - QNX toolchains use additional licensing and include-path parameters that do not apply to Linux toolchains. \ No newline at end of file diff --git a/docs/features.md b/docs/features.md index b98fe56..e7822c2 100644 --- a/docs/features.md +++ b/docs/features.md @@ -96,9 +96,18 @@ Opt-in / disabled by default unless noted otherwise. - **`warnings_as_errors`** (both) — Adds `-Werror`. ## Sanitizers (Linux, opt-in) -- **`sanitizer`** — Base sanitizer feature. -- **`asan`** / **`lsan`** / **`tsan`** / **`ubsan`** — Address / Leak / Thread / - Undefined-Behavior sanitizers; each implies `sanitizer`. +Sanitizers are **not** defined by this toolchain. They are provided as +rule-based `cc_feature` definitions by the `score_cpp_policies` module and made +available through *feature injection* — the `extra_known_features` / +`extra_enabled_features` attributes on `gcc.toolchain(...)`. Once injected they +behave like any other opt-in feature. + +- **`score_asan`** — AddressSanitizer (`-fsanitize=address`). +- **`score_lsan`** — LeakSanitizer (`-fsanitize=leak`). +- **`score_tsan`** — ThreadSanitizer (`-fsanitize=thread`). +- **`score_ubsan`** — UndefinedBehaviorSanitizer (`-fsanitize=undefined`). + +See the `score_cpp_policies` documentation for the authoritative list and usage. ## Threading - **`use_pthread`** (Linux) — Links with `-pthread`. diff --git a/docs/migration_guide.md b/docs/migration_guide.md index a49f5ff..c25cd8c 100644 --- a/docs/migration_guide.md +++ b/docs/migration_guide.md @@ -97,13 +97,13 @@ toolchain, are summarized below. | `gcov` | **Supported, wiring** | `tool_paths` (`gcov_wrapper`) | | Warnings (e.g. `-Wall`) added by default | **Linux: opt-in · QNX: on by default** | `minimal_warnings` (includes `-Wall`) is enabled by default on QNX but disabled on Linux. `strict_warnings` / `all_wall_warnings` are opt-in on both. | | `-Werror` | **Not implicit — opt-in** | `warnings_as_errors` (disabled by default) | -| Sanitizers (asan/lsan/tsan/ubsan) | **Not implicit — opt-in (Linux)** | `asan` / `lsan` / `tsan` / `ubsan` (disabled by default) | +| Sanitizers (asan/lsan/tsan/ubsan) | **Not part of this toolchain — injected (Linux)** | Defined by `score_cpp_policies` (`score_asan` / `score_lsan` / `score_tsan` / `score_ubsan`) and brought in via `extra_known_features` / `extra_enabled_features` | | Fully static link (`-static`) | **Not implicit — opt-in** | `fully_static_link` (disabled by default) | | `-pthread` | **Not implicit — opt-in (Linux)** | `use_pthread` (disabled by default) | | Fission / split DWARF, linkstamps, strip, static-libgcc | **Supported, guarded** | Guarded features; no-ops until the relevant build mode is active | | Any other Bazel legacy default not listed above | **Not available** | Must be added explicitly (see below) | -Two categories deserve special attention because they are the most frequent +Three categories deserve special attention because they are the most frequent migration surprises: 1. **Warnings differ by platform.** On **Linux**, all warning features are @@ -111,8 +111,12 @@ migration surprises: **QNX**, `minimal_warnings` (which includes `-Wall`) is enabled by default, so those warnings do *not* disappear; `strict_warnings`, `all_wall_warnings`, and `-Werror` remain opt-in on both platforms. -2. **`-pthread`, sanitizers, and fully-static linking are opt-in.** These emit - nothing until you enable the corresponding feature. +2. **`-pthread` and fully-static linking are opt-in.** These emit nothing until + you enable the corresponding feature. +3. **Sanitizers are not part of this toolchain.** They are defined by the + `score_cpp_policies` module and must first be injected via + `extra_known_features` / `extra_enabled_features` before they can be enabled. + See [Toolchain features](features.md#sanitizers-linux-opt-in). For the authoritative and complete list, always refer to [Toolchain features](features.md). @@ -155,10 +159,12 @@ build:myconfig --host_features=use_pthread ### 3. Turn on an opt-in feature that already exists Many legacy-equivalent behaviors already ship as opt-in features (warnings, -sanitizers, `fully_static_link`, `use_pthread`, `per_object_debug_info`, ...). +`fully_static_link`, `use_pthread`, `per_object_debug_info`, ...). You do not need to modify the toolchain to use them — enable them with mechanism 1 or 2 above. See the *Opt-in* entries in -[Toolchain features](features.md). +[Toolchain features](features.md). (Sanitizers are the exception: they are +defined by `score_cpp_policies` and must first be injected via +`extra_known_features` / `extra_enabled_features`.) ### 4. Inject raw flags through the toolchain attributes diff --git a/extensions/gcc.bzl b/extensions/gcc.bzl index cf5f455..ae8b330 100644 --- a/extensions/gcc.bzl +++ b/extensions/gcc.bzl @@ -78,6 +78,26 @@ _attrs_tc = { default = [], doc = "List of additional flags to be passed to linker.", ), + "extra_enabled_features": attr.label_list( + mandatory = False, + default = [], + doc = ("Extra `cc_feature` features to add to this toolchain in an initially " + + "enabled state. This attribute has limited integration with `cc_feature`, " + + "and does not run additional correctness checks or handle things like `data` " + + "files. This is only offered as a migration bridge for projects transitioning " + + "to rule-based toolchain configurations, or sharing of simple argument sets " + + "with older toolchains."), + ), + "extra_known_features": attr.label_list( + mandatory = False, + default = [], + doc = ("Extra `cc_feature` features to add to this toolchain in an initially " + + "disabled state. This attribute has limited integration with `cc_feature`, " + + "and does not run additional correctness checks or handle things like `data` " + + "files. This is only offered as a migration bridge for projects transitioning " + + "to rule-based toolchain configurations, or sharing of simple argument sets " + + "with older toolchains."), + ), "license_info_url": attr.string( default = "", mandatory = False, @@ -197,6 +217,8 @@ def _get_toolchains(tags): "tc_extra_compile_flags": tag.extra_compile_flags, "tc_extra_cxx_compile_flags": tag.extra_cxx_compile_flags, "tc_extra_link_flags": tag.extra_link_flags, + "tc_extra_known_features": tag.extra_known_features, + "tc_extra_enabled_features": tag.extra_enabled_features, "tc_license_info_url": tag.license_info_url, "tc_license_info_variable": tag.license_info_variable, "tc_license_path": tag.license_path, @@ -377,6 +399,8 @@ def _impl(mctx): extra_compile_flags = toolchain_info["tc_extra_compile_flags"], extra_c_compile_flags = toolchain_info["tc_extra_c_compile_flags"], extra_cxx_compile_flags = toolchain_info["tc_extra_cxx_compile_flags"], + extra_known_features = toolchain_info["tc_extra_known_features"], + extra_enabled_features = toolchain_info["tc_extra_enabled_features"], extra_link_flags = toolchain_info["tc_extra_link_flags"], license_info_variable = toolchain_info["tc_license_info_variable"], license_info_value = toolchain_info["tc_license_info_url"], diff --git a/rules/common.bzl b/rules/common.bzl index 8fc7abf..ad756d6 100644 --- a/rules/common.bzl +++ b/rules/common.bzl @@ -51,3 +51,14 @@ def get_flag_group(flags): ), ] return [] + +def label_list_to_string(input_list): + """ Small helper function to transform label list into string list + + Args: + input_list (list[labels]): A list of Bazel labels. + + Returns: + str: Formatted string + """ + return "[{}]".format(", ".join(["\"{}\"".format(item) for item in input_list])) diff --git a/rules/gcc.bzl b/rules/gcc.bzl index 205deaa..1a9504b 100644 --- a/rules/gcc.bzl +++ b/rules/gcc.bzl @@ -14,7 +14,7 @@ """ Module rule for defining GCC toolchains in Bazel. """ -load("@score_bazel_cpp_toolchains//rules:common.bzl", "SDP_VERSION_MAPPING", "get_flag_groups") +load("@score_bazel_cpp_toolchains//rules:common.bzl", "SDP_VERSION_MAPPING", "get_flag_groups", "label_list_to_string") # Constants _OS_QNX = "qnx" @@ -71,12 +71,16 @@ cc_toolchain_config( sysroot = "@{tc_pkg_repo}//:sysroot_dir", target_cpu = "{tc_cpu}", target_os = "{tc_os}", + extra_known_features = {tc_extra_known_features}, + extra_enabled_features = {tc_extra_enabled_features}, visibility = ["//visibility:public"], ) """.format( tc_pkg_repo = rctx.attr.tc_pkg_repo, tc_cpu = rctx.attr.tc_cpu, tc_os = rctx.attr.tc_os, + tc_extra_known_features = label_list_to_string(rctx.attr.extra_known_features), + tc_extra_enabled_features = label_list_to_string(rctx.attr.extra_enabled_features), ) def _get_cc_config_qnx(rctx): @@ -109,12 +113,16 @@ cc_toolchain_config( cxx_builtin_include_directories = "@{tc_pkg_repo}//:cxx_builtin_include_directories", target_cpu = "{tc_cpu}", target_os = "{tc_os}", + extra_known_features = {tc_extra_known_features}, + extra_enabled_features = {tc_extra_enabled_features}, visibility = ["//visibility:public"], ) """.format( tc_pkg_repo = rctx.attr.tc_pkg_repo, tc_cpu = rctx.attr.tc_cpu, tc_os = rctx.attr.tc_os, + tc_extra_known_features = label_list_to_string(rctx.attr.extra_known_features), + tc_extra_enabled_features = label_list_to_string(rctx.attr.extra_enabled_features), ) def _normalize_cpu(cpu): @@ -318,6 +326,8 @@ gcc_toolchain = repository_rule( "extra_c_compile_flags": attr.string_list(doc = "Extra/Additional C-specific compile flags."), "extra_compile_flags": attr.string_list(doc = "Extra/Additional compile flags."), "extra_cxx_compile_flags": attr.string_list(doc = "Extra/Additional C++-specific compile flags."), + "extra_known_features": attr.label_list(doc = "Extra/Additional C++ FeatureInfo provider list"), + "extra_enabled_features": attr.label_list(doc = "Extra/Additional C++ FeatureInfo provider list enabled by default"), "extra_link_flags": attr.string_list(doc = "Extra/Additional link flags."), "gcc_version": attr.string(doc = "GCC version string"), "use_base_constraints_only": attr.bool(doc = "Boolean flag to state only base constraints should be used for toolchain compatibility definition"), diff --git a/templates/linux/cc_toolchain_config.bzl.template b/templates/linux/cc_toolchain_config.bzl.template index 2ac5196..f85312f 100644 --- a/templates/linux/cc_toolchain_config.bzl.template +++ b/templates/linux/cc_toolchain_config.bzl.template @@ -28,6 +28,7 @@ load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "with_feature_set", ) load("@rules_cc//cc:defs.bzl", "cc_common", "CcToolchainConfigInfo") +load("@rules_cc//cc/toolchains:feature_injection.bzl", "convert_feature", "FeatureInfo") load(":flags.bzl", "UNFILTERED_COMPILE_FLAGS", "DEFAULT_COMPILE_FLAGS", @@ -291,187 +292,6 @@ def _impl(ctx): ], ) - sanitizer_feature = feature( - name = "sanitizer", - flag_sets = [ - flag_set( - actions = all_compile_actions, - flag_groups = [ - flag_group( - flags = [ - "-fno-omit-frame-pointer", - "-fno-sanitize-recover=all", # TODO: Check if this is needed. - "-g", - ], - ), - ], - with_features = [ - with_feature_set( - features = [ - "asan", - "lsan", - "tsan", - "ubsan", - ], - not_features = ["opt"], - ) - ], - ), - ], - ) - - asan_feature = feature( - name = "asan", - implies = ["sanitizer"], - flag_sets = [ - flag_set( - actions = all_compile_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=address", - "-DADDRESS_SANITIZER", - "-O0", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - flag_set( - actions = all_link_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=address", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - ], - ) - - lsan_feature = feature( - name = "lsan", - implies = ["sanitizer"], - flag_sets = [ - flag_set( - actions = all_compile_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=leak", - "-O0", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - flag_set( - actions = all_link_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=leak", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - ], - ) - - tsan_feature = feature( - name = "tsan", - implies = ["sanitizer"], - flag_sets = [ - flag_set( - actions = all_compile_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=thread", - "-O1", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - flag_set( - actions = all_link_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=thread", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - ], - ) - - ubsan_feature = feature( - name = "ubsan", - implies = ["sanitizer"], - flag_sets = [ - flag_set( - actions = all_compile_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=undefined", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - flag_set( - actions = all_link_actions, - flag_groups = [ - flag_group( - flags = [ - "-fsanitize=undefined", - ], - ), - ], - with_features = [ - with_feature_set( - not_features = ["opt"], - ) - ], - ), - ], - ) - minimal_warnings_feature = feature( name = "minimal_warnings", enabled = False, @@ -1194,11 +1014,6 @@ def _impl(ctx): features = [ no_legacy_features_feature, compiler_library_search_paths_feature, - sanitizer_feature, - asan_feature, - lsan_feature, - tsan_feature, - ubsan_feature, dbg_feature, unfiltered_compile_flags_feature, gnu11_feature, @@ -1247,6 +1062,9 @@ def _impl(ctx): gcc_coverage_map_format_feature, ] + extra_rules_based_features = depset(ctx.attr.extra_enabled_features + ctx.attr.extra_known_features) + features.extend([convert_feature(extra_feature[FeatureInfo], enabled = extra_feature in ctx.attr.extra_enabled_features) for extra_feature in extra_rules_based_features.to_list()]) + # Get builtin include directories from attribute if provided cxx_builtin_include_directories = [] if hasattr(ctx.attr, "builtin_include_directories") and ctx.attr.builtin_include_directories: @@ -1291,5 +1109,23 @@ cc_toolchain_config = rule( "host_dir": attr.label(default = None), "target_dir": attr.label(default = None), "cxx_builtin_include_directories": attr.label(allow_files = True, default = None), + "extra_enabled_features": attr.label_list( + providers = [FeatureInfo], + default = [], + doc = """ +Extra `cc_feature` features to add to this toolchain in an initially enabled state. +This attribute has limited integration with `cc_feature`, and does not run additional correctness checks or handle things like `data` files. +This is only offered as a migration bridge for projects transitioning to rule-based toolchain configurations, or sharing of simple argument sets with older toolchains. +""", + ), + "extra_known_features": attr.label_list( + providers = [FeatureInfo], + default = [], + doc = """ +Extra `cc_feature` features to add to this toolchain in an initially disabled state. +This attribute has limited integration with `cc_feature`, and does not run additional correctness checks or handle things like `data` files. +This is only offered as a migration bridge for projects transitioning to rule-based toolchain configurations, or sharing of simple argument sets with older toolchains. +""", + ), }, ) \ No newline at end of file diff --git a/templates/qnx/cc_toolchain_config.bzl.template b/templates/qnx/cc_toolchain_config.bzl.template index 21e6218..39bb81a 100644 --- a/templates/qnx/cc_toolchain_config.bzl.template +++ b/templates/qnx/cc_toolchain_config.bzl.template @@ -28,6 +28,7 @@ load("@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "with_feature_set", ) load("@rules_cc//cc:defs.bzl", "cc_common", "CcToolchainConfigInfo") +load("@rules_cc//cc/toolchains:feature_injection.bzl", "convert_feature", "FeatureInfo") load(":flags.bzl", "UNFILTERED_COMPILE_FLAGS", "DEFAULT_COMPILE_FLAGS", @@ -835,6 +836,9 @@ def _impl(ctx): gcc_coverage_map_format_feature, ] + extra_rules_based_features = depset(ctx.attr.extra_enabled_features + ctx.attr.extra_known_features) + features.extend([convert_feature(extra_feature[FeatureInfo], enabled = extra_feature in ctx.attr.extra_enabled_features) for extra_feature in extra_rules_based_features.to_list()]) + cxx_builtin_include_directories = [ "/proc/self/cwd/{}".format(include_directory.path) for include_directory in ctx.files.cxx_builtin_include_directories @@ -873,5 +877,23 @@ cc_toolchain_config = rule( "target_cpu": attr.string(mandatory = True), "target_os": attr.string(mandatory = True), "cxx_builtin_include_directories": attr.label(allow_files = True, mandatory = True), + "extra_enabled_features": attr.label_list( + providers = [FeatureInfo], + default = [], + doc = """ +Extra `cc_feature` features to add to this toolchain in an initially enabled state. +This attribute has limited integration with `cc_feature`, and does not run additional correctness checks or handle things like `data` files. +This is only offered as a migration bridge for projects transitioning to rule-based toolchain configurations, or sharing of simple argument sets with older toolchains. +""", + ), + "extra_known_features": attr.label_list( + providers = [FeatureInfo], + default = [], + doc = """ +Extra `cc_feature` features to add to this toolchain in an initially disabled state. +This attribute has limited integration with `cc_feature`, and does not run additional correctness checks or handle things like `data` files. +This is only offered as a migration bridge for projects transitioning to rule-based toolchain configurations, or sharing of simple argument sets with older toolchains. +""", + ), }, ) diff --git a/tests/MODULE.bazel.lock b/tests/MODULE.bazel.lock index e1819e2..20575b0 100644 --- a/tests/MODULE.bazel.lock +++ b/tests/MODULE.bazel.lock @@ -1368,7 +1368,7 @@ }, "@@score_bazel_cpp_toolchains+//extensions:gcc.bzl%gcc": { "general": { - "bzlTransitiveDigest": "Of8FuJrUjVWLtlJvJ7FWpS+D0OMsYxR4WbF89GzvPD8=", + "bzlTransitiveDigest": "yw9Re6dZy5Gs5l3byDyzu4qgBXBSZQbNHWm89ygsTPA=", "usagesDigest": "1UfIz7yPBC2LyOHghQlB2yltcJXkkGH7NQITjVhleH8=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1468,6 +1468,8 @@ "extra_compile_flags": [], "extra_c_compile_flags": [], "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [], "license_info_variable": "", "license_info_value": "", @@ -1493,6 +1495,8 @@ "extra_compile_flags": [], "extra_c_compile_flags": [], "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [], "license_info_variable": "", "license_info_value": "", @@ -1518,6 +1522,8 @@ "extra_compile_flags": [], "extra_c_compile_flags": [], "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [], "license_info_variable": "", "license_info_value": "", @@ -1543,6 +1549,8 @@ "extra_compile_flags": [], "extra_c_compile_flags": [], "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [ "-lpthread" ], @@ -1570,6 +1578,8 @@ "extra_compile_flags": [], "extra_c_compile_flags": [], "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [], "license_info_variable": "", "license_info_value": "", @@ -1595,6 +1605,8 @@ "extra_compile_flags": [], "extra_c_compile_flags": [], "extra_cxx_compile_flags": [], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [], "license_info_variable": "", "license_info_value": "", @@ -1639,6 +1651,8 @@ "-isystem", "external/%{toolchain_pkg}%/usr/include" ], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [ "-B", "external/%{toolchain_pkg}%/usr/bin", @@ -1714,6 +1728,8 @@ "external/%{toolchain_pkg}%/usr/lib/x86_64-linux-gnu", "--no-canonical-prefixes" ], + "extra_known_features": [], + "extra_enabled_features": [], "extra_link_flags": [ "-B", "external/%{toolchain_pkg}%/usr/aarch64-linux-gnu/bin",