aboutsummaryrefslogtreecommitdiff
path: root/pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl')
-rw-r--r--pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl225
1 files changed, 225 insertions, 0 deletions
diff --git a/pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl b/pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl
new file mode 100644
index 000000000..23094e864
--- /dev/null
+++ b/pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl
@@ -0,0 +1,225 @@
+# 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 pw_cc_flag_set and pw_cc_flag_group."""
+
+load(
+ "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
+ "FlagGroupInfo",
+ "FlagSetInfo",
+ "flag_group",
+ "flag_set",
+)
+
+def _pw_cc_flag_group_impl(ctx):
+ """Implementation for pw_cc_flag_group."""
+
+ # If these are empty strings, they are handled differently than if they
+ # are None. Rather than an explicit error or breakage, there's just silent
+ # behavioral differences. Ideally, these attributes default to `None`, but
+ # that is not supported with string types. Since these have no practical
+ # meaning if they are empty strings, just remap empty strings to `None`.
+ #
+ # A minimal reproducer of this behavior with some useful analysis is
+ # provided here:
+ #
+ # https://github.com/armandomontanez/bazel_reproducers/tree/main/flag_group_with_empty_strings
+ iterate_over = ctx.attr.iterate_over if ctx.attr.iterate_over else None
+ expand_if = ctx.attr.expand_if_available if ctx.attr.expand_if_available else None
+ expand_if_not = ctx.attr.expand_if_not_available if ctx.attr.expand_if_not_available else None
+ return flag_group(
+ flags = ctx.attr.flags,
+ iterate_over = iterate_over,
+ expand_if_available = expand_if,
+ expand_if_not_available = expand_if_not,
+ )
+
+pw_cc_flag_group = rule(
+ implementation = _pw_cc_flag_group_impl,
+ attrs = {
+ "flags": attr.string_list(
+ doc = """List of flags provided by this rule.
+
+For extremely complex expressions of flags that require nested flag groups with
+multiple layers of expansion, prefer creating a custom rule in Starlark that
+provides `FlagGroupInfo` or `FlagSetInfo`.
+""",
+ ),
+ "iterate_over": attr.string(
+ doc = """Expands `flags` for items in the named list.
+
+Toolchain actions have various variables accessible as names that can be used
+to guide flag expansions. For variables that are lists, `iterate_over` must be
+used to expand the list into a series of flags.
+
+Note that `iterate_over` is the string name of a build variable, and not an
+actual list. Valid options are listed at:
+
+ https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables
+
+Note that the flag expansion stamps out the entire list of flags in `flags`
+once for each item in the list.
+
+Example:
+
+ # Expands each path in `system_include_paths` to a series of `-isystem`
+ # includes.
+ #
+ # Example input:
+ # system_include_paths = ["/usr/local/include", "/usr/include"]
+ #
+ # Expected result:
+ # "-isystem /usr/local/include -isystem /usr/include"
+ pw_cc_flag_group(
+ name = "system_include_paths",
+ flags = ["-isystem", "%{system_include_paths}"],
+ iterate_over = "system_include_paths",
+ )
+""",
+ ),
+ "expand_if_available": attr.string(
+ doc = "Expands the expression in `flags` if the specified build variable is set.",
+ ),
+ "expand_if_not_available": attr.string(
+ doc = "Expands the expression in `flags` if the specified build variable is NOT set.",
+ ),
+ },
+ provides = [FlagGroupInfo],
+ doc = """Declares an (optionally parametric) ordered set of flags.
+
+`pw_cc_flag_group` rules are expected to be consumed exclusively by
+`pw_cc_flag_set` rules. Though simple lists of flags can be expressed by
+populating `flags` on a `pw_cc_flag_set`, `pw_cc_flag_group` provides additional
+power in the following two ways:
+
+ 1. Iteration and conditional expansion. Using `iterate_over`,
+ `expand_if_available`, and `expand_if_not_available`, more complex flag
+ expressions can be made. This is critical for implementing things like
+ the `libraries_to_link` feature, where library names are transformed
+ into flags that end up in the final link invocation.
+
+ Note: `expand_if_equal`, `expand_if_true`, and `expand_if_false` are not
+ yet supported.
+
+ 2. Flags are tool-independent. A `pw_cc_flag_group` expresses ordered flags
+ that may be reused across various `pw_cc_flag_set` rules. This is useful
+ for cases where multiple `pw_cc_flag_set` rules must be created to
+ implement a feature for which flags are slightly different depending on
+ the action (e.g. compile vs link). Common flags can be expressed in a
+ shared `pw_cc_flag_group`, and the differences can be relegated to
+ separate `pw_cc_flag_group` instances.
+
+Examples:
+
+ pw_cc_flag_group(
+ name = "user_compile_flag_expansion",
+ flags = ["%{user_compile_flags}"],
+ iterate_over = "user_compile_flags",
+ expand_if_available = "user_compile_flags",
+ )
+
+ # This flag_group might be referenced from various FDO-related
+ # `pw_cc_flag_set` rules. More importantly, the flag sets pulling this in
+ # may apply to different sets of actions.
+ pw_cc_flag_group(
+ name = "fdo_profile_correction",
+ flags = ["-fprofile-correction"],
+ expand_if_available = "fdo_profile_path",
+ )
+""",
+)
+
+def _pw_cc_flag_set_impl(ctx):
+ """Implementation for pw_cc_flag_set."""
+ if ctx.attr.flags and ctx.attr.flag_groups:
+ fail("{} specifies both `flag_groups` and `flags`, but only one can be specified. Consider splitting into two `pw_cc_flag_set` rules to make the intended order clearer.".format(ctx.label))
+ flag_groups = []
+ if ctx.attr.flags:
+ flag_groups.append(flag_group(flags = ctx.attr.flags))
+ elif ctx.attr.flag_groups:
+ for dep in ctx.attr.flag_groups:
+ if not dep[FlagGroupInfo]:
+ fail("{} in `flag_groups` of {} does not provide FlagGroupInfo".format(dep.label, ctx.label))
+
+ flag_groups = [dep[FlagGroupInfo] for dep in ctx.attr.flag_groups]
+ return flag_set(
+ actions = ctx.attr.actions,
+ flag_groups = flag_groups,
+ )
+
+pw_cc_flag_set = rule(
+ implementation = _pw_cc_flag_set_impl,
+ attrs = {
+ "actions": attr.string_list(
+ mandatory = True,
+ # inclusive-language: disable
+ doc = """A list of action names that this flag set applies to.
+
+Valid choices are listed here:
+
+ https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl
+
+It is possible for some needed action names to not be enumerated in this list,
+so there is not rigid validation for these strings. Prefer using constants
+rather than manually typing action names.
+""",
+ # inclusive-language: enable
+ ),
+ "flag_groups": attr.label_list(
+ doc = """Labels pointing to `pw_cc_flag_group` rules.
+
+This is intended to be compatible with any other rules that provide
+`FlagGroupInfo`. These are evaluated in order, with earlier flag groups
+appearing earlier in the invocation of the underlying tool.
+
+Note: `flag_groups` and `flags` are mutually exclusive.
+""",
+ ),
+ "flags": attr.string_list(
+ doc = """Flags that should be applied to the specified actions.
+
+These are evaluated in order, with earlier flags appearing earlier in the
+invocation of the underlying tool. If you need expansion logic, prefer
+enumerating flags in a `pw_cc_flag_group` or create a custom rule that provides
+`FlagGroupInfo`.
+
+Note: `flags` and `flag_groups` are mutually exclusive.
+""",
+ ),
+ },
+ provides = [FlagSetInfo],
+ doc = """Declares an ordered set of flags bound to a set of actions.
+
+Flag sets can be attached to a `pw_cc_toolchain` via `action_config_flag_sets`.
+
+Examples:
+
+ pw_cc_flag_set(
+ name = "warnings_as_errors",
+ flags = ["-Werror"],
+ )
+
+ pw_cc_flag_set(
+ name = "layering_check",
+ flag_groups = [
+ ":strict_module_headers",
+ ":dependent_module_map_files",
+ ],
+ )
+
+Note: In the vast majority of cases, alphabetical sorting is not desirable for
+the `flags` and `flag_groups` attributes. Buildifier shouldn't ever try to sort
+these, but in the off chance it starts to these members should be listed as
+exceptions in the `SortableDenylist`.
+""",
+)