From 51a3bc8a73c63d235be39110dface68a46b6ce23 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Sat, 21 Mar 2026 20:00:25 -0700 Subject: [PATCH] Add support for toolchains in cc_args This allows you to propagate specific attributes that can only be accessed in rules, such as from fragments, or other native API like xcode_config, in env or args. I chose to reuse the existing replacement infrastructure instead of `$()` to avoid conflicts with the `$` character. --- cc/toolchains/args.bzl | 5 +++- cc/toolchains/impl/nested_args.bzl | 12 ++++++-- tests/rule_based_toolchain/args/BUILD | 29 +++++++++++++++++++ tests/rule_based_toolchain/args/args_test.bzl | 18 ++++++++++++ .../args/make_var_provider.bzl | 11 +++++++ tests/rule_based_toolchain/nested_args/BUILD | 19 ++++++++++++ .../nested_args/nested_args_test.bzl | 7 +++++ 7 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 tests/rule_based_toolchain/args/make_var_provider.bzl diff --git a/cc/toolchains/args.bzl b/cc/toolchains/args.bzl index 95cdf5217..0c3f5451c 100644 --- a/cc/toolchains/args.bzl +++ b/cc/toolchains/args.bzl @@ -47,9 +47,12 @@ def _cc_args_impl(ctx): 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 = {k: struct(__raw_string = v) for k, v in ctx.var.items()} | {k: v for v, k in ctx.attr.format.items()}, ) + # Filter out variables from ctx.var which don't have to be used + used_format_vars = [v for v in used_format_vars if v in ctx.attr.format.values()] + for path in ctx.attr.allowlist_absolute_include_directories: if not is_path_absolute(path): fail("`{}` is not an absolute paths".format(path)) diff --git a/cc/toolchains/impl/nested_args.bzl b/cc/toolchains/impl/nested_args.bzl index e59711d5a..c91c1f71d 100644 --- a/cc/toolchains/impl/nested_args.bzl +++ b/cc/toolchains/impl/nested_args.bzl @@ -97,6 +97,7 @@ def nested_args_provider_from_ctx(ctx, maybe_used_vars = []): return nested_args_provider( label = ctx.label, args = ctx.attr.args, + make_variables = ctx.var, format = ctx.attr.format, nested = collect_provider(ctx.attr.nested, NestedArgsInfo), files = collect_files(ctx.attr.data), @@ -125,6 +126,7 @@ def nested_args_provider( requires_equal = None, requires_equal_value = "", maybe_used_vars = [], + make_variables = {}, fail = fail): """Creates a validated NestedArgsInfo. @@ -153,6 +155,8 @@ def nested_args_provider( requires_equal_value: (str) The value to compare the requires_equal variable with maybe_used_vars: (List[str]) A list of format variables that are not needed during args formatting. + make_variables: (dict[str, str]) Make variable substitutions from + ctx.var. {KEY} references in args are replaced with values. fail: A fail function. Use only for testing. Returns: NestedArgsInfo @@ -177,6 +181,8 @@ def nested_args_provider( )) replacements[name] = target + make_replacements = {k: struct(__raw_string = v) for k, v in make_variables.items()} + # Intentionally ensure that we do not have to use the variable provided by # iterate_over in the format string. # For example, a valid use case is: @@ -190,7 +196,7 @@ def nested_args_provider( # ) formatted_args, _ = format_list( args, - replacements, + make_replacements | replacements, must_use = [var for var in format.values() if var not in maybe_used_vars], fail = fail, ) @@ -292,7 +298,9 @@ def _escape(s): return s.replace("%", "%%") def _format_target(target, fail = fail): - if VariableInfo in target: + if hasattr(target, "__raw_string"): + return _escape(target.__raw_string) + elif VariableInfo in target: return "%%{%s}" % target[VariableInfo].name elif DirectoryInfo in target: return _escape(target[DirectoryInfo].path) diff --git a/tests/rule_based_toolchain/args/BUILD b/tests/rule_based_toolchain/args/BUILD index 7f47073c4..aa0582184 100644 --- a/tests/rule_based_toolchain/args/BUILD +++ b/tests/rule_based_toolchain/args/BUILD @@ -4,6 +4,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(":make_var_provider.bzl", "make_var_provider") cc_variable( name = "some_variable", @@ -134,6 +135,34 @@ expect_failure_test( target = ":bad_env_format_wrong_action", ) +make_var_provider( + name = "make_vars", + variables = { + "MY_PATH": "/usr/local", + "MY_FLAG": "-DFOO", + }, +) + +util.helper_target( + cc_args, + name = "with_make_vars", + actions = ["//tests/rule_based_toolchain/actions:all_compile"], + args = [ + "--path={MY_PATH}", + "{MY_FLAG}", + ], + toolchains = [":make_vars"], +) + +util.helper_target( + cc_args, + name = "with_make_vars_env", + actions = ["//tests/rule_based_toolchain/actions:all_compile"], + args = ["--foo"], + env = {"MY_ENV": "{MY_PATH}/bin"}, + toolchains = [":make_vars"], +) + analysis_test_suite( name = "test_suite", targets = TARGETS, diff --git a/tests/rule_based_toolchain/args/args_test.bzl b/tests/rule_based_toolchain/args/args_test.bzl index 2adc82b0b..36cc91e0b 100644 --- a/tests/rule_based_toolchain/args/args_test.bzl +++ b/tests/rule_based_toolchain/args/args_test.bzl @@ -176,6 +176,8 @@ TARGETS = [ ":env_only_requires", ":with_dir", ":with_dir_and_data", + ":with_make_vars", + ":with_make_vars_env", ":iterate_over_optional", ":good_env_format", ":good_env_format_optional", @@ -322,6 +324,20 @@ def _good_env_format_test(env, targets): )], )]) +def _with_make_vars_test(env, targets): + converted = env.expect.that_value( + convert_args(targets.with_make_vars[ArgsInfo]), + factory = _CONVERTED_ARGS, + ) + converted.flag_sets().contains_exactly([flag_set( + actions = ["c_compile", "cpp_compile"], + flag_groups = [flag_group(flags = ["--path=/usr/local", "-DFOO"])], + )]) + +def _with_make_vars_env_test(env, targets): + make_vars_env = env.expect.that_target(targets.with_make_vars_env).provider(ArgsInfo) + make_vars_env.env().entries().contains_exactly({"MY_ENV": "/usr/local/bin"}) + def _good_env_format_optional_test(env, targets): """Test that env formatting works with optional types.""" good_env_optional = env.expect.that_target(targets.good_env_format_optional).provider(ArgsInfo) @@ -363,4 +379,6 @@ TESTS = { "with_dir_and_data_test": _with_dir_and_data_test, "good_env_format_test": _good_env_format_test, "good_env_format_optional_test": _good_env_format_optional_test, + "with_make_vars_test": _with_make_vars_test, + "with_make_vars_env_test": _with_make_vars_env_test, } diff --git a/tests/rule_based_toolchain/args/make_var_provider.bzl b/tests/rule_based_toolchain/args/make_var_provider.bzl new file mode 100644 index 000000000..740387397 --- /dev/null +++ b/tests/rule_based_toolchain/args/make_var_provider.bzl @@ -0,0 +1,11 @@ +"""Helper rule for providing TemplateVariableInfo in tests.""" + +def _make_var_provider_impl(ctx): + return [platform_common.TemplateVariableInfo(ctx.attr.variables)] + +make_var_provider = rule( + implementation = _make_var_provider_impl, + attrs = { + "variables": attr.string_dict(), + }, +) diff --git a/tests/rule_based_toolchain/nested_args/BUILD b/tests/rule_based_toolchain/nested_args/BUILD index 491ed0b4d..180453741 100644 --- a/tests/rule_based_toolchain/nested_args/BUILD +++ b/tests/rule_based_toolchain/nested_args/BUILD @@ -1,5 +1,7 @@ +load("//cc/toolchains:nested_args.bzl", "cc_nested_args") 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/args:make_var_provider.bzl", "make_var_provider") load(":nested_args_test.bzl", "TARGETS", "TESTS") cc_variable( @@ -12,6 +14,23 @@ cc_variable( type = types.list(types.string), ) +make_var_provider( + name = "make_vars", + variables = { + "MY_PATH": "/usr/local", + "MY_FLAG": "-DFOO", + }, +) + +cc_nested_args( + name = "nested_with_make_vars", + args = [ + "--path={MY_PATH}", + "{MY_FLAG}", + ], + toolchains = [":make_vars"], +) + analysis_test_suite( name = "test_suite", targets = TARGETS, diff --git a/tests/rule_based_toolchain/nested_args/nested_args_test.bzl b/tests/rule_based_toolchain/nested_args/nested_args_test.bzl index 83d99887c..fa446e2d2 100644 --- a/tests/rule_based_toolchain/nested_args/nested_args_test.bzl +++ b/tests/rule_based_toolchain/nested_args/nested_args_test.bzl @@ -15,6 +15,7 @@ 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") load( "//cc/toolchains/impl:nested_args.bzl", "FORMAT_ARGS_ERR", @@ -225,9 +226,14 @@ def _requires_types_test(env, targets): flags = ["--foo=%{foo}"], )) +def _nested_make_vars_test(env, targets): + nested = env.expect.that_target(targets.nested_with_make_vars).provider(NestedArgsInfo) + nested.legacy_flag_group().equals(flag_group(flags = ["--path=/usr/local", "-DFOO"])) + TARGETS = [ ":foo", ":my_list", + ":nested_with_make_vars", "//tests/rule_based_toolchain/testdata:directory", "//tests/rule_based_toolchain/testdata:bin_wrapper", ] @@ -236,4 +242,5 @@ TESTS = { "format_args_test": _format_args_test, "iterate_over_test": _iterate_over_test, "requires_types_test": _requires_types_test, + "nested_make_vars_test": _nested_make_vars_test, }