diff --git a/cc/toolchains/feature.bzl b/cc/toolchains/feature.bzl index a2827628..f0acbfe7 100644 --- a/cc/toolchains/feature.bzl +++ b/cc/toolchains/feature.bzl @@ -110,22 +110,23 @@ 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. Example: +``` +cc_feature( + name = "sysroot_macos", + feature_name = "sysroot", + ... +) - cc_feature( - name = "sysroot_macos", - feature_name = "sysroot", - ... - ) - - cc_feature( - name = "sysroot_linux", - feature_name = "sysroot", - ... - ) +cc_feature( + name = "sysroot_linux", + feature_name = "sysroot", + ... +) +``` """, ), "args": attr.label_list( - doc = """Args that, when expanded, implement this feature.""", + doc = """A list of `cc_args` or `cc_args_list` labels that are expanded when this feature is enabled.""", providers = [ArgsListInfo], ), "requires_any_of": attr.label_list( @@ -152,7 +153,7 @@ silently disabled. ), "mutually_exclusive": attr.label_list( providers = [MutuallyExclusiveCategoryInfo], - doc = """A list of things that this is mutually exclusive with. + doc = """A list of things that this feature is mutually exclusive with. It can be either: * A feature, in which case the two features are mutually exclusive. @@ -171,14 +172,16 @@ In the example below, if you missed the "overrides" attribute, it would complain that the feature "opt" was defined twice. Example: - - cc_feature( - name = "opt", - feature_name = "opt", - ... - overrides = "@toolchain//features/well_known:opt", - ) - +``` +load("//cc/toolchains:feature.bzl", "cc_feature") + +cc_feature( + name = "opt", + feature_name = "opt", + args = [":size_optimized"], + overrides = "//cc/toolchains/features:opt", +) +``` """, ), }, @@ -188,53 +191,58 @@ Example: FeatureConstraintInfo, MutuallyExclusiveCategoryInfo, ], - doc = """Defines the implemented behavior of a C/C++ toolchain feature. + doc = """A dynamic set of toolchain flags that create a singular [feature](https://bazel.build/docs/cc-toolchain-config-reference#features) definition. -A feature is basically a toggleable list of args. There are a variety of -dependencies and compatibility requirements that must be satisfied for the -listed args to be applied. +A feature is basically a dynamically toggleable `cc_args_list`. There are a variety of +dependencies and compatibility requirements that must be satisfied to enable a +`cc_feature`. Once those conditions are met, the arguments in [`cc_feature.args`](#cc_feature-args) +are expanded and added to the command-line. 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 toggleable nature of toolchain features, it's generally best to -avoid defining features as part of your toolchain with the following exceptions: -* You want build files to be able to configure compiler flags. For example, a +* Via command-line flags, or a `.bazelrc` file via the + [`--features` flag](https://bazel.build/reference/command-line-reference#flag--features) +* Through inter-feature relationships (via [`cc_feature.implies`](#cc_feature-implies)) where one + feature may implicitly enable another. +* Individual rules (e.g. `cc_library`) or `package` definitions may elect to manually enable or + disable features through the + [`features` attribute](https://bazel.build/reference/be/common-definitions#common.features). + +Note that a feature may alternate between enabled and disabled dynamically over the course of a +build. Because of their toggleable nature, it's generally best to avoid adding arguments to a +`cc_toolchain` as a `cc_feature` unless strictly necessary. Instead, prefer to express arguments +via [`cc_toolchain.args`](#cc_toolchain-args) whenever possible. + +You should use a `cc_feature` when any of the following apply: +* You need the flags to be dynamically toggled over the course of a build. +* You want build files to be able to configure the flags in question. For example, a binary might specify `features = ["optimize_for_size"]` to create a small binary instead of optimizing for performance. * 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. + workflows and flags were likely based on features. -If you want to be able to configure flags via the bazel command-line, instead -consider making a bool_flag, and then making your `cc_args` `select` on those -flags. +If you only need to configure flags via the Bazel command-line, instead +consider adding a +[`bool_flag`](https://github.com/bazelbuild/bazel-skylib/tree/main/doc/common_settings_doc.md#bool_flag) +paired with a [`config_setting`](https://bazel.build/reference/be/general#config_setting) +and then make your `cc_args` rule `select` on the `config_setting`. For more details about how Bazel handles features, see the official Bazel documentation at https://bazel.build/docs/cc-toolchain-config-reference#features. -Examples: - - # A feature that can be easily toggled to optimize for size - cc_feature( - name = "optimize_for_size", - feature_name = "optimize_for_size", - args = [":optimize_for_size_args"], - ) - - # 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 - cc_feature( - name = "supports_pic", - overrides = "//cc/toolchains/features:supports_pic - ) +Example: +``` +load("//cc/toolchains:feature.bzl", "cc_feature") + +# A feature that enables LTO, which may be incompatible when doing interop with various +# languages (e.g. rust, go), or may need to be disabled for particular `cc_binary` rules +# for various reasons. +cc_feature( + name = "lto", + feature_name = "lto", + args = [":lto_args"], +) +``` """, ) diff --git a/cc/toolchains/feature_constraint.bzl b/cc/toolchains/feature_constraint.bzl index c6ae44a5..8a3d60f8 100644 --- a/cc/toolchains/feature_constraint.bzl +++ b/cc/toolchains/feature_constraint.bzl @@ -47,8 +47,26 @@ cc_feature_constraint = rule( ), }, provides = [FeatureConstraintInfo], - doc = """Defines a constraint on features. + doc = """Defines a compound relationship between features. -Can be used with require_any_of to specify that something is only enabled when -a constraint is met.""", +This rule can be used with [`cc_args.require_any_of`](#cc_args-require_any_of) to specify that a set +of arguments are only enabled when a constraint is met. Both `all_of` and `none_of` must be +satisfied simultaneously. + +This is basically a `cc_feature_set` that supports `none_of` expressions. This extra flexibility +is why this rule may only be used by [`cc_args.require_any_of`](#cc_args-require_any_of). + +Example: +``` +load("//cc/toolchains:feature_constraint.bzl", "cc_feature_constraint") + +# A constraint that requires a `linker_supports_thinlto` feature to be enabled, +# AND a `no_optimization` to be disabled. +cc_feature_constraint( + name = "thinlto_constraint", + all_of = [":linker_supports_thinlto"], + none_of = [":no_optimization"], +) +``` +""", ) diff --git a/cc/toolchains/feature_set.bzl b/cc/toolchains/feature_set.bzl index 07af6d1d..5fcdae42 100644 --- a/cc/toolchains/feature_set.bzl +++ b/cc/toolchains/feature_set.bzl @@ -44,14 +44,20 @@ cc_feature_set = rule( provides = [FeatureSetInfo], doc = """Defines a set of features. +This may be used by both `cc_feature` and `cc_args` rules, and is effectively a way to express +a logical `AND` operation across multiple requred features. + Example: +``` +load("//cc/toolchains:feature_set.bzl", "cc_feature_set") - cc_feature_set( - name = "thin_lto_requirements", - all_of = [ - ":thin_lto", - ":opt", - ], - ) +cc_feature_set( + name = "thin_lto_requirements", + all_of = [ + ":thin_lto", + ":opt", + ], +) +``` """, ) diff --git a/cc/toolchains/impl/documented_api.bzl b/cc/toolchains/impl/documented_api.bzl index 827d7fb4..a8632906 100644 --- a/cc/toolchains/impl/documented_api.bzl +++ b/cc/toolchains/impl/documented_api.bzl @@ -16,9 +16,14 @@ load("//cc/toolchains:actions.bzl", _cc_action_type = "cc_action_type", _cc_action_type_set = "cc_action_type_set") load("//cc/toolchains:args.bzl", _cc_args = "cc_args") load("//cc/toolchains:args_list.bzl", _cc_args_list = "cc_args_list") +load("//cc/toolchains:feature.bzl", _cc_feature = "cc_feature") +load("//cc/toolchains:feature_constraint.bzl", _cc_feature_constraint = "cc_feature_constraint") +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:tool.bzl", _cc_tool = "cc_tool") load("//cc/toolchains:tool_map.bzl", _cc_tool_map = "cc_tool_map") +load("//cc/toolchains/impl:external_feature.bzl", _cc_external_feature = "cc_external_feature") load("//cc/toolchains/impl:variables.bzl", _cc_variable = "cc_variable") cc_tool_map = _cc_tool_map @@ -29,6 +34,11 @@ cc_args_list = _cc_args_list cc_action_type = _cc_action_type cc_action_type_set = _cc_action_type_set cc_variable = _cc_variable +cc_feature = _cc_feature +cc_feature_constraint = _cc_feature_constraint +cc_feature_set = _cc_feature_set +cc_mutually_exclusive_category = _cc_mutually_exclusive_category +cc_external_feature = _cc_external_feature # This list is used to automatically remap instances of `foo` to [`foo`](#foo) # links in the generated documentation so that maintainers don't need to manually @@ -42,4 +52,9 @@ DOCUMENTED_TOOLCHAIN_RULES = [ "cc_action_type", "cc_action_type_set", "cc_variable", + "cc_feature", + "cc_feature_constraint", + "cc_feature_set", + "cc_mutually_exclusive_category", + "cc_external_feature", ] diff --git a/cc/toolchains/impl/external_feature.bzl b/cc/toolchains/impl/external_feature.bzl index 1e11bc95..027738fe 100644 --- a/cc/toolchains/impl/external_feature.bzl +++ b/cc/toolchains/impl/external_feature.bzl @@ -69,5 +69,26 @@ cc_external_feature = rule( ), }, provides = [FeatureInfo, FeatureSetInfo, FeatureConstraintInfo], - doc = "A declaration that a feature with this name is defined elsewhere.", + doc = """A declaration that a [feature](https://bazel.build/docs/cc-toolchain-config-reference#features) with this name is defined elsewhere. + +This rule communicates that a feature has been defined externally to make it possible to reference +features that live outside the rule-based cc toolchain ecosystem. This allows various toolchain +rules to reference the external feature without accidentally re-defining said feature. + +This rule is currently considered a private API of the toolchain rules to encourage the Bazel +ecosystem to migrate to properly defining their features as rules. + +Example: +``` +load("//cc/toolchains:external_feature.bzl", "cc_external_feature") + +# rules_rust defines a feature that is disabled whenever rust artifacts are being linked using +# the cc toolchain to signal that incompatible flags should be disabled as well. +cc_external_feature( + name = "rules_rust_unsupported_feature", + feature_name = "rules_rust_unsupported_feature", + overridable = False, +) +``` +""", ) diff --git a/cc/toolchains/mutually_exclusive_category.bzl b/cc/toolchains/mutually_exclusive_category.bzl index 99202905..f83b5549 100644 --- a/cc/toolchains/mutually_exclusive_category.bzl +++ b/cc/toolchains/mutually_exclusive_category.bzl @@ -23,7 +23,40 @@ def _cc_mutually_exclusive_category_impl(ctx): cc_mutually_exclusive_category = rule( implementation = _cc_mutually_exclusive_category_impl, - doc = "A category of features, for which only one can be enabled", + doc = """A rule used to categorize `cc_feature` definitions for which only one can be enabled. + +This is used by [`cc_feature.mutually_exclusive`](#cc_feature-mutually_exclusive) to express groups +of `cc_feature` definitions that are inherently incompatible with each other and must be treated as +mutually exclusive. + +Warning: These groups are keyed by name, so two `cc_mutually_exclusive_category` definitions of the +same name in different packages will resolve to the same logical group. + +Example: +``` +load("//cc/toolchains:feature.bzl", "cc_feature") +load("//cc/toolchains:mutually_exclusive_category.bzl", "cc_mutually_exclusive_category") + +cc_mutually_exclusive_category( + name = "opt_level", +) + +cc_feature( + name = "speed_optimized", + mutually_exclusive = [":opt_level"], +) + +cc_feature( + name = "size_optimized", + mutually_exclusive = [":opt_level"], +) + +cc_feature( + name = "unoptimized", + mutually_exclusive = [":opt_level"], +) +``` +""", attrs = {}, provides = [MutuallyExclusiveCategoryInfo], ) diff --git a/cc/toolchains/toolchain_api.md b/cc/toolchains/toolchain_api.md index 013df8aa..418ef52f 100644 --- a/cc/toolchains/toolchain_api.md +++ b/cc/toolchains/toolchain_api.md @@ -134,6 +134,247 @@ cc_args_list( | args | (ordered) cc_args to include in this list. | List of labels | optional | `[]` | + + +## cc_external_feature + +
+cc_external_feature(name, feature_name, overridable) ++ +A declaration that a [feature](https://bazel.build/docs/cc-toolchain-config-reference#features) with this name is defined elsewhere. + +This rule communicates that a feature has been defined externally to make it possible to reference +features that live outside the rule-based cc toolchain ecosystem. This allows various toolchain +rules to reference the external feature without accidentally re-defining said feature. + +This rule is currently considered a private API of the toolchain rules to encourage the Bazel +ecosystem to migrate to properly defining their features as rules. + +Example: +``` +load("@rules_cc//cc/toolchains:external_feature.bzl", "cc_external_feature") + +# rules_rust defines a feature that is disabled whenever rust artifacts are being linked using +# the cc toolchain to signal that incompatible flags should be disabled as well. +cc_external_feature( + name = "rules_rust_unsupported_feature", + feature_name = "rules_rust_unsupported_feature", + overridable = False, +) +``` + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| feature_name | The name of the feature | String | required | | +| overridable | Whether the feature can be overridden | Boolean | required | | + + + + +## cc_feature + +
+cc_feature(name, args, feature_name, implies, mutually_exclusive, overrides, requires_any_of) ++ +A dynamic set of toolchain flags that create a singular [feature](https://bazel.build/docs/cc-toolchain-config-reference#features) definition. + +A feature is basically a dynamically toggleable [`cc_args_list`](#cc_args_list). There are a variety of +dependencies and compatibility requirements that must be satisfied to enable a +[`cc_feature`](#cc_feature). Once those conditions are met, the arguments in [`cc_feature.args`](#cc_feature-args) +are expanded and added to the command-line. + +A feature may be enabled or disabled through the following mechanisms: +* Via command-line flags, or a `.bazelrc` file via the + [`--features` flag](https://bazel.build/reference/command-line-reference#flag--features) +* Through inter-feature relationships (via [`cc_feature.implies`](#cc_feature-implies)) where one + feature may implicitly enable another. +* Individual rules (e.g. `cc_library`) or `package` definitions may elect to manually enable or + disable features through the + [`features` attribute](https://bazel.build/reference/be/common-definitions#common.features). + +Note that a feature may alternate between enabled and disabled dynamically over the course of a +build. Because of their toggleable nature, it's generally best to avoid adding arguments to a +`cc_toolchain` as a [`cc_feature`](#cc_feature) unless strictly necessary. Instead, prefer to express arguments +via [`cc_toolchain.args`](#cc_toolchain-args) whenever possible. + +You should use a [`cc_feature`](#cc_feature) when any of the following apply: +* You need the flags to be dynamically toggled over the course of a build. +* You want build files to be able to configure the flags in question. For example, a + binary might specify `features = ["optimize_for_size"]` to create a small + binary instead of optimizing for performance. +* 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. + +If you only need to configure flags via the Bazel command-line, instead +consider adding a +[`bool_flag`](https://github.com/bazelbuild/bazel-skylib/tree/main/doc/common_settings_doc.md#bool_flag) +paired with a [`config_setting`](https://bazel.build/reference/be/general#config_setting) +and then make your [`cc_args`](#cc_args) rule `select` on the `config_setting`. + +For more details about how Bazel handles features, see the official Bazel +documentation at +https://bazel.build/docs/cc-toolchain-config-reference#features. + +Example: +``` +load("@rules_cc//cc/toolchains:feature.bzl", "cc_feature") + +# A feature that enables LTO, which may be incompatible when doing interop with various +# languages (e.g. rust, go), or may need to be disabled for particular `cc_binary` rules +# for various reasons. +cc_feature( + name = "lto", + feature_name = "lto", + args = [":lto_args"], +) +``` + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| args | A list of [`cc_args`](#cc_args) or [`cc_args_list`](#cc_args_list) labels that are expanded when this feature is enabled. | List of labels | optional | `[]` | +| feature_name | The name of the feature that this rule implements.
cc_feature(
name = "sysroot_macos",
feature_name = "sysroot",
...
)
cc_feature(
name = "sysroot_linux",
feature_name = "sysroot",
...
)
| String | optional | `""` |
+| implies | List of features enabled along with this feature.load("@rules_cc//cc/toolchains:feature.bzl", "cc_feature")
cc_feature(
name = "opt",
feature_name = "opt",
args = [":size_optimized"],
overrides = "@rules_cc//cc/toolchains/features:opt",
)
| Label | optional | `None` |
+| requires_any_of | A list of feature sets that define toolchain compatibility.+cc_feature_constraint(name, all_of, none_of) ++ +Defines a compound relationship between features. + +This rule can be used with [`cc_args.require_any_of`](#cc_args-require_any_of) to specify that a set +of arguments are only enabled when a constraint is met. Both `all_of` and `none_of` must be +satisfied simultaneously. + +This is basically a [`cc_feature_set`](#cc_feature_set) that supports `none_of` expressions. This extra flexibility +is why this rule may only be used by [`cc_args.require_any_of`](#cc_args-require_any_of). + +Example: +``` +load("@rules_cc//cc/toolchains:feature_constraint.bzl", "cc_feature_constraint") + +# A constraint that requires a `linker_supports_thinlto` feature to be enabled, +# AND a `no_optimization` to be disabled. +cc_feature_constraint( + name = "thinlto_constraint", + all_of = [":linker_supports_thinlto"], + none_of = [":no_optimization"], +) +``` + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| all_of | - | List of labels | optional | `[]` | +| none_of | - | List of labels | optional | `[]` | + + + + +## cc_feature_set + +
+cc_feature_set(name, all_of) ++ +Defines a set of features. + +This may be used by both [`cc_feature`](#cc_feature) and [`cc_args`](#cc_args) rules, and is effectively a way to express +a logical `AND` operation across multiple requred features. + +Example: +``` +load("@rules_cc//cc/toolchains:feature_set.bzl", "cc_feature_set") + +cc_feature_set( + name = "thin_lto_requirements", + all_of = [ + ":thin_lto", + ":opt", + ], +) +``` + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| all_of | A set of features | List of labels | optional | `[]` | + + + + +## cc_mutually_exclusive_category + +
+cc_mutually_exclusive_category(name) ++ +A rule used to categorize [`cc_feature`](#cc_feature) definitions for which only one can be enabled. + +This is used by [`cc_feature.mutually_exclusive`](#cc_feature-mutually_exclusive) to express groups +of [`cc_feature`](#cc_feature) definitions that are inherently incompatible with each other and must be treated as +mutually exclusive. + +Warning: These groups are keyed by name, so two [`cc_mutually_exclusive_category`](#cc_mutually_exclusive_category) definitions of the +same name in different packages will resolve to the same logical group. + +Example: +``` +load("@rules_cc//cc/toolchains:feature.bzl", "cc_feature") +load("@rules_cc//cc/toolchains:mutually_exclusive_category.bzl", "cc_mutually_exclusive_category") + +cc_mutually_exclusive_category( + name = "opt_level", +) + +cc_feature( + name = "speed_optimized", + mutually_exclusive = [":opt_level"], +) + +cc_feature( + name = "size_optimized", + mutually_exclusive = [":opt_level"], +) + +cc_feature( + name = "unoptimized", + mutually_exclusive = [":opt_level"], +) +``` + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | + + ## cc_tool @@ -195,7 +436,7 @@ argument expressed in a toolchain tool invocation (e.g. `gcc`, `llvm-ar`) is dec [`cc_args`](#cc_args) rule that applies an ordered list of arguments to a set of toolchain actions. [`cc_args`](#cc_args) rules can be added unconditionally to a `cc_toolchain`, conditionally via `select()` statements, or dynamically via an -intermediate `cc_feature`. +intermediate [`cc_feature`](#cc_feature). Conceptually, this is similar to the old `CFLAGS`, `CPPFLAGS`, etc. environment variables that many build systems use to determine which flags to use for a given action. The significant