diff options
author | Armando Montanez <amontanez@google.com> | 2023-11-15 03:20:51 +0000 |
---|---|---|
committer | CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2023-11-15 03:20:51 +0000 |
commit | 3c3dc003812d05bb31a47b93ff1df5666ad3db7f (patch) | |
tree | c59b9ba131df562f19581320098b3f83a229a6cf | |
parent | 6372cdef44d0338d47108c90c55ad38debcf6c30 (diff) | |
download | pigweed-3c3dc003812d05bb31a47b93ff1df5666ad3db7f.tar.gz |
pw_toolchain_bazel: Introduce pw_cc_flag_set and pw_cc_flag_group
As the first step of implementing toolchains as proposed in SEED-0113,
this CL introduces pw_cc_flag_set and pw_cc_flag_group. These will be
the core building blocks for specifying flags that will be bound to
toolchains.
As an illustrative example, moves the warning "features" to be
pw_cc_flag_set rules instead.
Bug: b/309533028
Change-Id: I3eaa6ac92511d2973bd5120432878d6953d14793
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/179932
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Commit-Queue: Armando Montanez <amontanez@google.com>
Reviewed-by: Kayce Basques <kayce@google.com>
-rw-r--r-- | pw_toolchain/host_clang/BUILD.bazel | 27 | ||||
-rw-r--r-- | pw_toolchain_bazel/BUILD.gn | 5 | ||||
-rw-r--r-- | pw_toolchain_bazel/api.rst | 298 | ||||
-rw-r--r-- | pw_toolchain_bazel/cc_toolchain/defs.bzl | 23 | ||||
-rw-r--r-- | pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl | 63 | ||||
-rw-r--r-- | pw_toolchain_bazel/cc_toolchain/private/flag_set.bzl | 225 | ||||
-rw-r--r-- | pw_toolchain_bazel/cc_toolchain/private/utils.bzl | 24 | ||||
-rw-r--r-- | pw_toolchain_bazel/docs.rst | 51 |
8 files changed, 687 insertions, 29 deletions
diff --git a/pw_toolchain/host_clang/BUILD.bazel b/pw_toolchain/host_clang/BUILD.bazel index bc2fb1ff5..7f8009787 100644 --- a/pw_toolchain/host_clang/BUILD.bazel +++ b/pw_toolchain/host_clang/BUILD.bazel @@ -14,6 +14,9 @@ load( "@pw_toolchain//cc_toolchain:defs.bzl", + "ALL_CPP_COMPILER_ACTIONS", + "ALL_C_COMPILER_ACTIONS", + "pw_cc_flag_set", "pw_cc_toolchain", "pw_cc_toolchain_feature", ) @@ -56,9 +59,10 @@ pw_cc_toolchain_feature( # Although we use similar warnings for clang and arm_gcc, we don't have one # centralized list, since we might want to use different warnings based on the # compiler in the future. -pw_cc_toolchain_feature( +pw_cc_flag_set( name = "warnings", - copts = [ + actions = ALL_C_COMPILER_ACTIONS + ALL_CPP_COMPILER_ACTIONS, + flags = [ "-Wall", "-Wextra", # Make all warnings errors, except for the exemptions below. @@ -68,9 +72,10 @@ pw_cc_toolchain_feature( ], ) -pw_cc_toolchain_feature( +pw_cc_flag_set( name = "no_unknown_warning_option", - copts = [ + actions = ALL_C_COMPILER_ACTIONS + ALL_CPP_COMPILER_ACTIONS, + flags = [ "-Wno-unknown-warning-option", ], ) @@ -87,6 +92,9 @@ pw_cc_toolchain( name = "host_toolchain_macos", abi_libc_version = "unknown", abi_version = "unknown", + action_config_flag_sets = [ + ":warnings", + ], all_files = "@llvm_toolchain//:all", ar = "@llvm_toolchain//:bin/llvm-ar", ar_files = "@llvm_toolchain//:all", @@ -97,7 +105,6 @@ pw_cc_toolchain( cpp = "@llvm_toolchain//:bin/clang++", dwp_files = "@llvm_toolchain//:all", feature_deps = [ - ":warnings", "@pw_toolchain//features:no_default_cpp_stdlib", ":macos_stdlib", "@pw_toolchain//features/macos:macos_sysroot", @@ -145,6 +152,9 @@ pw_cc_toolchain( name = "host_toolchain_linux", abi_libc_version = "unknown", abi_version = "unknown", + action_config_flag_sets = [ + ":warnings", + ], all_files = ":all_linux_files", ar = "@llvm_toolchain//:bin/llvm-ar", ar_files = ":all_linux_files", @@ -155,7 +165,6 @@ pw_cc_toolchain( cpp = "@llvm_toolchain//:bin/clang++", dwp_files = ":all_linux_files", feature_deps = [ - ":warnings", ":linux_sysroot", "@pw_toolchain//features:c++17", "@pw_toolchain//features:debugging", @@ -188,6 +197,10 @@ pw_cc_toolchain( name = "host_toolchain_linux_kythe", abi_libc_version = "unknown", abi_version = "unknown", + action_config_flag_sets = [ + ":warnings", + ":no_unknown_warning_option", + ], all_files = ":all_linux_files", ar = "@llvm_toolchain//:bin/llvm-ar", ar_files = ":all_linux_files", @@ -198,8 +211,6 @@ pw_cc_toolchain( cpp = "@llvm_toolchain//:bin/clang++", dwp_files = ":all_linux_files", feature_deps = [ - ":warnings", - ":no_unknown_warning_option", ":linux_sysroot", "@pw_toolchain//features:c++17", "@pw_toolchain//features:debugging", diff --git a/pw_toolchain_bazel/BUILD.gn b/pw_toolchain_bazel/BUILD.gn index 4537bcc74..bf6234783 100644 --- a/pw_toolchain_bazel/BUILD.gn +++ b/pw_toolchain_bazel/BUILD.gn @@ -24,5 +24,8 @@ pw_test_group("tests") { } pw_doc_group("docs") { - sources = [ "docs.rst" ] + sources = [ + "api.rst", + "docs.rst", + ] } diff --git a/pw_toolchain_bazel/api.rst b/pw_toolchain_bazel/api.rst new file mode 100644 index 000000000..e0e61d33f --- /dev/null +++ b/pw_toolchain_bazel/api.rst @@ -0,0 +1,298 @@ +.. _module-pw_toolchain_bazel-api: + +============= +API reference +============= + +.. py:class:: pw_cc_toolchain + + This rule is the core of a C/C++ toolchain definition. Critically, it is + intended to fully specify the following: + + * Which tools to use for various compile/link actions. + * Which `well-known features <https://bazel.build/docs/cc-toolchain-config-reference#wellknown-features>`_ + are supported. + * Which flags to apply to various actions. + + .. py:attribute:: feature_deps + :type: List[label] + + ``pw_cc_toolchain_feature`` labels that provide features for this toolchain. + + .. py:attribute:: ar + :type: File + + Path to the tool to use for ``ar`` (static link) actions. + + .. py:attribute:: cpp + :type: File + + Path to the tool to use for C++ compile actions. + + .. py:attribute:: gcc + :type: File + + Path to the tool to use for C compile actions. + + .. py:attribute:: gcov + :type: File + + Path to the tool to use for generating code coverage data. + + .. py:attribute:: ld + :type: File + + Path to the tool to use for link actions. + + .. py:attribute:: strip + :type: File + + Path to the tool to use for strip actions. + + .. py:attribute:: objcopy + :type: File + + Path to the tool to use for objcopy actions. + + .. py:attribute:: objdump + :type: File + + Path to the tool to use for objdump actions. + + .. py:attribute:: action_config_flag_sets + :type: List[label] + + List of flag sets to apply to the respective ``action_config``\s. The vast + majority of labels listed here will point to :py:class:`pw_cc_flag_set` + rules. + + .. py:attribute:: toolchain_identifier + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: host_system_name + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: target_system_name + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: target_cpu + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: target_libc + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: compiler + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: abi_version + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: abi_libc_version + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + + .. py:attribute:: cc_target_os + :type: str + + See `cc_common.create_cc_toolchain_config_info() <https://bazel.build/rules/lib/toplevel/cc_common#create_cc_toolchain_config_info>`_\. + +.. py:class:: pw_cc_flag_set + + Declares an ordered set of flags bound to a set of actions. + + Flag sets can be attached to a :py:class:`pw_cc_toolchain` via + :py:attr:`pw_cc_toolchain.action_config_flag_sets`\. + + Examples: + + .. code-block:: py + + 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", + ], + ) + + .. inclusive-language: disable + + Note: In the vast majority of cases, alphabetical sorting is not desirable + for the :py:attr:`pw_cc_flag_set.flags` and + :py:attr:`pw_cc_flag_set.flag_groups` attributes. + `Buildifier <https://github.com/bazelbuild/buildtools/blob/master/buildifier/README.md>`_ + 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``. + + .. inclusive-language: enable + + .. py:attribute:: actions + :type: List[str] + + A list of action names that this flag set applies to. + + .. inclusive-language: disable + + Valid choices are listed at + `@rules_cc//cc:action_names.bzl <https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl>`_\. + + .. inclusive-language: enable + + 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. + + .. py:attribute:: flags + :type: List[str] + + 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 :py:class:`pw_cc_flag_group` or create a custom + rule that provides ``FlagGroupInfo``. + + Note: :py:attr:`pw_cc_flag_set.flags` and + :py:attr:`pw_cc_flag_set.flag_groups` are mutually exclusive. + + .. py:attribute:: flag_groups + :type: List[label] + + Labels pointing to :py:class:`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: :py:attr:`pw_cc_flag_set.flag_groups` and + :py:attr:`pw_cc_flag_set.flags` are mutually exclusive. + +.. py:class:: pw_cc_flag_group + + Declares an (optionally parametric) ordered set of flags. + + :py:class:`pw_cc_flag_group` rules are expected to be consumed exclusively by + :py:class:`pw_cc_flag_set` rules. Though simple lists of flags can be + expressed by populating ``flags`` on a :py:class:`pw_cc_flag_set`, + :py:class:`pw_cc_flag_group` provides additional power in the following two + ways: + + 1. Iteration and conditional expansion. Using + :py:attr:`pw_cc_flag_group.iterate_over`, + :py:attr:`pw_cc_flag_group.expand_if_available`\, and + :py:attr:`pw_cc_flag_group.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 :py:class:`pw_cc_flag_group` expresses + ordered flags that may be reused across various + :py:class:`pw_cc_flag_set` rules. This is useful for cases where multiple + :py:class:`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 + :py:class:`pw_cc_flag_group`, and the differences can be relegated to + separate :py:class:`pw_cc_flag_group` instances. + + Examples: + + .. code-block:: py + + 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", + ) + + .. py:attribute:: flags + :type: List[str] + + 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 <https://bazel.build/rules/language>`_ that provides + ``FlagGroupInfo`` or ``FlagSetInfo``. + + + .. py:attribute:: iterate_over + :type: str + + Expands :py:attr:`pw_cc_flag_group.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, + :py:attr:`pw_cc_flag_group.iterate_over` must be used to expand the list into a series of flags. + + Note that :py:attr:`pw_cc_flag_group.iterate_over` is the string name of a + build variable, and not an actual list. Valid options are listed in the + `C++ Toolchain Configuration <https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables>`_ + reference. + + + + Note that the flag expansion stamps out the entire list of flags in + :py:attr:`pw_cc_flag_group.flags` once for each item in the list. + + Example: + + .. code-block:: py + + # 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", + ) + + .. py:attribute:: expand_if_available + :type: str + + Expands the expression in :py:attr:`pw_cc_flag_group.flags` if the + specified build variable is set. + + .. py:attribute:: expand_if_not_available + :type: str + + Expands the expression in :py:attr:`pw_cc_flag_group.flags` if the + specified build variable is **NOT** set. diff --git a/pw_toolchain_bazel/cc_toolchain/defs.bzl b/pw_toolchain_bazel/cc_toolchain/defs.bzl index 7847c4eb2..b5340c54b 100644 --- a/pw_toolchain_bazel/cc_toolchain/defs.bzl +++ b/pw_toolchain_bazel/cc_toolchain/defs.bzl @@ -20,15 +20,38 @@ load( _pw_cc_toolchain = "pw_cc_toolchain", ) load( + "//cc_toolchain/private:flag_set.bzl", + _pw_cc_flag_group = "pw_cc_flag_group", + _pw_cc_flag_set = "pw_cc_flag_set", +) +load( "//cc_toolchain/private:toolchain_feature.bzl", _pw_cc_toolchain_feature = "pw_cc_toolchain_feature", ) +load( + "//cc_toolchain/private:utils.bzl", + _ALL_AR_ACTIONS = "ALL_AR_ACTIONS", + _ALL_ASM_ACTIONS = "ALL_ASM_ACTIONS", + _ALL_CPP_COMPILER_ACTIONS = "ALL_CPP_COMPILER_ACTIONS", + _ALL_C_COMPILER_ACTIONS = "ALL_C_COMPILER_ACTIONS", + _ALL_LINK_ACTIONS = "ALL_LINK_ACTIONS", +) + +ALL_ASM_ACTIONS = _ALL_ASM_ACTIONS +ALL_C_COMPILER_ACTIONS = _ALL_C_COMPILER_ACTIONS +ALL_CPP_COMPILER_ACTIONS = _ALL_CPP_COMPILER_ACTIONS +ALL_LINK_ACTIONS = _ALL_LINK_ACTIONS +ALL_AR_ACTIONS = _ALL_AR_ACTIONS # TODO(b/301004620): Remove when bazel 7 is released and this constant exists in # ACTION_NAMES OBJ_COPY_ACTION_NAME = _OBJ_COPY_ACTION_NAME OBJ_DUMP_ACTION_NAME = _OBJ_DUMP_ACTION_NAME +pw_cc_flag_group = _pw_cc_flag_group +pw_cc_flag_set = _pw_cc_flag_set + pw_cc_toolchain = _pw_cc_toolchain +# TODO: b/309533028 - This is deprecated, and will soon be removed. pw_cc_toolchain_feature = _pw_cc_toolchain_feature diff --git a/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl b/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl index 85c6787bd..d36ec8e96 100644 --- a/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl +++ b/pw_toolchain_bazel/cc_toolchain/private/cc_toolchain.bzl @@ -16,6 +16,7 @@ load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", + "FlagSetInfo", "action_config", "feature", "flag_group", @@ -42,15 +43,16 @@ OBJ_COPY_ACTION_NAME = "objcopy_embed_data" OBJ_DUMP_ACTION_NAME = "objdump_embed_data" PW_CC_TOOLCHAIN_CONFIG_ATTRS = { - "feature_deps": "pw_cc_toolchain_feature labels that provide features for this toolchain", - "ar": "Path to the tool to use for ar (static link) actions", + "feature_deps": "`pw_cc_toolchain_feature` labels that provide features for this toolchain", + "ar": "Path to the tool to use for `ar` (static link) actions", "cpp": "Path to the tool to use for C++ compile actions", "gcc": "Path to the tool to use for C compile actions", - "gcov": "Pah to the tool to use for generating code coverag data", + "gcov": "Path to the tool to use for generating code coverage data", "ld": "Path to the tool to use for link actions", "strip": "Path to the tool to use for strip actions", "objcopy": "Path to the tool to use for objcopy actions", "objdump": "Path to the tool to use for objdump actions", + "action_config_flag_sets": "List of flag sets to apply to the respective `action_config`s", # Attributes originally part of create_cc_toolchain_config_info. "toolchain_identifier": "See documentation for cc_common.create_cc_toolchain_config_info()", @@ -77,12 +79,13 @@ PW_CC_TOOLCHAIN_BLOCKED_ATTRS = { "builtin_sysroot": "Use a pw_cc_toolchain_feature to add a builtin_sysroot", } -def _action_configs(action_tool, action_list): +def _action_configs(action_tool, action_list, flag_sets_by_action): """Binds a tool to an action. Args: action_tool (File): Tool to bind to the specified actions. action_list (List[str]): List of actions to bind to the specified tool. + flag_sets_by_action: Dictionary mapping action names to lists of applicable flag sets. Returns: action_config: A action_config binding the provided tool to the @@ -96,6 +99,7 @@ def _action_configs(action_tool, action_list): tool = action_tool, ), ], + flag_sets = flag_sets_by_action.get(action, default = []), ) for action in action_list ] @@ -180,6 +184,40 @@ def _archiver_flags(is_mac): else: return ["rcsD"] +def _strip_actions(flag_set_to_copy): + """Copies a flag_set, stripping `actions`. + + Args: + flag_set_to_copy: The base flag_set to copy. + Returns: + flag_set with empty `actions` list. + """ + return flag_set( + with_features = flag_set_to_copy.with_features, + flag_groups = flag_set_to_copy.flag_groups, + ) + +def _create_action_flag_set_map(flag_sets): + """Creates a mapping of action names to flag sets. + + Args: + flag_sets: the flag sets to expand. + Returns: + Dictionary mapping action names to lists of FlagSetInfo providers. + """ + flag_sets_by_action = {} + for fs in flag_sets: + handled_actions = {} + for action in fs.actions: + if action not in flag_sets_by_action: + flag_sets_by_action[action] = [] + + # Dedupe action set list. + if action not in handled_actions: + handled_actions[action] = True + flag_sets_by_action[action].append(_strip_actions(fs)) + return flag_sets_by_action + def _pw_cc_toolchain_config_impl(ctx): """Rule that provides a CcToolchainConfigInfo. @@ -190,11 +228,14 @@ def _pw_cc_toolchain_config_impl(ctx): CcToolchainConfigInfo """ check_deps(ctx) + + flag_sets_by_action = _create_action_flag_set_map([dep[FlagSetInfo] for dep in ctx.attr.action_config_flag_sets]) + all_actions = [] - all_actions += _action_configs(ctx.executable.gcc, ALL_ASM_ACTIONS) - all_actions += _action_configs(ctx.executable.gcc, ALL_C_COMPILER_ACTIONS) - all_actions += _action_configs(ctx.executable.cpp, ALL_CPP_COMPILER_ACTIONS) - all_actions += _action_configs(ctx.executable.cpp, ALL_LINK_ACTIONS) + all_actions += _action_configs(ctx.executable.gcc, ALL_ASM_ACTIONS, flag_sets_by_action) + all_actions += _action_configs(ctx.executable.gcc, ALL_C_COMPILER_ACTIONS, flag_sets_by_action) + all_actions += _action_configs(ctx.executable.cpp, ALL_CPP_COMPILER_ACTIONS, flag_sets_by_action) + all_actions += _action_configs(ctx.executable.cpp, ALL_LINK_ACTIONS, flag_sets_by_action) all_actions += [ action_config( @@ -205,6 +246,7 @@ def _pw_cc_toolchain_config_impl(ctx): tool = ctx.executable.ar, ), ], + flag_sets = flag_sets_by_action.get(ACTION_NAMES.cpp_link_static_library, default = []), ), action_config( action_name = ACTION_NAMES.llvm_cov, @@ -213,6 +255,7 @@ def _pw_cc_toolchain_config_impl(ctx): tool = ctx.executable.gcov, ), ], + flag_sets = flag_sets_by_action.get(ACTION_NAMES.llvm_cov, default = []), ), action_config( action_name = OBJ_COPY_ACTION_NAME, @@ -221,6 +264,7 @@ def _pw_cc_toolchain_config_impl(ctx): tool = ctx.executable.objcopy, ), ], + flag_sets = flag_sets_by_action.get(OBJ_COPY_ACTION_NAME, default = []), ), action_config( action_name = OBJ_DUMP_ACTION_NAME, @@ -229,6 +273,7 @@ def _pw_cc_toolchain_config_impl(ctx): tool = ctx.executable.objdump, ), ], + flag_sets = flag_sets_by_action.get(OBJ_DUMP_ACTION_NAME, default = []), ), action_config( action_name = ACTION_NAMES.strip, @@ -237,6 +282,7 @@ def _pw_cc_toolchain_config_impl(ctx): tool = ctx.executable.strip, ), ], + flag_sets = flag_sets_by_action.get(ACTION_NAMES.strip, default = []), ), ] @@ -284,6 +330,7 @@ pw_cc_toolchain_config = rule( "objcopy": attr.label(allow_single_file = True, executable = True, cfg = "exec"), "objdump": attr.label(allow_single_file = True, executable = True, cfg = "exec"), "strip": attr.label(allow_single_file = True, executable = True, cfg = "exec"), + "action_config_flag_sets": attr.label_list(), # Attributes from create_cc_toolchain_config_info. "toolchain_identifier": attr.string(), 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`. +""", +) diff --git a/pw_toolchain_bazel/cc_toolchain/private/utils.bzl b/pw_toolchain_bazel/cc_toolchain/private/utils.bzl index 321cafad4..7030f2498 100644 --- a/pw_toolchain_bazel/cc_toolchain/private/utils.bzl +++ b/pw_toolchain_bazel/cc_toolchain/private/utils.bzl @@ -15,6 +15,10 @@ load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load( + "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", + "FlagSetInfo", +) +load( "//cc_toolchain/private:providers.bzl", "ToolchainFeatureInfo", ) @@ -52,12 +56,18 @@ ACTION_MAP = { "linkopts": ALL_LINK_ACTIONS, } +def _check_dep_provides(ctx_label, dep, provider, what_provides): + if provider not in dep: + fail( + "{} listed as a dependency of {}, but it's not a {}".format( + dep.label, + ctx_label, + what_provides, + ), + ) + def check_deps(ctx): for dep in ctx.attr.feature_deps: - if ToolchainFeatureInfo not in dep: - fail( - "{} listed as a dependency of {}, but it's not a pw_cc_toolchain_feature".format( - dep.label, - ctx.label, - ), - ) + _check_dep_provides(ctx.label, dep, ToolchainFeatureInfo, "pw_cc_toolchain_feature") + for dep in ctx.attr.action_config_flag_sets: + _check_dep_provides(ctx.label, dep, FlagSetInfo, "pw_cc_flag_set") diff --git a/pw_toolchain_bazel/docs.rst b/pw_toolchain_bazel/docs.rst index 9c885c14f..075dc3f7b 100644 --- a/pw_toolchain_bazel/docs.rst +++ b/pw_toolchain_bazel/docs.rst @@ -3,16 +3,57 @@ ================== pw_toolchain_bazel ================== -This module provides building blocks for Bazel's ``cc_toolchain`` API in a way -that increases modularity and reusability. While this module does NOT provide a -hermetic toolchain, Pigweed does provide fully instantiated and supported -toolchains as part of ``pw_toolchain``. + +.. pigweed-module:: + :name: pw_toolchain_bazel + :tagline: Modular Bazel C/C++ toolchain API + :status: unstable + :languages: Starlark + +Assembling a complete, hermetic toolchain with Bazel using the native primitives +can be quite challenging. Additionally, Bazel's native API for declaring C/C++ +toolchains doesn't inherently encourage modularity or reusability. + +``pw_toolchain_bazel`` provides a suite of building blocks that make the process +of assembling a complete, hermetic toolchain significantly easier. The Bazel +rules introduced by this module push the vast majority of a toolchain's +declaration into build files, and encourages reusability through sharing of +flag groups, tools, and toolchain feature implementations. + +While this module does **not** provide a hermetic toolchain, Pigweed provides +`fully instantiated and supported toolchains <https://cs.opensource.google/pigweed/pigweed/+/main:pw_toolchain/host_clang/BUILD.bazel>`_ +that are a useful reference for building your own toolchain. .. warning:: - This module is under construction and is subject to major breaking changes. + `b/309533028 <https://issues.pigweed.dev/309533028>`_\: This module is under + construction and is subject to major breaking changes. + +.. grid:: 1 + + .. grid-item-card:: :octicon:`info` API reference + :link: module-pw_toolchain_bazel-api + :link-type: ref + :class-item: sales-pitch-cta-primary + + Detailed reference information about the pw_toolchain_bazel API. + +.. grid:: 1 + + .. grid-item-card:: :octicon:`file` Original SEED + :link: seed-0113 + :link-type: ref + :class-item: sales-pitch-cta-secondary + + SEED-0113: Add modular Bazel C/C++ toolchain API ------------ Dependencies ------------ This module is not permitted to have dependencies on other modules. When this module stabilizes, it will be broken out into a separate repository. + +.. toctree:: + :hidden: + :maxdepth: 1 + + API reference <api> |