diff --git a/pw_toolchain/host_clang/BUILD.bazel b/pw_toolchain/host_clang/BUILD.bazel index 303890f520..93fb5a4339 100644 --- a/pw_toolchain/host_clang/BUILD.bazel +++ b/pw_toolchain/host_clang/BUILD.bazel @@ -14,9 +14,11 @@ load( "@pw_toolchain//cc_toolchain:defs.bzl", + "ALL_ASM_ACTIONS", "ALL_CPP_COMPILER_ACTIONS", "ALL_C_COMPILER_ACTIONS", "ALL_LINK_ACTIONS", + "pw_cc_feature", "pw_cc_flag_set", "pw_cc_toolchain", ) @@ -73,6 +75,24 @@ pw_cc_flag_set( ], ) +pw_cc_flag_set( + name = "verbose_compiler_flags", + actions = ALL_ASM_ACTIONS + ALL_C_COMPILER_ACTIONS + ALL_CPP_COMPILER_ACTIONS + ALL_LINK_ACTIONS, + flags = ["-v"], +) + +# A feature that can be easily toggled to include extra compiler output to help +# debug things like include search path ordering and showing all the flags +# passed to the compiler. +# +# Add `--features=verbose_compiler_output` to your Bazel invocation to enable. +pw_cc_feature( + name = "verbose_compiler_output", + enabled = False, + feature_name = "verbose_compiler_output", + flag_sets = [":verbose_compiler_flags"], +) + pw_cc_flag_set( name = "no_unknown_warning_option", actions = ALL_C_COMPILER_ACTIONS + ALL_CPP_COMPILER_ACTIONS, @@ -158,6 +178,10 @@ pw_cc_toolchain( "@platforms//os:macos": "macosx", "//conditions:default": None, }), + target_system_name = "unknown", + toolchain_features = [ + ":verbose_compiler_output", + ], toolchain_identifier = "host-toolchain", ) diff --git a/pw_toolchain_bazel/api.rst b/pw_toolchain_bazel/api.rst index a41404b48e..646ec84f34 100644 --- a/pw_toolchain_bazel/api.rst +++ b/pw_toolchain_bazel/api.rst @@ -581,3 +581,188 @@ API reference will silently be disabled. This behavior is native to Bazel itself, and there's no way to detect this and emit an error instead. For this reason, be very cautious when listing implied features! + +.. py:class:: pw_cc_feature + + Defines the implemented behavior of a C/C++ toolchain feature. + + + This rule is effectively a wrapper for the ``feature`` constructor in + `@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl `_. + + A feature is basically a dynamic flag set. There are a variety of + dependencies and compatibility requirements that must be satisfied for the + listed flag sets to be applied. + + A feature may be enabled or disabled through the following mechanisms:\ + + * Via command-line flags, or a + `.bazelrc file `_\. + * Through inter-feature relationships (enabling one feature may implicitly + enable another). + * Individual rules may elect to manually enable or disable features through + the + `builtin features attribute `_\. + + Because of the dynamic nature of toolchain features, it's generally best to + avoid enumerating features as part of your toolchain with the following + exceptions: + + * You want the flags to be controllable via Bazel's CLI. For example, adding + ``-v`` to a compiler invocation is often too verbose to be useful for most + workflows, but can be instrumental when debugging obscure errors. By + expressing compiler verbosity as a feature, users may opt-in when + necessary. + * You need to carry forward Starlark toolchain behaviors. If you're migrating + a complex Starlark-based toolchain definition to these rules, many of the + workflows and flags were likely based on features. This rule exists to + support those existing structures. + + For more details about how Bazel handles features, see the official Bazel + documentation at + https://bazel.build/docs/cc-toolchain-config-reference#features. + + Note: ``env_sets`` are not yet supported. + + Examples: + + .. code-block:: py + + # A feature that can be easily toggled to include extra compiler output to + # help debug things like include search path ordering and showing all the + # flags passed to the compiler. + # + # Add `--features=verbose_compiler_output` to your Bazel invocation to + # enable. + pw_cc_feature( + name = "verbose_compiler_output", + enabled = False, + feature_name = "verbose_compiler_output", + flag_sets = [":verbose_compiler_flags"], + ) + + # This feature signals a capability, and doesn't have associated flags. + # + # For a list of well-known features, see: + # https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features + pw_cc_feature( + name = "link_object_files", + enabled = True, + feature_name = "supports_start_end_lib", + ) + + .. py:attribute:: feature_name + :type: str + + The name of the feature that this rule implements. + + Feature names are used to express feature dependencies and compatibility. + Because features are tracked by string names rather than labels, there's + great flexibility in swapping out feature implementations or overriding + the built-in legacy features that Bazel silently binds to every + toolchain. + + :py:attr:`pw_cc_feature.feature_name` is used rather than ``name`` to + distinguish between the rule name, and the intended final feature name. + This allows similar rules to exist in the same package, even if slight + differences are required. + + Example: + + .. code-block:: py + + pw_cc_feature( + name = "sysroot_macos", + feature_name = "sysroot", + ... + ) + + pw_cc_feature( + name = "sysroot_linux", + feature_name = "sysroot", + ... + ) + + While two features with the same :py:attr:`pw_cc_feature.feature_name` may + not be bound to the same toolchain, they can happily live alongside each + other in the same BUILD file. + + .. py:attribute:: enabled + :type: bool + + Whether or not this feature is enabled by default. + + .. py:attribute:: flag_sets + :type: List[label] + + Flag sets that, when expanded, implement this feature. + + .. py:attribute:: requires + :type: List[label] + + A list of feature sets that define toolchain compatibility. + + If **at least one** of the listed :py:class:`pw_cc_feature_set`\s are + satisfied (all features exist in the toolchain AND are currently enabled), + this feature is deemed compatible and may be enabled. + + .. admonition:: Note + + Even if :py:attr:`pw_cc_feature.requires` is satisfied, a feature is + not enabled unless another mechanism (e.g. command-line flags, + :py:attr:`pw_cc_feature.implies`, or :py:attr:`pw_cc_feature.enabled`\) + signals that the feature should actually be enabled. + + .. py:attribute:: implies + :type: List[str] + + Names of features enabled along with this feature. + + .. admonition:: Note + :class: warning + + If any of the named features cannot be enabled, this feature is + silently disabled. + + .. py:attribute:: provides + :type: List[str] + + A list of additional feature names this feature fulfills. + + .. admonition:: Note + + This feature cannot be enabled if another feature also provides the + listed feature names. + + +.. py:class:: pw_cc_feature_set + + Defines a set of required features. + + This rule is effectively a wrapper for the ``feature_set`` constructor in + `@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl `_. + + This rule is used to express a group of features that may satisfy a + :py:attr:`pw_cc_feature.requires` list. If **all** of the specified features + in a :py:class:`pw_cc_feature_set` are enabled, the :py:class:`pw_cc_feature` + that lists the feature set *can* also be enabled. Note that **this does cause + the feature to be enabled**; it only means it is possible for the feature to + be enabled. + + Example: + + .. code-block:: py + + pw_cc_feature_set( + name = "thin_lto_requirements", + feature_names = [ + "thin_lto", + "opt", + ], + ) + + .. py:attribute:: feature_names + :type: List[str] + + Features that must be enabled for this feature set to be deemed compatible + with the current toolchain configuration. diff --git a/pw_toolchain_bazel/cc_toolchain/defs.bzl b/pw_toolchain_bazel/cc_toolchain/defs.bzl index 77de21943b..241de6782f 100644 --- a/pw_toolchain_bazel/cc_toolchain/defs.bzl +++ b/pw_toolchain_bazel/cc_toolchain/defs.bzl @@ -26,6 +26,11 @@ load( "//cc_toolchain/private:cc_toolchain.bzl", _pw_cc_toolchain = "pw_cc_toolchain", ) +load( + "//cc_toolchain/private:feature.bzl", + _pw_cc_feature = "pw_cc_feature", + _pw_cc_feature_set = "pw_cc_feature_set", +) load( "//cc_toolchain/private:flag_set.bzl", _pw_cc_flag_group = "pw_cc_flag_group", @@ -66,6 +71,9 @@ ALL_STRIP_ACTIONS = [STRIP_ACTION_NAME] pw_cc_action_config = _pw_cc_action_config pw_cc_tool = _pw_cc_tool +pw_cc_feature = _pw_cc_feature +pw_cc_feature_set = _pw_cc_feature_set + pw_cc_flag_group = _pw_cc_flag_group pw_cc_flag_set = _pw_cc_flag_set diff --git a/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl b/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl index 77a4beb20d..57d63a8909 100644 --- a/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl +++ b/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl @@ -17,6 +17,7 @@ load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "ActionConfigInfo", + "FeatureInfo", "FlagSetInfo", "action_config", "feature", @@ -53,6 +54,7 @@ PW_CC_TOOLCHAIN_DEPRECATED_TOOL_ATTRS = { PW_CC_TOOLCHAIN_CONFIG_ATTRS = { "action_configs": "List of `pw_cc_action_config` labels that bind tools to the appropriate actions", "action_config_flag_sets": "List of `pw_cc_flag_set`s to apply to their respective action configs", + "toolchain_features": "List of `pw_cc_feature`s that this toolchain supports", # Attributes originally part of create_cc_toolchain_config_info. "toolchain_identifier": "See documentation for cc_common.create_cc_toolchain_config_info()", @@ -73,7 +75,7 @@ PW_CC_TOOLCHAIN_SHARED_ATTRS = ["toolchain_identifier"] PW_CC_TOOLCHAIN_BLOCKED_ATTRS = { "toolchain_config": "pw_cc_toolchain includes a generated toolchain config", "artifact_name_patterns": "pw_cc_toolchain does not yet support artifact name patterns", - "features": "pw_cc_toolchain does not yet support features", + "features": "Use toolchain_features to add pw_cc_toolchain_feature deps to the toolchain", "tool_paths": "pw_cc_toolchain does not support tool_paths, use \"action_configs\" to set toolchain tools", "make_variables": "pw_cc_toolchain does not yet support make variables", } @@ -239,9 +241,7 @@ def _pw_cc_toolchain_config_impl(ctx): all_actions = _collect_action_configs(ctx, flag_sets_by_action) builtin_include_dirs = ctx.attr.cxx_builtin_include_directories if ctx.attr.cxx_builtin_include_directories else [] sysroot_dir = ctx.attr.builtin_sysroot if ctx.attr.builtin_sysroot else None - - # TODO: b/309533028 - Support features. - features = [] + features = [dep[FeatureInfo] for dep in ctx.attr.toolchain_features] # TODO: b/297413805 - This could be externalized. features.append(_archiver_flags_feature(ctx.attr.target_libc == "macosx")) @@ -269,6 +269,7 @@ pw_cc_toolchain_config = rule( # Attributes new to this rule. "action_configs": attr.label_list(), "action_config_flag_sets": attr.label_list(providers = [FlagSetInfo]), + "toolchain_features": attr.label_list(providers = [FeatureInfo]), # Attributes from create_cc_toolchain_config_info. "toolchain_identifier": attr.string(), diff --git a/pw_toolchain_bazel/cc_toolchain/private/feature.bzl b/pw_toolchain_bazel/cc_toolchain/private/feature.bzl new file mode 100644 index 0000000000..b8c1b2df0d --- /dev/null +++ b/pw_toolchain_bazel/cc_toolchain/private/feature.bzl @@ -0,0 +1,206 @@ +# Copyright 2023 The Pigweed Authors +# +# 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 +# +# https://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 pw_cc_feature and pw_cc_feature_set rules.""" + +load( + "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", + "FeatureInfo", + "FeatureSetInfo", + "FlagSetInfo", + "feature", + "feature_set", +) + +def _pw_cc_feature_set_impl(ctx): + if not ctx.attr.feature_names: + fail("At least one feature name must be specified in `feature_names`") + return feature_set( + feature_names = ctx.attr.features, + ) + +pw_cc_feature_set = rule( + implementation = _pw_cc_feature_set_impl, + attrs = { + "feature_names": attr.string_list( + doc = """Features that must be enabled for this feature set to be deemed compatible with the current toolchain configuration.""", + ), + }, + provides = [FeatureSetInfo], + doc = """Defines a set of required features. + +This rule is effectively a wrapper for the `feature_set` constructor in +@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl. + +This rule is used to express a group of features that may satisfy a +`pw_cc_feature`'s' `requires` list. If all of the specified features in a +`pw_cc_feature_set` are enabled, the `pw_cc_feature` that lists the feature +set *can* also be enabled. Note that this does cause the feature to be enabled; +it only means it is possible for the feature to be enabled. + +Example: + + pw_cc_feature_set( + name = "thin_lto_requirements", + feature_names = [ + "thin_lto", + "opt", + ], + ) +""", +) + +def _pw_cc_feature_impl(ctx): + return feature( + name = ctx.attr.feature_name, + enabled = ctx.attr.enabled, + flag_sets = [fs[FlagSetInfo] for fs in ctx.attr.flag_sets], # TODO: b/311679764 - Add label propagation for deduping. + requires = [req[FeatureSetInfo] for req in ctx.attr.requires], + implies = ctx.attr.implies, + provides = ctx.attr.provides, + ) + +pw_cc_feature = rule( + implementation = _pw_cc_feature_impl, + attrs = { + "feature_name": attr.string( + mandatory = True, + doc = """The name of the feature that this rule implements. + +Feature names are used to express feature dependencies and compatibility. +Because features are tracked by string names rather than labels, there's great +flexibility in swapping out feature implementations or overriding the built-in +legacy features that Bazel silently binds to every toolchain. + +`feature_name` is used rather than `name` to distinguish between the rule +name, and the intended final feature name. This allows similar rules to exist +in the same package, even if slight differences are required. + +Example: + + pw_cc_feature( + name = "sysroot_macos", + feature_name = "sysroot", + ... + ) + + pw_cc_feature( + name = "sysroot_linux", + feature_name = "sysroot", + ... + ) + +While two features with the same `feature_name` may not be bound to the same +toolchain, they can happily live alongside each other in the same BUILD file. +""", + ), + "enabled": attr.bool( + mandatory = True, + doc = """Whether or not this feature is enabled by default.""", + ), + "flag_sets": attr.label_list( + doc = """Flag sets that, when expanded, implement this feature.""", + providers = [FlagSetInfo], + ), + "requires": attr.label_list( + doc = """A list of feature sets that define toolchain compatibility. + +If *at least one* of the listed `pw_cc_feature_set`s are fully satisfied (all +features exist in the toolchain AND are currently enabled), this feature is +deemed compatible and may be enabled. + +Note: Even if `pw_cc_feature.requires` is satisfied, a feature is not enabled +unless another mechanism (e.g. command-line flags, `pw_cc_feature.implies`, +`pw_cc_feature.enabled`) signals that the feature should actually be enabled. +""", + providers = [FeatureSetInfo], + ), + "implies": attr.string_list( + doc = """Names of features enabled along with this feature. + +Warning: If any of the named features cannot be enabled, this feature is +silently disabled. +""", + ), + "provides": attr.string_list( + doc = """A list of additional feature names this feature fulfills. +If this feature has a side-effect of implementing another feature, it can be +useful to list that feature's name here to ensure they aren't enabled at the +same time. + +Note: This feature cannot be enabled if another feature also provides the listed +feature names. +""", + ), + }, + provides = [FeatureInfo], + doc = """Defines the implemented behavior of a C/C++ toolchain feature. + +This rule is effectively a wrapper for the `feature` constructor in +@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl. + +A feature is basically a dynamic flag set. There are a variety of dependencies +and compatibility requirements that must be satisfied for the listed flag sets +to be applied. + +A feature may be enabled or disabled through the following mechanisms: +* Via command-line flags, or a `.bazelrc`. +* Through inter-feature relationships (enabling one feature may implicitly + enable another). +* Individual rules may elect to manually enable or disable features through the + builtin ``features`` attribute. + +Because of the dynamic nature of toolchain features, it's generally best to +avoid enumerating features as part of your toolchain with the following +exceptions: +* You want the flags to be controllable via Bazel's CLI. For example, adding + `-v` to a compiler invocation is often too verbose to be useful for most + workflows, but can be instrumental when debugging obscure errors. By + expressing compiler verbosity as a feature, users may opt-in when necessary. +* You need to carry forward Starlark toolchain behaviors. If you're migrating a + complex Starlark-based toolchain definition to these rules, many of the + workflows and flags were likely based on features. This rule exists to support + those existing structures. + +For more details about how Bazel handles features, see the official Bazel +documentation at +https://bazel.build/docs/cc-toolchain-config-reference#features. + +Note: `env_sets` are not yet supported. + +Examples: + + # A feature that can be easily toggled to include extra compiler output to + # help debug things like include search path ordering and showing all the + # flags passed to the compiler. + # + # Add `--features=verbose_compiler_output` to your Bazel invocation to + # enable. + pw_cc_feature( + name = "verbose_compiler_output", + enabled = False, + feature_name = "verbose_compiler_output", + flag_sets = [":verbose_compiler_flags"], + ) + + # This feature signals a capability, and doesn't have associated flags. + # + # For a list of well-known features, see: + # https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features + pw_cc_feature( + name = "link_object_files", + enabled = True, + feature_name = "supports_start_end_lib", + ) +""", +)