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
17 changes: 12 additions & 5 deletions cc/toolchains/args.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion cc/toolchains/impl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
17 changes: 16 additions & 1 deletion cc/toolchains/impl/nested_args.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -291,17 +292,31 @@ def nested_args_provider(
def _escape(s):
return s.replace("%", "%%")

_SUPPORTED_BUILD_SETTING_TYPES = ["string", "bool", "int", "Label"]

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." % (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[BuildSettingInfo].value,
target.label,
fail = fail,
)
elif DirectoryInfo in target:
return _escape(target[DirectoryInfo].path)

files = target[DefaultInfo].files.to_list()
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
Expand Down
2 changes: 1 addition & 1 deletion docs/toolchain_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ For more extensive examples, see the usages here:
| <a id="cc_args-args"></a>args | (List[str]) The command-line arguments that are applied by using this rule. This is mutually exclusive with [nested](#cc_args-nested). | `None` |
| <a id="cc_args-data"></a>data | (List[Label]) A list of runtime data dependencies that are required for these arguments to work as intended. | `None` |
| <a id="cc_args-env"></a>env | (Dict[str, str]) Environment variables that should be set when the tool is invoked. | `None` |
| <a id="cc_args-format"></a>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. | `{}` |
| <a id="cc_args-format"></a>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. | `{}` |
| <a id="cc_args-iterate_over"></a>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` |
| <a id="cc_args-nested"></a>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` |
| <a id="cc_args-requires_not_none"></a>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` |
Expand Down
12 changes: 11 additions & 1 deletion tests/rule_based_toolchain/analysis_test_suite.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
22 changes: 22 additions & 0 deletions tests/rule_based_toolchain/args/BUILD
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
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")
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",
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",
Expand Down Expand Up @@ -55,6 +62,21 @@ 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"},
)

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",
Expand Down
47 changes: 47 additions & 0 deletions tests/rule_based_toolchain/args/args_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -169,11 +170,55 @@ def _with_dir_and_data_test(env, targets):
)
c_compile.files().contains_at_least(_SIMPLE_FILES)

def _build_setting_format_test(env, targets):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a test that sets the flag to a new value and validates that the new value is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

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"])],
)])

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",
Expand Down Expand Up @@ -361,6 +406,8 @@ 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,
"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,
}
51 changes: 51 additions & 0 deletions tests/rule_based_toolchain/args/override_args.bzl
Original file line number Diff line number Diff line change
@@ -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),
},
)