From 640cf52eb81dfd42d8949eb954b955f08b573f9b Mon Sep 17 00:00:00 2001 From: David Zbarsky Date: Sun, 8 Feb 2026 08:22:30 -0500 Subject: [PATCH 1/2] Teach cc_args to interop with BuildSettingInfo --- cc/toolchains/args.bzl | 17 ++++++++++---- cc/toolchains/impl/BUILD | 5 +++- cc/toolchains/impl/nested_args.bzl | 15 +++++++++++- docs/toolchain_api.md | 2 +- tests/rule_based_toolchain/args/BUILD | 15 ++++++++++++ tests/rule_based_toolchain/args/args_test.bzl | 23 +++++++++++++++++++ 6 files changed, 69 insertions(+), 8 deletions(-) diff --git a/cc/toolchains/args.bzl b/cc/toolchains/args.bzl index 95cdf5217..70c111447 100644 --- a/cc/toolchains/args.bzl +++ b/cc/toolchains/args.bzl @@ -44,11 +44,17 @@ visibility("public") def _cc_args_impl(ctx): actions = collect_action_types(ctx.attr.actions) + format_targets = {k: v for v, k in ctx.attr.format.items()} formatted_env, used_format_vars = format_dict_values( env = ctx.attr.env, must_use = [], # checking for unused variables in done when formatting `args`. - format = {k: v for v, k in ctx.attr.format.items()}, + format = format_targets, ) + used_env_variables = [ + var + for var in used_format_vars + if var in format_targets and VariableInfo in format_targets[var] + ] for path in ctx.attr.allowlist_absolute_include_directories: if not is_path_absolute(path): @@ -80,7 +86,7 @@ def _cc_args_impl(ctx): actions = actions.to_list(), env = env, variables = ctx.attr._variables[BuiltinVariablesInfo].variables, - used_format_vars = used_format_vars, + used_format_vars = used_env_variables, ) args = ArgsInfo( @@ -280,9 +286,10 @@ def cc_args( arguments to work as intended. env: (Dict[str, str]) Environment variables that should be set when the tool is invoked. format: (Dict[str, Label]) A mapping of format strings to the label of a corresponding - target. This target can be a `directory`, `subdirectory`, `cc_variable`, or a single - file that the value should be pulled from. All instances of `{variable_name}` in the - `args` list will be replaced with the expanded value in this dictionary. + target. This target can be a `directory`, `subdirectory`, `cc_variable`, a build setting + (a target that provides `BuildSettingInfo`), or a single file that the value should be + pulled from. All instances of `{variable_name}` in the `args` list will be replaced with + the expanded value in this dictionary. The complete list of possible variables can be found in https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/variables/BUILD. It is not possible to declare custom variables--these are inherent to Bazel itself. diff --git a/cc/toolchains/impl/BUILD b/cc/toolchains/impl/BUILD index 7ee93fdbe..c5f95a6e4 100644 --- a/cc/toolchains/impl/BUILD +++ b/cc/toolchains/impl/BUILD @@ -16,7 +16,10 @@ bzl_library( name = "toolchain_impl_rules", srcs = glob(["*.bzl"]), visibility = ["//cc/toolchains:__subpackages__"], - deps = ["//cc/common"], + deps = [ + "//cc/common", + "@bazel_skylib//rules:common_settings", + ], ) config_setting( diff --git a/cc/toolchains/impl/nested_args.bzl b/cc/toolchains/impl/nested_args.bzl index e59711d5a..ed16fbd1d 100644 --- a/cc/toolchains/impl/nested_args.bzl +++ b/cc/toolchains/impl/nested_args.bzl @@ -13,6 +13,7 @@ # limitations under the License. """Helper functions for working with args.""" +load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo") load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value") load("//cc/toolchains:cc_toolchain_info.bzl", "NestedArgsInfo", "VariableInfo") @@ -291,9 +292,21 @@ def nested_args_provider( def _escape(s): return s.replace("%", "%%") +_SUPPORTED_BUILD_SETTING_TYPES = ["string", "bool", "int", "Label"] + +def _format_build_setting(target, fail = fail): + value = target[BuildSettingInfo].value + + if type(value) in _SUPPORTED_BUILD_SETTING_TYPES: + return _escape(str(value)) + + fail("%s had an unsupported build setting type %s. Only string, bool, int, or Label values may be formatted." % (target.label, type(value))) + def _format_target(target, fail = fail): if VariableInfo in target: return "%%{%s}" % target[VariableInfo].name + elif BuildSettingInfo in target: + return _format_build_setting(target, fail = fail) elif DirectoryInfo in target: return _escape(target[DirectoryInfo].path) @@ -301,7 +314,7 @@ def _format_target(target, fail = fail): if len(files) == 1: return _escape(files[0].path) - fail("%s should be either a variable, a directory, or a single file." % target.label) + fail("%s should be either a variable, a build setting, a directory, or a single file." % target.label) def _format_string(arg, format, used_vars, fail = fail): upto = 0 diff --git a/docs/toolchain_api.md b/docs/toolchain_api.md index eee9241f1..3dfe886f6 100755 --- a/docs/toolchain_api.md +++ b/docs/toolchain_api.md @@ -663,7 +663,7 @@ For more extensive examples, see the usages here: | args | (List[str]) The command-line arguments that are applied by using this rule. This is mutually exclusive with [nested](#cc_args-nested). | `None` | | data | (List[Label]) A list of runtime data dependencies that are required for these arguments to work as intended. | `None` | | env | (Dict[str, str]) Environment variables that should be set when the tool is invoked. | `None` | -| format | (Dict[str, Label]) A mapping of format strings to the label of a corresponding target. This target can be a `directory`, `subdirectory`, [`cc_variable`](#cc_variable), or a single file that the value should be pulled from. All instances of `{variable_name}` in the `args` list will be replaced with the expanded value in this dictionary. The complete list of possible variables can be found in https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/variables/BUILD. It is not possible to declare custom variables--these are inherent to Bazel itself. | `{}` | +| format | (Dict[str, Label]) A mapping of format strings to the label of a corresponding target. This target can be a `directory`, `subdirectory`, [`cc_variable`](#cc_variable), a build setting (a target that provides `BuildSettingInfo`), or a single file that the value should be pulled from. All instances of `{variable_name}` in the `args` list will be replaced with the expanded value in this dictionary. The complete list of possible variables can be found in https://github.com/bazelbuild/rules_cc/tree/main/cc/toolchains/variables/BUILD. It is not possible to declare custom variables--these are inherent to Bazel itself. | `{}` | | iterate_over | (Label) The label of a [`cc_variable`](#cc_variable) that should be iterated over. This is intended for use with built-in variables that are lists. | `None` | | nested | (List[Label]) A list of [`cc_nested_args`](#cc_nested_args) rules that should be expanded to command-line arguments when this rule is used. This is mutually exclusive with [args](#cc_args-args). | `None` | | requires_not_none | (Label) The label of a [`cc_variable`](#cc_variable) that should be checked for existence before expanding this rule. If the variable is None, this rule will be ignored. | `None` | diff --git a/tests/rule_based_toolchain/args/BUILD b/tests/rule_based_toolchain/args/BUILD index 7f47073c4..9779d9777 100644 --- a/tests/rule_based_toolchain/args/BUILD +++ b/tests/rule_based_toolchain/args/BUILD @@ -1,3 +1,4 @@ +load("@bazel_skylib//rules:common_settings.bzl", "string_flag") load("@rules_testing//lib:util.bzl", "util") load("//cc/toolchains:args.bzl", "cc_args") load("//cc/toolchains/impl:variables.bzl", "cc_variable", "types") @@ -10,6 +11,11 @@ cc_variable( type = types.string, ) +string_flag( + name = "macos_min_os_flag", + build_setting_default = "-mmacosx-version-min=12.0", +) + util.helper_target( cc_args, name = "simple", @@ -55,6 +61,15 @@ util.helper_target( requires_not_none = "//cc/toolchains/variables:user_compile_flags", ) +util.helper_target( + cc_args, + name = "build_setting_format", + actions = ["//tests/rule_based_toolchain/actions:all_compile"], + args = ["{min_os_flag}"], + env = {"APPLE_MIN_OS": "{min_os_flag}"}, + format = {"min_os_flag": ":macos_min_os_flag"}, +) + util.helper_target( cc_args, name = "with_dir", diff --git a/tests/rule_based_toolchain/args/args_test.bzl b/tests/rule_based_toolchain/args/args_test.bzl index 2adc82b0b..009cc7ce9 100644 --- a/tests/rule_based_toolchain/args/args_test.bzl +++ b/tests/rule_based_toolchain/args/args_test.bzl @@ -169,11 +169,33 @@ def _with_dir_and_data_test(env, targets): ) c_compile.files().contains_at_least(_SIMPLE_FILES) +def _build_setting_format_test(env, targets): + build_setting = env.expect.that_target(targets.build_setting_format).provider(ArgsInfo) + build_setting.actions().contains_exactly([ + targets.c_compile.label, + targets.cpp_compile.label, + ]) + build_setting.env().entries().contains_exactly({"APPLE_MIN_OS": "-mmacosx-version-min=12.0"}) + + converted = env.expect.that_value( + convert_args(targets.build_setting_format[ArgsInfo]), + factory = _CONVERTED_ARGS, + ) + converted.env_sets().contains_exactly([env_set( + actions = ["c_compile", "cpp_compile"], + env_entries = [env_entry(key = "APPLE_MIN_OS", value = "-mmacosx-version-min=12.0")], + )]) + converted.flag_sets().contains_exactly([flag_set( + actions = ["c_compile", "cpp_compile"], + flag_groups = [flag_group(flags = ["-mmacosx-version-min=12.0"])], + )]) + TARGETS = [ ":simple", ":some_variable", ":env_only", ":env_only_requires", + ":build_setting_format", ":with_dir", ":with_dir_and_data", ":iterate_over_optional", @@ -361,6 +383,7 @@ TESTS = { "env_only_requires_test": _env_only_requires_test, "with_dir_test": _with_dir_test, "with_dir_and_data_test": _with_dir_and_data_test, + "build_setting_format_test": _build_setting_format_test, "good_env_format_test": _good_env_format_test, "good_env_format_optional_test": _good_env_format_optional_test, } From d3dfcff189100a029364990e016dcc6c2a8f973c Mon Sep 17 00:00:00 2001 From: David Zbarsky Date: Mon, 9 Feb 2026 13:04:28 -0500 Subject: [PATCH 2/2] CR --- cc/toolchains/impl/nested_args.bzl | 12 +++-- .../analysis_test_suite.bzl | 12 ++++- tests/rule_based_toolchain/args/BUILD | 7 +++ tests/rule_based_toolchain/args/args_test.bzl | 24 +++++++++ .../args/override_args.bzl | 51 +++++++++++++++++++ 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 tests/rule_based_toolchain/args/override_args.bzl diff --git a/cc/toolchains/impl/nested_args.bzl b/cc/toolchains/impl/nested_args.bzl index ed16fbd1d..0396fd55a 100644 --- a/cc/toolchains/impl/nested_args.bzl +++ b/cc/toolchains/impl/nested_args.bzl @@ -294,19 +294,21 @@ def _escape(s): _SUPPORTED_BUILD_SETTING_TYPES = ["string", "bool", "int", "Label"] -def _format_build_setting(target, fail = fail): - value = target[BuildSettingInfo].value - +def _format_build_setting(value, label, fail = fail): if type(value) in _SUPPORTED_BUILD_SETTING_TYPES: return _escape(str(value)) - fail("%s had an unsupported build setting type %s. Only string, bool, int, or Label values may be formatted." % (target.label, type(value))) + fail("%s had an unsupported build setting type %s. Only string, bool, int, or Label values may be formatted." % (label, type(value))) def _format_target(target, fail = fail): if VariableInfo in target: return "%%{%s}" % target[VariableInfo].name elif BuildSettingInfo in target: - return _format_build_setting(target, fail = fail) + return _format_build_setting( + target[BuildSettingInfo].value, + target.label, + fail = fail, + ) elif DirectoryInfo in target: return _escape(target[DirectoryInfo].path) diff --git a/tests/rule_based_toolchain/analysis_test_suite.bzl b/tests/rule_based_toolchain/analysis_test_suite.bzl index 01ba4e298..a67c4e753 100644 --- a/tests/rule_based_toolchain/analysis_test_suite.bzl +++ b/tests/rule_based_toolchain/analysis_test_suite.bzl @@ -33,15 +33,25 @@ def analysis_test_suite(name, tests, targets = [_DEFAULT_TARGET]): targets = [native.package_relative_label(target) for target in targets] test_case_names = [] - for test_name, impl in tests.items(): + for test_name, test in tests.items(): if not test_name.endswith("_test"): fail("Expected test keys to end with '_test', got test case %r" % test_name) test_case_names.append(":" + test_name) + config_settings = {} + if type(test) == "struct": + if not hasattr(test, "impl"): + fail("Expected struct tests to define an 'impl' field, got %r" % test_name) + impl = test.impl + if hasattr(test, "config_settings"): + config_settings = test.config_settings + else: + impl = test analysis_test( name = test_name, impl = impl, provider_subject_factories = FACTORIES, targets = {label.name: label for label in targets}, + config_settings = config_settings, ) native.test_suite( diff --git a/tests/rule_based_toolchain/args/BUILD b/tests/rule_based_toolchain/args/BUILD index 9779d9777..35899647c 100644 --- a/tests/rule_based_toolchain/args/BUILD +++ b/tests/rule_based_toolchain/args/BUILD @@ -5,6 +5,7 @@ load("//cc/toolchains/impl:variables.bzl", "cc_variable", "types") load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite") load("//tests/rule_based_toolchain:testing_rules.bzl", "expect_failure_test") load(":args_test.bzl", "TARGETS", "TESTS") +load(":override_args.bzl", "override_args") cc_variable( name = "some_variable", @@ -70,6 +71,12 @@ util.helper_target( format = {"min_os_flag": ":macos_min_os_flag"}, ) +override_args( + name = "build_setting_format_override", + target = ":build_setting_format", + value = "-mmacosx-version-min=13.0", +) + util.helper_target( cc_args, name = "with_dir", diff --git a/tests/rule_based_toolchain/args/args_test.bzl b/tests/rule_based_toolchain/args/args_test.bzl index 009cc7ce9..535e5db88 100644 --- a/tests/rule_based_toolchain/args/args_test.bzl +++ b/tests/rule_based_toolchain/args/args_test.bzl @@ -50,6 +50,7 @@ _SIMPLE_FILES = [ "tests/rule_based_toolchain/testdata/multiple2", ] _TOOL_DIRECTORY = "tests/rule_based_toolchain/testdata" +_OVERRIDDEN_MIN_OS = "-mmacosx-version-min=13.0" _CONVERTED_ARGS = subjects.struct( flag_sets = subjects.collection, @@ -190,12 +191,34 @@ def _build_setting_format_test(env, targets): flag_groups = [flag_group(flags = ["-mmacosx-version-min=12.0"])], )]) +def _build_setting_format_override_test(env, targets): + build_setting = env.expect.that_target(targets.build_setting_format_override).provider(ArgsInfo) + build_setting.actions().contains_exactly([ + targets.c_compile.label, + targets.cpp_compile.label, + ]) + build_setting.env().entries().contains_exactly({"APPLE_MIN_OS": _OVERRIDDEN_MIN_OS}) + + converted = env.expect.that_value( + convert_args(targets.build_setting_format_override[ArgsInfo]), + factory = _CONVERTED_ARGS, + ) + converted.env_sets().contains_exactly([env_set( + actions = ["c_compile", "cpp_compile"], + env_entries = [env_entry(key = "APPLE_MIN_OS", value = _OVERRIDDEN_MIN_OS)], + )]) + converted.flag_sets().contains_exactly([flag_set( + actions = ["c_compile", "cpp_compile"], + flag_groups = [flag_group(flags = [_OVERRIDDEN_MIN_OS])], + )]) + TARGETS = [ ":simple", ":some_variable", ":env_only", ":env_only_requires", ":build_setting_format", + ":build_setting_format_override", ":with_dir", ":with_dir_and_data", ":iterate_over_optional", @@ -384,6 +407,7 @@ TESTS = { "with_dir_test": _with_dir_test, "with_dir_and_data_test": _with_dir_and_data_test, "build_setting_format_test": _build_setting_format_test, + "build_setting_format_override_test": _build_setting_format_override_test, "good_env_format_test": _good_env_format_test, "good_env_format_optional_test": _good_env_format_optional_test, } diff --git a/tests/rule_based_toolchain/args/override_args.bzl b/tests/rule_based_toolchain/args/override_args.bzl new file mode 100644 index 000000000..fd294afcb --- /dev/null +++ b/tests/rule_based_toolchain/args/override_args.bzl @@ -0,0 +1,51 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Test-only helper to apply a build setting override.""" + +load("//cc/toolchains:cc_toolchain_info.bzl", "ArgsInfo", "ArgsListInfo") + +_MIN_OS_FLAG = "//tests/rule_based_toolchain/args:macos_min_os_flag" + +def _override_min_os_transition_impl(_settings, attr): + return {_MIN_OS_FLAG: attr.value} + +_override_min_os_transition = transition( + implementation = _override_min_os_transition_impl, + inputs = [], + outputs = [_MIN_OS_FLAG], +) + +def _override_args_impl(ctx): + target = ctx.attr.target + if type(target) == "list": + target = target[0] + return [ + target[ArgsInfo], + target[ArgsListInfo], + ] + +override_args = rule( + implementation = _override_args_impl, + attrs = { + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + "target": attr.label( + cfg = _override_min_os_transition, + providers = [ArgsInfo], + mandatory = True, + ), + "value": attr.string(mandatory = True), + }, +)