diff --git a/cc/toolchains/capabilities/BUILD b/cc/toolchains/capabilities/BUILD index 8c55804..645ab00 100644 --- a/cc/toolchains/capabilities/BUILD +++ b/cc/toolchains/capabilities/BUILD @@ -1,3 +1,4 @@ +load("//cc/toolchains:target_capability.bzl", "cc_target_capability") load("//cc/toolchains:tool_capability.bzl", "cc_tool_capability") package(default_visibility = ["//visibility:public"]) @@ -10,10 +11,10 @@ cc_tool_capability( name = "supports_interface_shared_libraries", ) -cc_tool_capability( +cc_target_capability( name = "supports_dynamic_linker", ) -cc_tool_capability( +cc_target_capability( name = "supports_pic", ) diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl index 17881a8..2359c83 100644 --- a/cc/toolchains/cc_toolchain_info.bzl +++ b/cc/toolchains/cc_toolchain_info.bzl @@ -119,6 +119,11 @@ FeatureInfo = provider( "allowlist_include_directories": "(depset[DirectoryInfo]) Include directories implied by this feature that should be allowlisted in Bazel's include checker", }, ) + +FeatureImplyabilityInfo = provider( + doc = "Provider used to constrain which kinds of features can be implied", +) + FeatureSetInfo = provider( doc = "A set of features", # @unsorted-dict-items @@ -161,7 +166,16 @@ ToolInfo = provider( ) ToolCapabilityInfo = provider( - doc = "A capability associated with a tool (eg. supports_pic).", + doc = "A capability associated with a tool (eg. supports_start_end_lib).", + # @unsorted-dict-items + fields = { + "label": "(Label) The label defining this provider. Place in error messages to simplify debugging", + "feature": "(FeatureInfo) The feature this capability defines", + }, +) + +TargetCapabilityInfo = provider( + doc = "A capability associated with a target (eg. supports_pic).", # @unsorted-dict-items fields = { "label": "(Label) The label defining this provider. Place in error messages to simplify debugging", diff --git a/cc/toolchains/feature.bzl b/cc/toolchains/feature.bzl index f0acbfe..95dbd14 100644 --- a/cc/toolchains/feature.bzl +++ b/cc/toolchains/feature.bzl @@ -23,6 +23,7 @@ load( ":cc_toolchain_info.bzl", "ArgsListInfo", "FeatureConstraintInfo", + "FeatureImplyabilityInfo", "FeatureInfo", "FeatureSetInfo", "MutuallyExclusiveCategoryInfo", @@ -87,6 +88,7 @@ def _cc_feature_impl(ctx): return [ feature, + FeatureImplyabilityInfo(), FeatureSetInfo(label = ctx.label, features = depset([feature])), FeatureConstraintInfo( label = ctx.label, @@ -144,7 +146,7 @@ be enabled. providers = [FeatureSetInfo], ), "implies": attr.label_list( - providers = [FeatureSetInfo], + providers = [FeatureSetInfo, FeatureImplyabilityInfo], doc = """List of features enabled along with this feature. Warning: If any of the features cannot be enabled, this feature is @@ -186,6 +188,7 @@ cc_feature( ), }, provides = [ + FeatureImplyabilityInfo, FeatureInfo, FeatureSetInfo, FeatureConstraintInfo, diff --git a/cc/toolchains/impl/documented_api.bzl b/cc/toolchains/impl/documented_api.bzl index e6cfa99..2e624ab 100644 --- a/cc/toolchains/impl/documented_api.bzl +++ b/cc/toolchains/impl/documented_api.bzl @@ -21,6 +21,7 @@ load("//cc/toolchains:feature_constraint.bzl", _cc_feature_constraint = "cc_feat load("//cc/toolchains:feature_set.bzl", _cc_feature_set = "cc_feature_set") load("//cc/toolchains:mutually_exclusive_category.bzl", _cc_mutually_exclusive_category = "cc_mutually_exclusive_category") load("//cc/toolchains:nested_args.bzl", _cc_nested_args = "cc_nested_args") +load("//cc/toolchains:target_capability.bzl", _cc_target_capability = "cc_target_capability") load("//cc/toolchains:tool.bzl", _cc_tool = "cc_tool") load("//cc/toolchains:tool_capability.bzl", _cc_tool_capability = "cc_tool_capability") load("//cc/toolchains:tool_map.bzl", _cc_tool_map = "cc_tool_map") @@ -28,6 +29,7 @@ load("//cc/toolchains:toolchain.bzl", _cc_toolchain = "cc_toolchain") load("//cc/toolchains/impl:external_feature.bzl", _cc_external_feature = "cc_external_feature") load("//cc/toolchains/impl:variables.bzl", _cc_variable = "cc_variable") +cc_target_capability = _cc_target_capability cc_tool_map = _cc_tool_map cc_tool = _cc_tool cc_tool_capability = _cc_tool_capability @@ -48,6 +50,7 @@ cc_toolchain = _cc_toolchain # links in the generated documentation so that maintainers don't need to manually # ensure every reference to a rule is properly linked. DOCUMENTED_TOOLCHAIN_RULES = [ + "cc_target_capability", "cc_tool_map", "cc_tool", "cc_tool_capability", diff --git a/cc/toolchains/target_capability.bzl b/cc/toolchains/target_capability.bzl new file mode 100644 index 0000000..9a1f576 --- /dev/null +++ b/cc/toolchains/target_capability.bzl @@ -0,0 +1,92 @@ +# 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. +"""Implementation of the cc_target_capability rule.""" + +load( + ":cc_toolchain_info.bzl", + "ArgsListInfo", + "FeatureConstraintInfo", + "FeatureInfo", + "FeatureSetInfo", + "TargetCapabilityInfo", +) + +def _cc_target_capability_impl(ctx): + ft = FeatureInfo( + name = ctx.attr.feature_name or ctx.label.name, + label = ctx.label, + enabled = False, + args = ArgsListInfo( + label = ctx.label, + args = (), + files = depset(), + by_action = (), + allowlist_include_directories = depset(), + ), + implies = depset(), + requires_any_of = (), + mutually_exclusive = (), + external = False, + overridable = True, + overrides = None, + allowlist_include_directories = depset(), + ) + + # Intentionally does not provide FeatureImplyabilityInfo to prevent + # features from implying these kinds of rules. + return [ + ft, + FeatureSetInfo(label = ctx.label, features = depset([ft])), + TargetCapabilityInfo(label = ctx.label, feature = ft), + FeatureConstraintInfo(label = ctx.label, all_of = depset([ft])), + ] + +cc_target_capability = rule( + implementation = _cc_target_capability_impl, + provides = [TargetCapabilityInfo, FeatureSetInfo, FeatureConstraintInfo], + doc = """A target capability is an optional feature that a target platform supports. + +For example, not all target platforms have dynamic loaders (e.g. microcontroller +firmware), so a toolchain may conditionally enable the capabilty to communicate +the capability to C/C++ rule implementations. + +``` +load("//cc/toolchains:toolchain.bzl", "cc_toolchain") + +cc_toolchain( + name = "universal_cc_toolchain", + # Assume no operating system means no dynamic loader support. + enabled_features = select({ + "@platforms//os:none": [], + "//conditions:default": [ + "//cc/toolchains/capabilities:supports_dynamic_linker", + ], + }), + # ... +) +``` + +`cc_target_capability` rules cannot be listed in a +[`cc_feature.implies`](#cc_feature-implies) list. + +Note: User-defined capabilities should prefer traditional +[user-defined build settings](https://bazel.build/extending/config#user-defined-build-settings). +This construct exists to communicate these features to preexisting C/C++ rule +implementations that expect these options to be exposed as +[features](https://bazel.build/docs/cc-toolchain-config-reference#features). +""", + attrs = { + "feature_name": attr.string(doc = "The name of the feature to generate for this capability"), + }, +) diff --git a/cc/toolchains/tool_capability.bzl b/cc/toolchains/tool_capability.bzl index 60b0f59..1e7e8ea 100644 --- a/cc/toolchains/tool_capability.bzl +++ b/cc/toolchains/tool_capability.bzl @@ -44,10 +44,11 @@ def _cc_tool_capability_impl(ctx): overrides = None, allowlist_include_directories = depset(), ) + + # Intentionally does not provide FeatureImplyabilityInfo to prevent + # features from implying these kinds of rules. return [ ToolCapabilityInfo(label = ctx.label, feature = ft), - # Only give it a feature constraint info and not a feature info. - # This way you can't imply it - you can only require it. FeatureConstraintInfo(label = ctx.label, all_of = depset([ft])), ] @@ -56,28 +57,41 @@ cc_tool_capability = rule( provides = [ToolCapabilityInfo, FeatureConstraintInfo], doc = """A capability is an optional feature that a tool supports. -For example, not all compilers support PIC, so to handle this, we write: +For example, not all linkers support --start-lib, so to handle this, we write: ``` +load("//cc/toolchains:args.bzl", "cc_args") +load("//cc/toolchains:tool.bzl", "cc_tool") + cc_tool( name = "clang", src = "@host_tools/bin/clang", capabilities = [ - "//cc/toolchains/capabilities:supports_pic", + "//cc/toolchains/capabilities:supports_start_end_lib", ], ) cc_args( name = "pic", - requires = [ - "//cc/toolchains/capabilities:supports_pic" - ], - args = ["-fPIC"], + requires = ["//cc/toolchains/capabilities:supports_start_end_lib"], + args = ["-Wl,--start-lib"], ) ``` -This ensures that `-fPIC` is added to the command-line only when we are using a -tool that supports PIC. +This ensures that `-Wl,--start-lib` is added to the command-line only when using +a tool that supports the argument. + + +`cc_target_capability` rules cannot be listed in a +[`cc_feature.implies`](#cc_feature-implies) list, or in +[`cc_toolchain.enabled_features`](#cc_toolchain-enabled_features). + +Note: Because [`cc_tool`](#cc_tool) rules are always evaluated under the exec +configuration, a `select()` to guide capabilities will `select()` on the +properties of the exec configuration. If you need a capability that is +conditionally guided by the target configuration, prefer using configurabilty +constructs to enable the feature at the +[`cc_toolchain.enabled_features`](#cc_toolchain-enabled_features) level. """, attrs = { "feature_name": attr.string(doc = "The name of the feature to generate for this capability"), diff --git a/tests/rule_based_toolchain/tool/BUILD b/tests/rule_based_toolchain/tool/BUILD index daa617a..534c283 100644 --- a/tests/rule_based_toolchain/tool/BUILD +++ b/tests/rule_based_toolchain/tool/BUILD @@ -6,7 +6,7 @@ load(":tool_test.bzl", "TARGETS", "TESTS") cc_tool( name = "tool", src = "//tests/rule_based_toolchain/testdata:bin_wrapper.sh", - capabilities = ["//cc/toolchains/capabilities:supports_pic"], + capabilities = ["//cc/toolchains/capabilities:supports_start_end_lib"], data = ["//tests/rule_based_toolchain/testdata:bin"], tags = ["requires-network"], ) diff --git a/tests/rule_based_toolchain/toolchain_config/BUILD b/tests/rule_based_toolchain/toolchain_config/BUILD index 08b5f83..a0a5a2a 100644 --- a/tests/rule_based_toolchain/toolchain_config/BUILD +++ b/tests/rule_based_toolchain/toolchain_config/BUILD @@ -47,7 +47,7 @@ cc_tool( name = "c_compile_tool", src = "//tests/rule_based_toolchain/testdata:bin_wrapper", allowlist_include_directories = ["//tests/rule_based_toolchain/testdata:subdirectory_3"], - capabilities = ["//cc/toolchains/capabilities:supports_pic"], + capabilities = ["//cc/toolchains/capabilities:supports_start_end_lib"], ) cc_sysroot( @@ -69,7 +69,10 @@ util.helper_target( ":sysroot", ":c_compile_args", ], - enabled_features = [":simple_feature"], + enabled_features = [ + ":simple_feature", + "//cc/toolchains/capabilities:supports_pic", + ], known_features = [":compile_feature"], tool_map = ":compile_tool_map", ) diff --git a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl index 1047203..6d30fad 100644 --- a/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl +++ b/tests/rule_based_toolchain/toolchain_config/toolchain_config_test.bzl @@ -197,6 +197,10 @@ def _toolchain_collects_files_test(env, targets): ], )], ), + legacy_feature( + name = "supports_pic", + enabled = True, + ), legacy_feature( name = "compile_feature", enabled = False, @@ -208,7 +212,7 @@ def _toolchain_collects_files_test(env, targets): )], ), legacy_feature( - name = "supports_pic", + name = "supports_start_end_lib", enabled = False, ), legacy_feature( @@ -225,7 +229,7 @@ def _toolchain_collects_files_test(env, targets): action_name = "c_compile", enabled = True, tools = [legacy_tool(tool = exe)], - implies = ["supports_pic"], + implies = ["supports_start_end_lib"], flag_sets = [ legacy_flag_set( flag_groups = [