BEGIN_PUBLIC

Implement cc_args.

Also change cc_flag_set / cc_flag_group to cc_args / cc_arg_group. This is to lean into the idea that this is roughly equivalent to ctx.actions.args()
END_PUBLIC

PiperOrigin-RevId: 608804069
Change-Id: I74ea883b14219f904aaafc4eab902b96a1fb3e3d
This commit is contained in:
Googler 2024-02-20 17:58:00 -08:00 committed by Copybara-Service
parent 2e780ceda9
commit 35fe45e91b
14 changed files with 376 additions and 59 deletions

View File

@ -36,7 +36,7 @@ sh_binary(
An action config is a mapping from action to: An action config is a mapping from action to:
* A list of tools, (the first one matching the execution requirements is used). * A list of tools, (the first one matching the execution requirements is used).
* A list of flags and features that are always enabled for the action * A list of args and features that are always enabled for the action
* A set of additional files required for the action * A set of additional files required for the action
Each action can only be specified once in the toolchain. Specifying multiple Each action can only be specified once in the toolchain. Specifying multiple
@ -62,7 +62,7 @@ cc_action_config(
name = "c_compile", name = "c_compile",
actions = ["@rules_cc//actions:all_c_compile"], actions = ["@rules_cc//actions:all_c_compile"],
tools = ["@sysroot//:clang"], tools = ["@sysroot//:clang"],
flag_sets = [":my_flag_set"], args = [":my_args"],
implies = [":my_feature"], implies = [":my_feature"],
additional_files = ["@sysroot//:all_header_files"], additional_files = ["@sysroot//:all_header_files"],
) )
@ -74,57 +74,109 @@ cc_additional_files_for_actions(
) )
``` ```
## Step 3: Define some flag sets ## Step 3: Define some arguments
Flag sets are just sets of flags to be associated with actions. Most flag sets Arguments are our replacement for `flag_set` and `env_set`. To add arguments to
are simple, so we provide the shorthand `flags`. However, sometimes you our tools, we take heavy inspiration from bazel's
need to do more complex things, for which we support `flag_groups` instead. [`Args`](https://bazel.build/rules/lib/builtins/Args) type. We provide the same
API, with the following caveats:
Flag groups work exactly the same as the existing toolchain definition. * `actions` specifies which actions the arguments apply to (same as `flag_set`).
* `requires_any_of` is equivalent to `with_features` on the `flag_set`.
Flag sets are a combination of both `flag_set` and `env_set` from the existing * `args` may be used instead of `add` if your command-line is only strings.
toolchain definition. * `env` may be used to add environment variables to the arguments. Environment
variables set by later args take priority.
`cc_flag_set_list` is simply a list of flag sets. This can be used to group * By default, all inputs are automatically added to the corresponding actions.
flag sets together, and preserves ordering. `additional_files` specifies files that are required for an action when using
that argument.
``` ```
cc_flag_set( cc_args(
name = "simple", name = "inline",
actions = ["@rules_cc//actions:all_cpp_compile_actions"], actions = ["@rules_cc//actions:all_cpp_compile_actions"],
flags = ["--foo"], args = ["--foo"],
envs = {"FOO": "bar"}, requires_any_of = [":feature"]
env = {"FOO": "bar"},
additional_files = [":file"],
)
```
For more complex use cases, we use the same API as `Args`. Values are either:
* A list of files (or a single file for `cc_add_args`).
* Something returning `CcVariableInfo`, which is equivalent to a list of strings.
```
cc_variable(
name = "bar_baz",
values = ["bar", "baz"],
) )
cc_flag_group( # Expands to CcVariableInfo(values = ["x86_64-unknown-linux-gnu"])
name = "complex_flag_group", custom_variable_rule(
# API TBD name = "triple",
...
) )
cc_flag_set(
# Taken from https://bazel.build/rules/lib/builtins/Args#add
cc_add_args(
name = "single",
arg_name = "--platform",
value = ":triple", # Either a single file or a cc_variable
format = "%s",
)
# Taken from https://bazel.build/rules/lib/builtins/Args#add_all
cc_add_args_all(
name = "multiple",
arg_name = "--foo",
values = [":file", ":file_set"], # Either files or cc_variable.
# map_each not supported. Write a custom rule if you want that.
format_each = "%s",
before_each = "--foo",
omit_if_empty = True,
uniquify = False,
# Expand_directories not yet supported.
terminate_with = "foo",
)
# Taken from https://bazel.build/rules/lib/builtins/Args#add_joined
cc_add_args_joined(
name = "joined",
arg_name = "--foo",
values = [":file", ":file_set"], # Either files or cc_variable.
join_with = ",",
# map_each not supported. Write a custom rule if you want that.
format_each = "%s",
format_joined = "--foo=%s",
omit_if_empty = True,
uniquify = False,
# Expand_directories not yet supported.
)
cc_args(
name = "complex", name = "complex",
actions = ["@rules_cc//actions:c_compile"], actions = ["@rules_cc//actions:c_compile"],
flag_groups = [":complex_flag_group"], add = [":single", ":multiple", ":joined"],
) )
cc_flag_set_list( cc_args_list(
name = "all_flags", name = "all_flags",
flag_sets = [":simple", ":complex"], args = [":inline", ":complex"],
) )
``` ```
## Step 4: Define some features ## Step 4: Define some features
A feature is a set of flags and configurations that can be enabled or disabled. A feature is a set of args and configurations that can be enabled or disabled.
Although the existing toolchain recommends using features to avoid duplication Although the existing toolchain recommends using features to avoid duplication
of definitions, we recommend avoiding using features unless you want the user to of definitions, we recommend avoiding using features unless you want the user to
be able to enable / disable the feature themselves. This is because we provide be able to enable / disable the feature themselves. This is because we provide
alternatives such as `cc_flag_set_list` to allow combining flag sets and alternatives such as `cc_args_list` to allow combining arguments and
specifying them on each action in the action config. specifying them on each action in the action config.
``` ```
cc_feature( cc_feature(
name = "my_feature", name = "my_feature",
feature_name = "my_feature", feature_name = "my_feature",
flag_sets = [":all_flags"], args = [":all_args"],
implies = [":other_feature"], implies = [":other_feature"],
) )
``` ```
@ -143,7 +195,7 @@ The `cc_toolchain` macro:
cc_toolchain( cc_toolchain(
name = "toolchain", name = "toolchain",
features = [":my_feature"] features = [":my_feature"]
unconditional_flag_sets = [":all_warnings"], unconditional_args = [":all_warnings"],
action_configs = [":c_compile"], action_configs = [":c_compile"],
additional_files = [":all_action_files"], additional_files = [":all_action_files"],
) )
@ -187,7 +239,7 @@ def cc_legacy_features(name, features):
# Build file # Build file
FOO = feature(name = "foo", flag_sets=[flag_group(...)]) FOO = feature(name = "foo", args=[arg_group(...)])
FEATURES = [FOO] FEATURES = [FOO]
cc_legacy_features(name = "legacy_features", features = FEATURES) cc_legacy_features(name = "legacy_features", features = FEATURES)
@ -240,7 +292,7 @@ Feature requirements can come in two formats.
For example: For example:
* Features can require some subset of features to be enabled. * Features can require some subset of features to be enabled.
* Flag sets can require some subset of features to be enabled, but others to be * Arguments can require some subset of features to be enabled, but others to be
disabled. disabled.
This is very confusing for toolchain authors, so we will simplify things with This is very confusing for toolchain authors, so we will simplify things with
@ -265,7 +317,7 @@ cc_feature_constraint(
none_of = [":my_other_feature"], none_of = [":my_other_feature"],
) )
cc_flag_set( cc_args(
name = "foo", name = "foo",
# All of these provide with_feature. # All of these provide with_feature.
requires_any_of = [":my_feature", ":my_feature_set", ":my_feature_constraint"] requires_any_of = [":my_feature", ":my_feature_set", ":my_feature_constraint"]

View File

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
"""Rules to turn action types into bazel labels.""" """Rules to turn action types into bazel labels."""
load("//cc/toolchains/impl:collect.bzl", "collect_action_types")
load(":cc_toolchain_info.bzl", "ActionTypeInfo", "ActionTypeSetInfo") load(":cc_toolchain_info.bzl", "ActionTypeInfo", "ActionTypeSetInfo")
visibility("public") visibility("public")
@ -51,10 +52,7 @@ cc_action_type(
def _cc_action_type_set_impl(ctx): def _cc_action_type_set_impl(ctx):
return [ActionTypeSetInfo( return [ActionTypeSetInfo(
label = ctx.label, label = ctx.label,
actions = depset(transitive = [ actions = collect_action_types(ctx.attr.actions),
attr[ActionTypeSetInfo].actions
for attr in ctx.attr.actions
]),
)] )]
cc_action_type_set = rule( cc_action_type_set = rule(

105
cc/toolchains/args.bzl Normal file
View File

@ -0,0 +1,105 @@
# 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.
"""All providers for rule-based bazel toolchain config."""
load(
"//cc/toolchains/impl:collect.bzl",
"collect_action_types",
"collect_files",
"collect_provider",
)
load(
":cc_toolchain_info.bzl",
"ActionTypeSetInfo",
"AddArgsInfo",
"ArgsInfo",
"ArgsListInfo",
"FeatureConstraintInfo",
)
visibility("public")
def _cc_args_impl(ctx):
add_args = [AddArgsInfo(
label = ctx.label,
args = tuple(ctx.attr.args),
files = depset([]),
)]
actions = collect_action_types(ctx.attr.actions)
files = collect_files(ctx.attr.additional_files)
requires = collect_provider(ctx.attr.requires_any_of, FeatureConstraintInfo)
args = ArgsInfo(
label = ctx.label,
actions = actions,
requires_any_of = tuple(requires),
files = files,
args = add_args,
env = ctx.attr.env,
)
return [
args,
ArgsListInfo(label = ctx.label, args = tuple([args])),
]
cc_args = rule(
implementation = _cc_args_impl,
attrs = {
"actions": attr.label_list(
providers = [ActionTypeSetInfo],
mandatory = True,
doc = """A list of action types that this flag set applies to.
See @rules_cc//cc/toolchains/actions:all for valid options.
""",
),
"additional_files": attr.label_list(
allow_files = True,
doc = """Files required to add this argument to the command-line.
For example, a flag that sets the header directory might add the headers in that
directory as additional files.
""",
),
"args": attr.string_list(
doc = """Arguments that should be added to the command-line.
These are evaluated in order, with earlier args appearing earlier in the
invocation of the underlying tool.
""",
),
"env": attr.string_dict(
doc = "Environment variables to be added to the command-line.",
),
"requires_any_of": attr.label_list(
providers = [FeatureConstraintInfo],
doc = """This will be enabled when any of the constraints are met.
If omitted, this flag set will be enabled unconditionally.
""",
),
},
provides = [ArgsInfo],
doc = """Declares a list of arguments bound to a set of actions.
Roughly equivalent to ctx.actions.args()
Examples:
cc_args(
name = "warnings_as_errors",
args = ["-Werror"],
)
""",
)

View File

@ -19,7 +19,7 @@
# decide to just require users to use the public user-facing rules. # decide to just require users to use the public user-facing rules.
visibility([ visibility([
"//cc/toolchains/...", "//cc/toolchains/...",
"//tests/...", "//tests/rule_based_toolchain/...",
]) ])
# Note that throughout this file, we never use a list. This is because mutable # Note that throughout this file, we never use a list. This is because mutable
@ -29,6 +29,7 @@ visibility([
ActionTypeInfo = provider( ActionTypeInfo = provider(
doc = "A type of action (eg. c-compile, c++-link-executable)", doc = "A type of action (eg. c-compile, c++-link-executable)",
# @unsorted-dict-items
fields = { fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"name": "(str) The action name, as defined by action_names.bzl", "name": "(str) The action name, as defined by action_names.bzl",
@ -44,23 +45,34 @@ ActionTypeSetInfo = provider(
}, },
) )
FlagGroupInfo = provider( AddArgsInfo = provider(
doc = "A group of flags", doc = "A provider representation of Args.add/add_all/add_joined parameters",
# @unsorted-dict-items # @unsorted-dict-items
fields = { fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"flags": "(Sequence[str]) A list of flags to add to the command-line", "args": "(Sequence[str]) The command-line arguments to add",
"files": "(depset[File]) The files required to use this variable",
}, },
) )
FlagSetInfo = provider( ArgsInfo = provider(
doc = "A set of flags to be expanded in the command line for specific actions", doc = "A set of arguments to be added to the command line for specific actions",
# @unsorted-dict-items # @unsorted-dict-items
fields = { fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"actions": "(depset[ActionTypeInfo]) The set of actions this is associated with", "actions": "(depset[ActionTypeInfo]) The set of actions this is associated with",
"requires_any_of": "(Sequence[FeatureConstraintInfo]) This will be enabled if any of the listed predicates are met. Equivalent to with_features", "requires_any_of": "(Sequence[FeatureConstraintInfo]) This will be enabled if any of the listed predicates are met. Equivalent to with_features",
"flag_groups": "(Sequence[FlagGroupInfo]) Set of flag groups to include.", "args": "(Sequence[AddArgsInfo]) The command-line arguments to add.",
"files": "(depset[File]) Files required for the args",
"env": "(dict[str, str]) Environment variables to apply",
},
)
ArgsListInfo = provider(
doc = "A ordered list of arguments",
# @unsorted-dict-items
fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"args": "(Sequence[ArgsInfo]) The flag sets contained within",
}, },
) )
@ -71,7 +83,7 @@ FeatureInfo = provider(
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"name": "(str) The name of the feature", "name": "(str) The name of the feature",
"enabled": "(bool) Whether this feature is enabled by default", "enabled": "(bool) Whether this feature is enabled by default",
"flag_sets": "(depset[FlagSetInfo]) Flag sets enabled by this feature", "args": "(Sequence[ArgsInfo]) Flag sets enabled by this feature",
"implies": "(depset[FeatureInfo]) Set of features implied by this feature", "implies": "(depset[FeatureInfo]) Set of features implied by this feature",
"requires_any_of": "(Sequence[FeatureSetInfo]) A list of feature sets, at least one of which is required to enable this feature. This is semantically equivalent to the requires attribute of rules_cc's FeatureInfo", "requires_any_of": "(Sequence[FeatureSetInfo]) A list of feature sets, at least one of which is required to enable this feature. This is semantically equivalent to the requires attribute of rules_cc's FeatureInfo",
"provides": "(Sequence[MutuallyExclusiveCategoryInfo]) Indicates that this feature is one of several mutually exclusive alternate features.", "provides": "(Sequence[MutuallyExclusiveCategoryInfo]) Indicates that this feature is one of several mutually exclusive alternate features.",
@ -100,6 +112,7 @@ FeatureConstraintInfo = provider(
MutuallyExclusiveCategoryInfo = provider( MutuallyExclusiveCategoryInfo = provider(
doc = "Multiple features with the category will be mutally exclusive", doc = "Multiple features with the category will be mutally exclusive",
# @unsorted-dict-items
fields = { fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging", "label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"name": "(str) The name of the category", "name": "(str) The name of the category",
@ -126,7 +139,7 @@ ActionConfigInfo = provider(
"action": "(ActionTypeInfo) The name of the action", "action": "(ActionTypeInfo) The name of the action",
"enabled": "(bool) If True, this action is enabled unless a rule type explicitly marks it as unsupported", "enabled": "(bool) If True, this action is enabled unless a rule type explicitly marks it as unsupported",
"tools": "(Sequence[ToolInfo]) The tool applied to the action will be the first tool in the sequence with a feature set that matches the feature configuration", "tools": "(Sequence[ToolInfo]) The tool applied to the action will be the first tool in the sequence with a feature set that matches the feature configuration",
"flag_sets": "(Sequence[FlagSetInfo]) Set of flag sets the action sets", "args": "(Sequence[ArgsInfo]) Set of flag sets the action sets",
"implies": "(depset[FeatureInfo]) Set of features implied by this action config", "implies": "(depset[FeatureInfo]) Set of features implied by this action config",
"files": "(depset[File]) The files required to run these actions", "files": "(depset[File]) The files required to run these actions",
}, },

6
cc/toolchains/impl/BUILD Normal file
View File

@ -0,0 +1,6 @@
# This directory contains implementations of starlark functions that contain
# complex logic. The objective is to keep the rules themselves as simple as
# possible, so that we can perform very thorough testing on the implementation.
# I wanted to call it private / internal, but then buildifier complains about
# referencing it from the tests directory.

View File

@ -0,0 +1,55 @@
# 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.
"""Helper functions to allow us to collect data from attr.label_list."""
load(
"//cc/toolchains:cc_toolchain_info.bzl",
"ActionTypeSetInfo",
)
visibility("//cc/toolchains/...")
def collect_provider(targets, provider):
"""Collects providers from a label list.
Args:
targets: (list[Target]) An attribute from attr.label_list
provider: (provider) The provider to look up
Returns:
A list of the providers
"""
return [target[provider] for target in targets]
def collect_defaultinfo(targets):
"""Collects DefaultInfo from a label list.
Args:
targets: (list[Target]) An attribute from attr.label_list
Returns:
A list of the associated defaultinfo
"""
return collect_provider(targets, DefaultInfo)
def _make_collector(provider, field):
def collector(targets, direct = [], transitive = []):
# Avoid mutating what was passed in.
transitive = transitive[:]
for value in collect_provider(targets, provider):
transitive.append(getattr(value, field))
return depset(direct = direct, transitive = transitive)
return collector
collect_action_types = _make_collector(ActionTypeSetInfo, "actions")
collect_files = _make_collector(DefaultInfo, "files")

View File

@ -0,0 +1,25 @@
load("@rules_testing//lib:util.bzl", "util")
load("//cc/toolchains:args.bzl", "cc_args")
load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
load(":args_test.bzl", "TARGETS", "TESTS")
util.helper_target(
cc_args,
name = "simple",
actions = ["//tests/rule_based_toolchain/actions:all_compile"],
additional_files = [
"//tests/rule_based_toolchain/testdata:file1",
"//tests/rule_based_toolchain/testdata:multiple",
],
args = [
"--foo",
"foo",
],
env = {"BAR": "bar"},
)
analysis_test_suite(
name = "test_suite",
targets = TARGETS,
tests = TESTS,
)

View File

@ -0,0 +1,45 @@
# 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.
"""Tests for the cc_args rule."""
load(
"//cc/toolchains:cc_toolchain_info.bzl",
"ArgsInfo",
)
visibility("//tests/rule_based_toolchain/...")
def _test_simple_args_impl(env, targets):
simple = env.expect.that_target(targets.simple).provider(ArgsInfo)
simple.actions().contains_exactly([
targets.c_compile.label,
targets.cpp_compile.label,
])
simple.args().contains_exactly([targets.simple.label])
simple.env().contains_exactly({"BAR": "bar"})
simple.files().contains_exactly([
"tests/rule_based_toolchain/testdata/file1",
"tests/rule_based_toolchain/testdata/multiple1",
"tests/rule_based_toolchain/testdata/multiple2",
])
TARGETS = [
":simple",
"//tests/rule_based_toolchain/actions:c_compile",
"//tests/rule_based_toolchain/actions:cpp_compile",
]
TESTS = {
"simple_test": _test_simple_args_impl,
}

View File

@ -20,11 +20,11 @@ load(
"ActionConfigSetInfo", "ActionConfigSetInfo",
"ActionTypeInfo", "ActionTypeInfo",
"ActionTypeSetInfo", "ActionTypeSetInfo",
"AddArgsInfo",
"ArgsInfo",
"FeatureConstraintInfo", "FeatureConstraintInfo",
"FeatureInfo", "FeatureInfo",
"FeatureSetInfo", "FeatureSetInfo",
"FlagGroupInfo",
"FlagSetInfo",
"MutuallyExclusiveCategoryInfo", "MutuallyExclusiveCategoryInfo",
"ToolInfo", "ToolInfo",
) )
@ -94,22 +94,24 @@ _FeatureConstraintFactory = generate_factory(
) )
# buildifier: disable=name-conventions # buildifier: disable=name-conventions
_FlagGroupFactory = generate_factory( _AddArgsFactory = generate_factory(
FlagGroupInfo, AddArgsInfo,
"FlagGroupInfo", "AddArgsInfo",
dict( dict(
flags = subjects.collection, args = subjects.collection,
files = subjects.depset_file,
), ),
) )
# buildifier: disable=name-conventions # buildifier: disable=name-conventions
_FlagSetFactory = generate_factory( _ArgsFactory = generate_factory(
FlagSetInfo, ArgsInfo,
"FlagSetInfo", "ArgsInfo",
dict( dict(
actions = ProviderDepset(_ActionTypeFactory), actions = ProviderDepset(_ActionTypeFactory),
# Use a collection here because we don't want to args = ProviderSequence(_AddArgsFactory),
flag_groups = subjects.collection, env = subjects.dict,
files = subjects.depset_file,
requires_any_of = ProviderSequence(_FeatureConstraintFactory), requires_any_of = ProviderSequence(_FeatureConstraintFactory),
), ),
) )
@ -144,7 +146,7 @@ _ActionConfigFactory = generate_factory(
action = _ActionTypeFactory, action = _ActionTypeFactory,
enabled = subjects.bool, enabled = subjects.bool,
tools = ProviderSequence(_ToolFactory), tools = ProviderSequence(_ToolFactory),
flag_sets = ProviderSequence(_FlagSetFactory), flag_sets = ProviderSequence(_ArgsFactory),
implies = ProviderDepset(_FeatureFactory), implies = ProviderDepset(_FeatureFactory),
files = subjects.depset_file, files = subjects.depset_file,
), ),
@ -174,8 +176,8 @@ _ActionConfigSetFactory = struct(
FACTORIES = [ FACTORIES = [
_ActionTypeFactory, _ActionTypeFactory,
_ActionTypeSetFactory, _ActionTypeSetFactory,
_FlagGroupFactory, _AddArgsFactory,
_FlagSetFactory, _ArgsFactory,
_MutuallyExclusiveCategoryFactory, _MutuallyExclusiveCategoryFactory,
_FeatureFactory, _FeatureFactory,
_FeatureConstraintFactory, _FeatureConstraintFactory,

View File

@ -0,0 +1,16 @@
package(default_visibility = ["//tests/rule_based_toolchain:__subpackages__"])
exports_files(
glob(
["*"],
exclude = ["BUILD"],
),
)
filegroup(
name = "multiple",
srcs = [
"multiple1",
"multiple2",
],
)

View File

View File

View File

View File