mirror of https://github.com/bazelbuild/rules_cc
Add support for select'ing on cc_args(args=...).
This CL is an alternative to unknown commit. I left the other CL seperately, because I wasn't 100% sure that we'd agree to this, since this is an API change. I did it this way because I believe it's much less hacky, and it also allows us to format things that aren't variables. BEGIN_PUBLIC Add support for select'ing on cc_args(args=...). This is quite tricky because the one parameter was being split into two in a macro, one of type label and the other of type string. For example, `args = ["--foo", format_arg("--bar=%s", "//path/to:bar")]` was rewritten by the macro to `args = [json.encode(struct(format_type="raw", format="foo")), json.encode(struct(format_type="format_arg", format="--bar=%s", value=0))], variables = ["//path/to:bar"]`. To allow it to work with selects, we need to ensure that we don't perform post-processing on the inside of the select. To solve this, we: * Ensure that args only take strings * Provide a seperate parameter for substitutions. This new mechanism also has the useful property that we can now format things that are not variables. For example, I can do the following: ``` directory(name = "sysroot", ...) cc_args( name = "sysroot_arg", args = ["--sysroot={sysroot}"], format = { ":sysroot": "sysroot" } ) ``` END_PUBLIC PiperOrigin-RevId: 656211278 Change-Id: If83f1ea5a99090c18f2a561c51ec6d39ce9fe419
This commit is contained in:
parent
0d1b084cfa
commit
e1c7ebb858
|
@ -23,7 +23,6 @@ load(
|
||||||
load(
|
load(
|
||||||
"//cc/toolchains/impl:nested_args.bzl",
|
"//cc/toolchains/impl:nested_args.bzl",
|
||||||
"NESTED_ARGS_ATTRS",
|
"NESTED_ARGS_ATTRS",
|
||||||
"args_wrapper_macro",
|
|
||||||
"nested_args_provider_from_ctx",
|
"nested_args_provider_from_ctx",
|
||||||
)
|
)
|
||||||
load(
|
load(
|
||||||
|
@ -40,9 +39,6 @@ visibility("public")
|
||||||
def _cc_args_impl(ctx):
|
def _cc_args_impl(ctx):
|
||||||
actions = collect_action_types(ctx.attr.actions)
|
actions = collect_action_types(ctx.attr.actions)
|
||||||
|
|
||||||
if not ctx.attr.args and not ctx.attr.nested and not ctx.attr.env:
|
|
||||||
fail("cc_args requires at least one of args, nested, and env")
|
|
||||||
|
|
||||||
nested = None
|
nested = None
|
||||||
if ctx.attr.args or ctx.attr.nested:
|
if ctx.attr.args or ctx.attr.nested:
|
||||||
nested = nested_args_provider_from_ctx(ctx)
|
nested = nested_args_provider_from_ctx(ctx)
|
||||||
|
@ -117,4 +113,9 @@ Examples:
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_args = lambda **kwargs: args_wrapper_macro(rule = _cc_args, **kwargs)
|
def cc_args(name, format = {}, **kwargs):
|
||||||
|
return _cc_args(
|
||||||
|
name = name,
|
||||||
|
format = {k: v for v, k in format.items()},
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 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.
|
|
||||||
"""Functions to format arguments for the cc toolchain"""
|
|
||||||
|
|
||||||
def format_arg(format, value = None):
|
|
||||||
"""Generate metadata to format a variable with a given value.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
format: (str) The format string
|
|
||||||
value: (Optional[Label]) The variable to format. Any is used because it can
|
|
||||||
be any representation of a variable.
|
|
||||||
Returns:
|
|
||||||
A struct corresponding to the formatted variable.
|
|
||||||
"""
|
|
||||||
return struct(format_type = "format_arg", format = format, value = value)
|
|
|
@ -13,7 +13,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""Helper functions for working with args."""
|
"""Helper functions for working with args."""
|
||||||
|
|
||||||
load("@bazel_skylib//lib:structs.bzl", "structs")
|
load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo")
|
||||||
load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value")
|
load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value")
|
||||||
load("//cc/toolchains:cc_toolchain_info.bzl", "NestedArgsInfo", "VariableInfo")
|
load("//cc/toolchains:cc_toolchain_info.bzl", "NestedArgsInfo", "VariableInfo")
|
||||||
load(":collect.bzl", "collect_files", "collect_provider")
|
load(":collect.bzl", "collect_files", "collect_provider")
|
||||||
|
@ -30,25 +30,7 @@ REQUIRES_TRUE_ERR = "requires_true only works on bools"
|
||||||
REQUIRES_FALSE_ERR = "requires_false only works on bools"
|
REQUIRES_FALSE_ERR = "requires_false only works on bools"
|
||||||
REQUIRES_EQUAL_ERR = "requires_equal only works on strings"
|
REQUIRES_EQUAL_ERR = "requires_equal only works on strings"
|
||||||
REQUIRES_EQUAL_VALUE_ERR = "When requires_equal is provided, you must also provide requires_equal_value to specify what it should be equal to"
|
REQUIRES_EQUAL_VALUE_ERR = "When requires_equal is provided, you must also provide requires_equal_value to specify what it should be equal to"
|
||||||
FORMAT_ARGS_ERR = "format_args can only format strings, files, or directories"
|
FORMAT_ARGS_ERR = "format only works on string, file, or directory type variables"
|
||||||
|
|
||||||
_NOT_ESCAPED_FMT = "%% should always either of the form %%s, or escaped with %%%%. Instead, got %r"
|
|
||||||
|
|
||||||
_EXAMPLE = """
|
|
||||||
|
|
||||||
cc_args(
|
|
||||||
...,
|
|
||||||
args = [format_arg("--foo=%s", "//cc/toolchains/variables:foo")]
|
|
||||||
)
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
cc_args(
|
|
||||||
...,
|
|
||||||
# If foo_list contains ["a", "b"], then this expands to ["--foo", "+a", "--foo", "+b"].
|
|
||||||
args = ["--foo", format_arg("+%s")],
|
|
||||||
iterate_over = "//toolchains/variables:foo_list",
|
|
||||||
"""
|
|
||||||
|
|
||||||
# @unsorted-dict-items.
|
# @unsorted-dict-items.
|
||||||
NESTED_ARGS_ATTRS = {
|
NESTED_ARGS_ATTRS = {
|
||||||
|
@ -58,7 +40,10 @@ NESTED_ARGS_ATTRS = {
|
||||||
Usage:
|
Usage:
|
||||||
cc_args(
|
cc_args(
|
||||||
...,
|
...,
|
||||||
args = ["--foo", format_arg("%s", "//cc/toolchains/variables:foo")]
|
args = ["--foo={foo}"],
|
||||||
|
format = {
|
||||||
|
"//cc/toolchains/variables:foo": "foo"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
This is equivalent to flag_group(flags = ["--foo", "%{foo}"])
|
This is equivalent to flag_group(flags = ["--foo", "%{foo}"])
|
||||||
|
@ -80,8 +65,7 @@ For example, a flag that sets the header directory might add the headers in that
|
||||||
directory as additional files.
|
directory as additional files.
|
||||||
""",
|
""",
|
||||||
),
|
),
|
||||||
"variables": attr.label_list(
|
"format": attr.label_keyed_string_dict(
|
||||||
providers = [VariableInfo],
|
|
||||||
doc = "Variables to be used in substitutions",
|
doc = "Variables to be used in substitutions",
|
||||||
),
|
),
|
||||||
"iterate_over": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.iterate_over"),
|
"iterate_over": attr.label(providers = [VariableInfo], doc = "Replacement for flag_group.iterate_over"),
|
||||||
|
@ -93,45 +77,6 @@ directory as additional files.
|
||||||
"requires_equal_value": attr.string(),
|
"requires_equal_value": attr.string(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def args_wrapper_macro(*, name, rule, args = [], **kwargs):
|
|
||||||
"""Invokes a rule by converting args to attributes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: (str) The name of the target.
|
|
||||||
rule: (rule) The rule to invoke. Either cc_args or cc_nested_args.
|
|
||||||
args: (List[str|Formatted]) A list of either strings, or function calls
|
|
||||||
from format.bzl. For example:
|
|
||||||
["--foo", format_arg("--sysroot=%s", "//cc/toolchains/variables:sysroot")]
|
|
||||||
**kwargs: kwargs to pass through into the rule invocation.
|
|
||||||
"""
|
|
||||||
out_args = []
|
|
||||||
vars = []
|
|
||||||
if type(args) != "list":
|
|
||||||
fail("Args must be a list in %s" % native.package_relative_label(name))
|
|
||||||
for arg in args:
|
|
||||||
if type(arg) == "string":
|
|
||||||
out_args.append(raw_string(arg))
|
|
||||||
elif getattr(arg, "format_type") == "format_arg":
|
|
||||||
arg = structs.to_dict(arg)
|
|
||||||
if arg["value"] == None:
|
|
||||||
out_args.append(arg)
|
|
||||||
else:
|
|
||||||
var = arg.pop("value")
|
|
||||||
|
|
||||||
# Swap the variable from a label to an index. This allows us to
|
|
||||||
# actually get the providers in a rule.
|
|
||||||
out_args.append(struct(value = len(vars), **arg))
|
|
||||||
vars.append(var)
|
|
||||||
else:
|
|
||||||
fail("Invalid type of args in %s. Expected either a string or format_args(format_string, variable_label), got value %r" % (native.package_relative_label(name), arg))
|
|
||||||
|
|
||||||
rule(
|
|
||||||
name = name,
|
|
||||||
args = [json.encode(arg) for arg in out_args],
|
|
||||||
variables = vars,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def _var(target):
|
def _var(target):
|
||||||
if target == None:
|
if target == None:
|
||||||
return None
|
return None
|
||||||
|
@ -147,21 +92,13 @@ def nested_args_provider_from_ctx(ctx):
|
||||||
Returns:
|
Returns:
|
||||||
NestedArgsInfo
|
NestedArgsInfo
|
||||||
"""
|
"""
|
||||||
variables = collect_provider(ctx.attr.variables, VariableInfo)
|
|
||||||
args = []
|
|
||||||
for arg in ctx.attr.args:
|
|
||||||
arg = json.decode(arg)
|
|
||||||
if "value" in arg:
|
|
||||||
if arg["value"] != None:
|
|
||||||
arg["value"] = variables[arg["value"]]
|
|
||||||
args.append(struct(**arg))
|
|
||||||
|
|
||||||
return nested_args_provider(
|
return nested_args_provider(
|
||||||
label = ctx.label,
|
label = ctx.label,
|
||||||
args = args,
|
args = ctx.attr.args,
|
||||||
|
format = ctx.attr.format,
|
||||||
nested = collect_provider(ctx.attr.nested, NestedArgsInfo),
|
nested = collect_provider(ctx.attr.nested, NestedArgsInfo),
|
||||||
files = collect_files(ctx.attr.data),
|
files = collect_files(ctx.attr.data),
|
||||||
iterate_over = _var(ctx.attr.iterate_over),
|
iterate_over = ctx.attr.iterate_over,
|
||||||
requires_not_none = _var(ctx.attr.requires_not_none),
|
requires_not_none = _var(ctx.attr.requires_not_none),
|
||||||
requires_none = _var(ctx.attr.requires_none),
|
requires_none = _var(ctx.attr.requires_none),
|
||||||
requires_true = _var(ctx.attr.requires_true),
|
requires_true = _var(ctx.attr.requires_true),
|
||||||
|
@ -170,85 +107,12 @@ def nested_args_provider_from_ctx(ctx):
|
||||||
requires_equal_value = ctx.attr.requires_equal_value,
|
requires_equal_value = ctx.attr.requires_equal_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
def raw_string(s):
|
|
||||||
"""Constructs metadata for creating a raw string.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
s: (str) The string to input.
|
|
||||||
Returns:
|
|
||||||
Metadata suitable for format_variable.
|
|
||||||
"""
|
|
||||||
return struct(format_type = "raw", format = s)
|
|
||||||
|
|
||||||
def format_string_indexes(s, fail = fail):
|
|
||||||
"""Gets the index of a '%s' in a string.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
s: (str) The string
|
|
||||||
fail: The fail function. Used for tests
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[int] The indexes of the '%s' in the string
|
|
||||||
"""
|
|
||||||
indexes = []
|
|
||||||
escaped = False
|
|
||||||
for i in range(len(s)):
|
|
||||||
if not escaped and s[i] == "%":
|
|
||||||
escaped = True
|
|
||||||
elif escaped:
|
|
||||||
if s[i] == "{":
|
|
||||||
fail('Using the old mechanism for variables, %%{variable}, but we instead use format_arg("--foo=%%s", "//cc/toolchains/variables:<variable>"). Got %r' % s)
|
|
||||||
elif s[i] == "s":
|
|
||||||
indexes.append(i - 1)
|
|
||||||
elif s[i] != "%":
|
|
||||||
fail(_NOT_ESCAPED_FMT % s)
|
|
||||||
escaped = False
|
|
||||||
if escaped:
|
|
||||||
return fail(_NOT_ESCAPED_FMT % s)
|
|
||||||
return indexes
|
|
||||||
|
|
||||||
def format_variable(arg, iterate_over, fail = fail):
|
|
||||||
"""Lists all of the variables referenced by an argument.
|
|
||||||
|
|
||||||
Eg: referenced_variables([
|
|
||||||
format_arg("--foo", None),
|
|
||||||
format_arg("--bar=%s", ":bar")
|
|
||||||
]) => ["--foo", "--bar=%{bar}"]
|
|
||||||
|
|
||||||
Args:
|
|
||||||
arg: [Formatted] The command-line arguments, as created by the format_arg function.
|
|
||||||
iterate_over: (Optional[str]) The name of the variable we're iterating over.
|
|
||||||
fail: The fail function. Used for tests
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A string defined to be compatible with flag groups.
|
|
||||||
"""
|
|
||||||
indexes = format_string_indexes(arg.format, fail = fail)
|
|
||||||
if arg.format_type == "raw":
|
|
||||||
if indexes:
|
|
||||||
return fail("Can't use %s with a raw string. Either escape it with %%s or use format_arg, like the following examples:" + _EXAMPLE)
|
|
||||||
return arg.format
|
|
||||||
else:
|
|
||||||
if len(indexes) == 0:
|
|
||||||
return fail('format_arg requires a "%%s" in the format string, but got %r' % arg.format)
|
|
||||||
elif len(indexes) > 1:
|
|
||||||
return fail("Only one %%s can be used in a format string, but got %r" % arg.format)
|
|
||||||
|
|
||||||
if arg.value == None:
|
|
||||||
if iterate_over == None:
|
|
||||||
return fail("format_arg requires either a variable to format, or iterate_over must be provided. For example:" + _EXAMPLE)
|
|
||||||
var = iterate_over
|
|
||||||
else:
|
|
||||||
var = arg.value.name
|
|
||||||
|
|
||||||
index = indexes[0]
|
|
||||||
return arg.format[:index] + "%{" + var + "}" + arg.format[index + 2:]
|
|
||||||
|
|
||||||
def nested_args_provider(
|
def nested_args_provider(
|
||||||
*,
|
*,
|
||||||
label,
|
label,
|
||||||
args = [],
|
args = [],
|
||||||
nested = [],
|
nested = [],
|
||||||
|
format = {},
|
||||||
files = depset([]),
|
files = depset([]),
|
||||||
iterate_over = None,
|
iterate_over = None,
|
||||||
requires_not_none = None,
|
requires_not_none = None,
|
||||||
|
@ -269,8 +133,9 @@ def nested_args_provider(
|
||||||
error messages.
|
error messages.
|
||||||
args: (List[str]) The command-line arguments to add.
|
args: (List[str]) The command-line arguments to add.
|
||||||
nested: (List[NestedArgsInfo]) command-line arguments to expand.
|
nested: (List[NestedArgsInfo]) command-line arguments to expand.
|
||||||
|
format: (dict[Target, str]) A mapping from target to format string name
|
||||||
files: (depset[File]) Files required for this set of command-line args.
|
files: (depset[File]) Files required for this set of command-line args.
|
||||||
iterate_over: (Optional[str]) Variable to iterate over
|
iterate_over: (Optional[Target]) Target for the variable to iterate over
|
||||||
requires_not_none: (Optional[str]) If provided, this NestedArgsInfo will
|
requires_not_none: (Optional[str]) If provided, this NestedArgsInfo will
|
||||||
be ignored if the variable is None
|
be ignored if the variable is None
|
||||||
requires_none: (Optional[str]) If provided, this NestedArgsInfo will
|
requires_none: (Optional[str]) If provided, this NestedArgsInfo will
|
||||||
|
@ -287,8 +152,38 @@ def nested_args_provider(
|
||||||
Returns:
|
Returns:
|
||||||
NestedArgsInfo
|
NestedArgsInfo
|
||||||
"""
|
"""
|
||||||
if bool(args) == bool(nested):
|
if bool(args) and bool(nested):
|
||||||
fail("Exactly one of args and nested must be provided")
|
fail("Args and nested are mutually exclusive")
|
||||||
|
|
||||||
|
replacements = {}
|
||||||
|
if iterate_over:
|
||||||
|
# Since the user didn't assign a name to iterate_over, allow them to
|
||||||
|
# reference it as "--foo={}"
|
||||||
|
replacements[""] = iterate_over
|
||||||
|
|
||||||
|
# Intentionally ensure that {} clashes between an explicit user format
|
||||||
|
# string "" and the implicit one provided by iterate_over.
|
||||||
|
for target, name in format.items():
|
||||||
|
if name in replacements:
|
||||||
|
fail("Both %s and %s have the format string name %r" % (
|
||||||
|
target.label,
|
||||||
|
replacements[name].label,
|
||||||
|
name,
|
||||||
|
))
|
||||||
|
replacements[name] = target
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
# cc_args(
|
||||||
|
# nested = ":nested",
|
||||||
|
# iterate_over = "//cc/toolchains/variables:libraries_to_link",
|
||||||
|
# )
|
||||||
|
# cc_nested_args(
|
||||||
|
# args = ["{}"],
|
||||||
|
# iterate_over = "//cc/toolchains/variables:libraries_to_link.object_files",
|
||||||
|
# )
|
||||||
|
args = format_args(args, replacements, must_use = format.values(), fail = fail)
|
||||||
|
|
||||||
transitive_files = [ea.files for ea in nested]
|
transitive_files = [ea.files for ea in nested]
|
||||||
transitive_files.append(files)
|
transitive_files.append(files)
|
||||||
|
@ -307,6 +202,10 @@ def nested_args_provider(
|
||||||
fail(REQUIRES_MUTUALLY_EXCLUSIVE_ERR)
|
fail(REQUIRES_MUTUALLY_EXCLUSIVE_ERR)
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
|
if args:
|
||||||
|
kwargs["flags"] = args
|
||||||
|
|
||||||
requires_types = {}
|
requires_types = {}
|
||||||
if nested:
|
if nested:
|
||||||
kwargs["flag_groups"] = [ea.legacy_flag_group for ea in nested]
|
kwargs["flag_groups"] = [ea.legacy_flag_group for ea in nested]
|
||||||
|
@ -314,7 +213,7 @@ def nested_args_provider(
|
||||||
unwrap_options = []
|
unwrap_options = []
|
||||||
|
|
||||||
if iterate_over:
|
if iterate_over:
|
||||||
kwargs["iterate_over"] = iterate_over
|
kwargs["iterate_over"] = _var(iterate_over)
|
||||||
|
|
||||||
if requires_not_none:
|
if requires_not_none:
|
||||||
kwargs["expand_if_available"] = requires_not_none
|
kwargs["expand_if_available"] = requires_not_none
|
||||||
|
@ -361,27 +260,98 @@ def nested_args_provider(
|
||||||
after_option_unwrap = True,
|
after_option_unwrap = True,
|
||||||
))
|
))
|
||||||
|
|
||||||
for arg in args:
|
for arg in format:
|
||||||
if arg.format_type != "raw":
|
if VariableInfo in arg:
|
||||||
var_name = arg.value.name if arg.value != None else iterate_over
|
requires_types.setdefault(arg[VariableInfo].name, []).append(struct(
|
||||||
requires_types.setdefault(var_name, []).append(struct(
|
|
||||||
msg = FORMAT_ARGS_ERR,
|
msg = FORMAT_ARGS_ERR,
|
||||||
valid_types = ["string", "file", "directory"],
|
valid_types = ["string", "file", "directory"],
|
||||||
after_option_unwrap = True,
|
after_option_unwrap = True,
|
||||||
))
|
))
|
||||||
|
|
||||||
if args:
|
|
||||||
kwargs["flags"] = [
|
|
||||||
format_variable(arg, iterate_over = iterate_over, fail = fail)
|
|
||||||
for arg in args
|
|
||||||
]
|
|
||||||
|
|
||||||
return NestedArgsInfo(
|
return NestedArgsInfo(
|
||||||
label = label,
|
label = label,
|
||||||
nested = nested,
|
nested = nested,
|
||||||
files = depset(transitive = transitive_files),
|
files = depset(transitive = transitive_files),
|
||||||
iterate_over = iterate_over,
|
iterate_over = _var(iterate_over),
|
||||||
unwrap_options = unwrap_options,
|
unwrap_options = unwrap_options,
|
||||||
requires_types = requires_types,
|
requires_types = requires_types,
|
||||||
legacy_flag_group = flag_group(**kwargs),
|
legacy_flag_group = flag_group(**kwargs),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _escape(s):
|
||||||
|
return s.replace("%", "%%")
|
||||||
|
|
||||||
|
def _format_target(target, fail = fail):
|
||||||
|
if VariableInfo in target:
|
||||||
|
return "%%{%s}" % target[VariableInfo].name
|
||||||
|
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)
|
||||||
|
|
||||||
|
def format_args(args, format, must_use = [], fail = fail):
|
||||||
|
"""Lists all of the variables referenced by an argument.
|
||||||
|
|
||||||
|
Eg: format_args(["--foo", "--bar={bar}"], {"bar": VariableInfo(name="bar")})
|
||||||
|
=> ["--foo", "--bar=%{bar}"]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: (List[str]) The command-line arguments.
|
||||||
|
format: (Dict[str, Target]) A mapping of substitutions from key to target.
|
||||||
|
must_use: (List[str]) A list of substitutions that must be used.
|
||||||
|
fail: The fail function. Used for tests
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A string defined to be compatible with flag groups.
|
||||||
|
"""
|
||||||
|
formatted = []
|
||||||
|
used_vars = {}
|
||||||
|
|
||||||
|
for arg in args:
|
||||||
|
upto = 0
|
||||||
|
out = []
|
||||||
|
has_format = False
|
||||||
|
|
||||||
|
# This should be "while true". I used this number because it's an upper
|
||||||
|
# bound of the number of iterations.
|
||||||
|
for _ in range(len(arg)):
|
||||||
|
if upto >= len(arg):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Escaping via "{{" and "}}"
|
||||||
|
if arg[upto] in "{}" and upto + 1 < len(arg) and arg[upto + 1] == arg[upto]:
|
||||||
|
out.append(arg[upto])
|
||||||
|
upto += 2
|
||||||
|
elif arg[upto] == "{":
|
||||||
|
chunks = arg[upto + 1:].split("}", 1)
|
||||||
|
if len(chunks) != 2:
|
||||||
|
fail("Unmatched { in %r" % arg)
|
||||||
|
variable = chunks[0]
|
||||||
|
|
||||||
|
if variable not in format:
|
||||||
|
fail('Unknown variable %r in format string %r. Try using cc_args(..., format = {"//path/to:variable": %r})' % (variable, arg, variable))
|
||||||
|
elif has_format:
|
||||||
|
fail("The format string %r contained multiple variables, which is unsupported." % arg)
|
||||||
|
else:
|
||||||
|
used_vars[variable] = None
|
||||||
|
has_format = True
|
||||||
|
out.append(_format_target(format[variable], fail = fail))
|
||||||
|
upto += len(variable) + 2
|
||||||
|
|
||||||
|
elif arg[upto] == "}":
|
||||||
|
fail("Unexpected } in %r" % arg)
|
||||||
|
else:
|
||||||
|
out.append(_escape(arg[upto]))
|
||||||
|
upto += 1
|
||||||
|
|
||||||
|
formatted.append("".join(out))
|
||||||
|
|
||||||
|
unused_vars = [var for var in must_use if var not in used_vars]
|
||||||
|
if unused_vars:
|
||||||
|
fail("The variable %r was not used in the format string." % unused_vars[0])
|
||||||
|
|
||||||
|
return formatted
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
load(
|
load(
|
||||||
"//cc/toolchains/impl:nested_args.bzl",
|
"//cc/toolchains/impl:nested_args.bzl",
|
||||||
"NESTED_ARGS_ATTRS",
|
"NESTED_ARGS_ATTRS",
|
||||||
"args_wrapper_macro",
|
|
||||||
"nested_args_provider_from_ctx",
|
"nested_args_provider_from_ctx",
|
||||||
)
|
)
|
||||||
load(
|
load(
|
||||||
|
@ -42,4 +41,9 @@ Examples:
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_nested_args = lambda **kwargs: args_wrapper_macro(rule = _cc_nested_args, **kwargs)
|
def cc_nested_args(name, format = {}, **kwargs):
|
||||||
|
return _cc_nested_args(
|
||||||
|
name = name,
|
||||||
|
format = {k: v for v, k in format.items()},
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
|
@ -109,5 +109,5 @@ TARGETS = [
|
||||||
# @unsorted-dict-items
|
# @unsorted-dict-items
|
||||||
TESTS = {
|
TESTS = {
|
||||||
"simple_test": _simple_test,
|
"simple_test": _simple_test,
|
||||||
"env_only_test_test": _env_only_test,
|
"env_only_test": _env_only_test,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,11 @@ cc_variable(
|
||||||
type = types.string,
|
type = types.string,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_variable(
|
||||||
|
name = "my_list",
|
||||||
|
type = types.list(types.string),
|
||||||
|
)
|
||||||
|
|
||||||
analysis_test_suite(
|
analysis_test_suite(
|
||||||
name = "test_suite",
|
name = "test_suite",
|
||||||
targets = TARGETS,
|
targets = TARGETS,
|
||||||
|
|
|
@ -13,19 +13,16 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""Tests for the cc_args rule."""
|
"""Tests for the cc_args rule."""
|
||||||
|
|
||||||
|
load("@bazel_skylib//rules/directory:providers.bzl", "DirectoryInfo")
|
||||||
load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value")
|
load("//cc:cc_toolchain_config_lib.bzl", "flag_group", "variable_with_value")
|
||||||
load("//cc/toolchains:cc_toolchain_info.bzl", "VariableInfo")
|
|
||||||
load("//cc/toolchains:format.bzl", "format_arg")
|
|
||||||
load(
|
load(
|
||||||
"//cc/toolchains/impl:nested_args.bzl",
|
"//cc/toolchains/impl:nested_args.bzl",
|
||||||
"FORMAT_ARGS_ERR",
|
"FORMAT_ARGS_ERR",
|
||||||
"REQUIRES_EQUAL_ERR",
|
"REQUIRES_EQUAL_ERR",
|
||||||
"REQUIRES_MUTUALLY_EXCLUSIVE_ERR",
|
"REQUIRES_MUTUALLY_EXCLUSIVE_ERR",
|
||||||
"REQUIRES_NONE_ERR",
|
"REQUIRES_NONE_ERR",
|
||||||
"format_string_indexes",
|
"format_args",
|
||||||
"format_variable",
|
|
||||||
"nested_args_provider",
|
"nested_args_provider",
|
||||||
"raw_string",
|
|
||||||
)
|
)
|
||||||
load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
|
load("//tests/rule_based_toolchain:subjects.bzl", "result_fn_wrapper", "subjects")
|
||||||
|
|
||||||
|
@ -41,83 +38,101 @@ def _expect_that_nested(env, expr = None, **kwargs):
|
||||||
factory = subjects.result(subjects.NestedArgsInfo),
|
factory = subjects.result(subjects.NestedArgsInfo),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _expect_that_formatted(env, var, iterate_over = None, expr = None):
|
def _expect_that_formatted(env, args, format, must_use = [], expr = None):
|
||||||
return env.expect.that_value(
|
return env.expect.that_value(
|
||||||
result_fn_wrapper(format_variable)(var, iterate_over),
|
result_fn_wrapper(format_args)(args, format, must_use = must_use),
|
||||||
factory = subjects.result(subjects.str),
|
|
||||||
expr = expr or "format_variable(var=%r, iterate_over=%r" % (var, iterate_over),
|
|
||||||
)
|
|
||||||
|
|
||||||
def _expect_that_format_string_indexes(env, var, expr = None):
|
|
||||||
return env.expect.that_value(
|
|
||||||
result_fn_wrapper(format_string_indexes)(var),
|
|
||||||
factory = subjects.result(subjects.collection),
|
factory = subjects.result(subjects.collection),
|
||||||
expr = expr or "format_string_indexes(%r)" % var,
|
expr = expr or "format_args(%r, %r)" % (args, format),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _format_string_indexes_test(env, _):
|
def _format_args_test(env, targets):
|
||||||
_expect_that_format_string_indexes(env, "foo").ok().contains_exactly([])
|
|
||||||
_expect_that_format_string_indexes(env, "%%").ok().contains_exactly([])
|
|
||||||
_expect_that_format_string_indexes(env, "%").err().equals(
|
|
||||||
'% should always either of the form %s, or escaped with %%. Instead, got "%"',
|
|
||||||
)
|
|
||||||
_expect_that_format_string_indexes(env, "%a").err().equals(
|
|
||||||
'% should always either of the form %s, or escaped with %%. Instead, got "%a"',
|
|
||||||
)
|
|
||||||
_expect_that_format_string_indexes(env, "%s").ok().contains_exactly([0])
|
|
||||||
_expect_that_format_string_indexes(env, "%%%s%s").ok().contains_exactly([2, 4])
|
|
||||||
_expect_that_format_string_indexes(env, "%%{").ok().contains_exactly([])
|
|
||||||
_expect_that_format_string_indexes(env, "%%s").ok().contains_exactly([])
|
|
||||||
_expect_that_format_string_indexes(env, "%{foo}").err().equals(
|
|
||||||
'Using the old mechanism for variables, %{variable}, but we instead use format_arg("--foo=%s", "//cc/toolchains/variables:<variable>"). Got "%{foo}"',
|
|
||||||
)
|
|
||||||
|
|
||||||
def _formats_raw_strings_test(env, _):
|
|
||||||
_expect_that_formatted(
|
_expect_that_formatted(
|
||||||
env,
|
env,
|
||||||
raw_string("foo"),
|
[
|
||||||
).ok().equals("foo")
|
"a % b",
|
||||||
_expect_that_formatted(
|
"a {{",
|
||||||
env,
|
"}} b",
|
||||||
raw_string("%s"),
|
"a {{ b }}",
|
||||||
).err().contains("Can't use %s with a raw string. Either escape it with %%s or use format_arg")
|
],
|
||||||
|
{},
|
||||||
def _formats_variables_test(env, targets):
|
).ok().contains_exactly([
|
||||||
_expect_that_formatted(
|
"a %% b",
|
||||||
env,
|
"a {",
|
||||||
format_arg("ab %s cd", targets.foo[VariableInfo]),
|
"} b",
|
||||||
).ok().equals("ab %{foo} cd")
|
"a { b }",
|
||||||
|
]).in_order()
|
||||||
|
|
||||||
_expect_that_formatted(
|
_expect_that_formatted(
|
||||||
env,
|
env,
|
||||||
format_arg("foo", targets.foo[VariableInfo]),
|
["{foo"],
|
||||||
).err().equals('format_arg requires a "%s" in the format string, but got "foo"')
|
{},
|
||||||
_expect_that_formatted(
|
).err().equals('Unmatched { in "{foo"')
|
||||||
env,
|
|
||||||
format_arg("%s%s", targets.foo[VariableInfo]),
|
|
||||||
).err().equals('Only one %s can be used in a format string, but got "%s%s"')
|
|
||||||
|
|
||||||
_expect_that_formatted(
|
_expect_that_formatted(
|
||||||
env,
|
env,
|
||||||
format_arg("%s"),
|
["foo}"],
|
||||||
iterate_over = "foo",
|
{},
|
||||||
).ok().equals("%{foo}")
|
).err().equals('Unexpected } in "foo}"')
|
||||||
_expect_that_formatted(
|
_expect_that_formatted(
|
||||||
env,
|
env,
|
||||||
format_arg("%s"),
|
["{foo}"],
|
||||||
).err().contains("format_arg requires either a variable to format, or iterate_over must be provided")
|
{},
|
||||||
|
).err().contains('Unknown variable "foo" in format string "{foo}"')
|
||||||
|
|
||||||
def _iterate_over_test(env, _):
|
_expect_that_formatted(
|
||||||
|
env,
|
||||||
|
[
|
||||||
|
"a {var}",
|
||||||
|
"b {directory}",
|
||||||
|
"c {file}",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"directory": targets.directory,
|
||||||
|
"file": targets.bin_wrapper,
|
||||||
|
"var": targets.foo,
|
||||||
|
},
|
||||||
|
).ok().contains_exactly([
|
||||||
|
"a %{foo}",
|
||||||
|
"b " + targets.directory[DirectoryInfo].path,
|
||||||
|
"c " + targets.bin_wrapper[DefaultInfo].files.to_list()[0].path,
|
||||||
|
]).in_order()
|
||||||
|
|
||||||
|
_expect_that_formatted(
|
||||||
|
env,
|
||||||
|
["{var}", "{var}"],
|
||||||
|
{"var": targets.foo},
|
||||||
|
).ok().contains_exactly(["%{foo}", "%{foo}"])
|
||||||
|
|
||||||
|
_expect_that_formatted(
|
||||||
|
env,
|
||||||
|
[],
|
||||||
|
{"var": targets.foo},
|
||||||
|
must_use = ["var"],
|
||||||
|
).err().contains('"var" was not used')
|
||||||
|
|
||||||
|
_expect_that_formatted(
|
||||||
|
env,
|
||||||
|
["{var} {var}"],
|
||||||
|
{"var": targets.foo},
|
||||||
|
).err().contains('"{var} {var}" contained multiple variables')
|
||||||
|
|
||||||
|
_expect_that_formatted(
|
||||||
|
env,
|
||||||
|
["{foo} {bar}"],
|
||||||
|
{"foo": targets.foo, "bar": targets.foo},
|
||||||
|
).err().contains('"{foo} {bar}" contained multiple variables')
|
||||||
|
|
||||||
|
def _iterate_over_test(env, targets):
|
||||||
inner = _expect_that_nested(
|
inner = _expect_that_nested(
|
||||||
env,
|
env,
|
||||||
args = [raw_string("--foo")],
|
args = ["--foo"],
|
||||||
).ok().actual
|
).ok().actual
|
||||||
env.expect.that_str(inner.legacy_flag_group).equals(flag_group(flags = ["--foo"]))
|
env.expect.that_str(inner.legacy_flag_group).equals(flag_group(flags = ["--foo"]))
|
||||||
|
|
||||||
nested = _expect_that_nested(
|
nested = _expect_that_nested(
|
||||||
env,
|
env,
|
||||||
nested = [inner],
|
nested = [inner],
|
||||||
iterate_over = "my_list",
|
iterate_over = targets.my_list,
|
||||||
).ok()
|
).ok()
|
||||||
nested.iterate_over().some().equals("my_list")
|
nested.iterate_over().some().equals("my_list")
|
||||||
nested.legacy_flag_group().equals(flag_group(
|
nested.legacy_flag_group().equals(flag_group(
|
||||||
|
@ -131,14 +146,14 @@ def _requires_types_test(env, targets):
|
||||||
env,
|
env,
|
||||||
requires_not_none = "abc",
|
requires_not_none = "abc",
|
||||||
requires_none = "def",
|
requires_none = "def",
|
||||||
args = [raw_string("--foo")],
|
args = ["--foo"],
|
||||||
expr = "mutually_exclusive",
|
expr = "mutually_exclusive",
|
||||||
).err().equals(REQUIRES_MUTUALLY_EXCLUSIVE_ERR)
|
).err().equals(REQUIRES_MUTUALLY_EXCLUSIVE_ERR)
|
||||||
|
|
||||||
_expect_that_nested(
|
_expect_that_nested(
|
||||||
env,
|
env,
|
||||||
requires_none = "var",
|
requires_none = "var",
|
||||||
args = [raw_string("--foo")],
|
args = ["--foo"],
|
||||||
expr = "requires_none",
|
expr = "requires_none",
|
||||||
).ok().requires_types().contains_exactly(
|
).ok().requires_types().contains_exactly(
|
||||||
{"var": [struct(
|
{"var": [struct(
|
||||||
|
@ -150,13 +165,8 @@ def _requires_types_test(env, targets):
|
||||||
|
|
||||||
_expect_that_nested(
|
_expect_that_nested(
|
||||||
env,
|
env,
|
||||||
args = [raw_string("foo %s baz")],
|
args = ["foo {foo} baz"],
|
||||||
expr = "no_variable",
|
format = {targets.foo: "foo"},
|
||||||
).err().contains("Can't use %s with a raw string")
|
|
||||||
|
|
||||||
_expect_that_nested(
|
|
||||||
env,
|
|
||||||
args = [format_arg("foo %s baz", targets.foo[VariableInfo])],
|
|
||||||
expr = "type_validation",
|
expr = "type_validation",
|
||||||
).ok().requires_types().contains_exactly(
|
).ok().requires_types().contains_exactly(
|
||||||
{"foo": [struct(
|
{"foo": [struct(
|
||||||
|
@ -170,7 +180,8 @@ def _requires_types_test(env, targets):
|
||||||
env,
|
env,
|
||||||
requires_equal = "foo",
|
requires_equal = "foo",
|
||||||
requires_equal_value = "value",
|
requires_equal_value = "value",
|
||||||
args = [format_arg("--foo=%s", targets.foo[VariableInfo])],
|
args = ["--foo={foo}"],
|
||||||
|
format = {targets.foo: "foo"},
|
||||||
expr = "type_and_requires_equal_validation",
|
expr = "type_and_requires_equal_validation",
|
||||||
).ok()
|
).ok()
|
||||||
nested.requires_types().contains_exactly(
|
nested.requires_types().contains_exactly(
|
||||||
|
@ -194,12 +205,13 @@ def _requires_types_test(env, targets):
|
||||||
|
|
||||||
TARGETS = [
|
TARGETS = [
|
||||||
":foo",
|
":foo",
|
||||||
|
":my_list",
|
||||||
|
"//tests/rule_based_toolchain/testdata:directory",
|
||||||
|
"//tests/rule_based_toolchain/testdata:bin_wrapper",
|
||||||
]
|
]
|
||||||
|
|
||||||
TESTS = {
|
TESTS = {
|
||||||
"format_string_indexes_test": _format_string_indexes_test,
|
"format_args_test": _format_args_test,
|
||||||
"formats_raw_strings_test": _formats_raw_strings_test,
|
|
||||||
"formats_variables_test": _formats_variables_test,
|
|
||||||
"iterate_over_test": _iterate_over_test,
|
"iterate_over_test": _iterate_over_test,
|
||||||
"requires_types_test": _requires_types_test,
|
"requires_types_test": _requires_types_test,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
load("//cc/toolchains:format.bzl", "format_arg")
|
|
||||||
load("//cc/toolchains:nested_args.bzl", "cc_nested_args")
|
load("//cc/toolchains:nested_args.bzl", "cc_nested_args")
|
||||||
load("//cc/toolchains/impl:variables.bzl", "cc_builtin_variables", "cc_variable", "types")
|
load("//cc/toolchains/impl:variables.bzl", "cc_builtin_variables", "cc_variable", "types")
|
||||||
load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
|
load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
|
||||||
|
@ -56,17 +55,19 @@ alias(
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "simple_str",
|
name = "simple_str",
|
||||||
args = [format_arg("%s", ":str")],
|
args = ["{str}"],
|
||||||
|
format = {"str": ":str"},
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "list_not_allowed",
|
name = "list_not_allowed",
|
||||||
args = [format_arg("%s", ":str_list")],
|
args = ["{s}"],
|
||||||
|
format = {"s": ":str_list"},
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "iterate_over_list",
|
name = "iterate_over_list",
|
||||||
args = [format_arg("%s")],
|
args = ["{}"],
|
||||||
iterate_over = ":str_list",
|
iterate_over = ":str_list",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ cc_nested_args(
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "inner_iter",
|
name = "inner_iter",
|
||||||
args = [format_arg("%s")],
|
args = ["{}"],
|
||||||
iterate_over = ":struct_list.nested_str_list",
|
iterate_over = ":struct_list.nested_str_list",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -103,7 +104,8 @@ cc_nested_args(
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "bad_inner_iter",
|
name = "bad_inner_iter",
|
||||||
args = [format_arg("%s", ":struct_list.nested_str_list")],
|
args = ["{s}"],
|
||||||
|
format = {"s": ":struct_list.nested_str_list"},
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
|
@ -114,12 +116,14 @@ cc_nested_args(
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "bad_nested_optional",
|
name = "bad_nested_optional",
|
||||||
args = [format_arg("%s", ":str_option")],
|
args = ["{s}"],
|
||||||
|
format = {"s": ":str_option"},
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_nested_args(
|
cc_nested_args(
|
||||||
name = "good_nested_optional",
|
name = "good_nested_optional",
|
||||||
args = [format_arg("%s", ":str_option")],
|
args = ["{s}"],
|
||||||
|
format = {"s": ":str_option"},
|
||||||
requires_not_none = ":str_option",
|
requires_not_none = ":str_option",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -141,6 +145,13 @@ cc_builtin_variables(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_builtin_variables(
|
||||||
|
name = "nested_variables",
|
||||||
|
srcs = [
|
||||||
|
":struct_list.nested_str_list",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
analysis_test_suite(
|
analysis_test_suite(
|
||||||
name = "test_suite",
|
name = "test_suite",
|
||||||
targets = TARGETS,
|
targets = TARGETS,
|
||||||
|
|
Loading…
Reference in New Issue