.. _module-pw_toolchain-bazel:

===============================
Bazel build system integrations
===============================
Pigweed provides a suite of Bazel build integrations to compliment existing
Bazel toolchain constructs such as `rules_cc toolchains <https://github.com/bazelbuild/rules_cc/blob/main/cc/toolchains/README.md>`_
to make it easier to design robust, feature-rich toolchains.

.. _module-pw_toolchain-bazel-upstream-pigweed-toolchains:

---------------------------
Upstream Pigweed toolchains
---------------------------
Pigweed's C/C++ toolchains can be registered from a bzlmod project by adding
the following to your ``MODULE.bazel``:

.. code-block:: py

   register_toolchains(
       "@pigweed//pw_toolchain:cc_toolchain_cortex-m0",
       "@pigweed//pw_toolchain:cc_toolchain_cortex-m0plus",
       "@pigweed//pw_toolchain:cc_toolchain_cortex-m33",
       "@pigweed//pw_toolchain/arm_gcc:arm_gcc_cc_toolchain_cortex-m3",
       "@pigweed//pw_toolchain/arm_gcc:arm_gcc_cc_toolchain_cortex-m4",
       "@pigweed//pw_toolchain/host_clang:host_cc_toolchain_linux",
       "@pigweed//pw_toolchain/host_clang:host_cc_toolchain_macos",
       "@pigweed//pw_toolchain/riscv_clang:riscv_clang_cc_toolchain_rv32imc",
       "@pigweed//pw_toolchain/riscv_clang:riscv_clang_cc_toolchain_rv32imac",
       dev_dependency = True,
   )

If you create custom ARM or RISC-V toolchains, you may want to remove
Pigweed's device-specific toolchains to avoid them accidentally getting selected
for your device build.

.. admonition:: Note
   :class: warning

   Pigweed's upstream toolchains are subject to change without notice. If you
   would prefer more stability in toolchain configurations, consider declaring
   custom toolchains in your project.

Injecting custom flags
======================
If you wish to add extra arguments to Pigweed's toolchains, you can set
the ``--@pigweed//pw_toolchain/cc/args:extra_toolchain_args`` label flag
to point at a ``cc_args`` or ``cc_args_list`` rule that contains the flags
you'd like to add.

For platform-specific flags, you can set this flag in your ``platform`` flags
rather than in a ``.bazelrc`` file.

.. _module-pw_toolchain-bazel-layering-check:

Layering check
==============
Upstream Pigweed toolchains have support for `layering check
<https://maskray.me/blog/2022-09-25-layering-check-with-clang>`__. In short,
enabling layering check makes it a compile-time error to ``#include`` a header
that's not in the ``hdrs`` of a ``cc_library`` you directly depend on. This
produces cleaner dependency graphs and is recommended for all users.

.. admonition:: Note

   Layering check requires Bazel 8.0.0 or newer.

How to enable layering check?
-----------------------------
#. Add ``common --@pigweed//pw_toolchain/host_clang:layering_check`` to your
   ``.bazelrc``. This does not by itself enable the check, it only instructs
   Bazel to include support for it in the toolchain configuration. (This flag
   will become true by default and be removed once all known Pigweed users are
   on Bazel 8.)
#. Enable the ``layering_check`` feature to enable enforcement. The recommended
   way to do this is to add a `REPO.bazel
   <https://bazel.build/external/overview#repo.bazel>`__ file at the root of
   your project, with the following content:

   .. code-block:: py

      repo(
          features = ["layering_check"],
      )

   This will enable ``layering_check`` for all code in your project, but not for
   code coming from any external dependencies also built with Bazel.

Gradual rollout
---------------
When you enable layering check for the first time, you will likely get a very
large number of errors, since many packages in your project will contain
layering violations. If there are too many errors to fix at once, here's one way
to roll out ``layering_check`` gradually:

#. Enable the ``layering_check`` feature for the entire repo, as discussed
   above.
#. In the same commit, disable layering check for each individual package
   (``BUILD.bazel`` file). This can be done using the following `buildozer
   <https://github.com/bazelbuild/buildtools/blob/main/buildozer/README.md>`__
   one-liner:

   .. code-block:: console

      buildozer 'add features -layering_check' '//...:__pkg__'

   Note the ``-`` in front of ``layering_check`` in the command above!

#. Re-enable layering check package by package by removing the lines added by
   buildozer. When no ``-layering_check`` remains in your codebase, you've
   enabled layering check for the entire repo!

You can also enable or disable the ``layering_check`` feature for individual
targets, using the `features
<https://bazel.build/reference/be/common-definitions#common.features>`__
attribute.

Limitations
-----------
#. Layering check only applies to ``#include`` statements in source files, not
   in header files. To also apply it to header files, you need the
   ``parse_headers`` feature. Pigweed's toolchains do not yet contain its
   implementation. Adding it is tracked at :bug:`391367050`.
#. Layering check will not prevent you from using symbols from transitively
   included headers. For this, use `misc-include-cleaner
   <https://clang.llvm.org/extra/clang-tidy/checks/misc/include-cleaner.html>`__
   in clang-tidy.  See also :bug:`329671260`.
#. A pattern we use for swapping header implementations using a label flag
   leads to layering check violations. Figuring out an alternative pattern is
   tracked at :bug:`391394448`.

.. _module-pw_toolchain-bazel-clang-tidy:

clang-tidy
==========
To integrate Pigweed's toolchain with `bazel_clang_tidy
<https://github.com/erenon/bazel_clang_tidy>`_:

#. Add a ``.clang-tidy`` file at the root of your repository listing the checks
   you wish to enable. `Pigweed's own .clang-tidy file
   <https://cs.opensource.google/pigweed/pigweed/+/main:.clang-tidy>`__ shows
   some checks we recommend.

#. Create a ``filegroup`` target containing that file in ``BUILD.bazel`` at
   the root of your repo.

   .. code-block:: python

      filegroup(
         name = "clang_tidy_config",
         srcs = [".clang-tidy"],
      )

#. Add `bazel_clang_tidy`_ to your ``MODULE.bazel``.

   .. code-block::python

      git_repository = use_repo_rule(
         "@bazel_tools//tools/build_defs/repo:git.bzl",
         "git_repository",
      )
      git_repository(
         name = "bazel_clang_tidy",
         # Check the repository for the latest version!
         commit = "db677011c7363509a288a9fb3bf0a50830bbf791",
         remote = "https://github.com/erenon/bazel_clang_tidy.git",
      )

#. Add a ``clang-tidy`` config in your ``.bazelrc`` file.

   .. code-block:: python

      # clang-tidy configuration
      build:clang-tidy --aspects @bazel_clang_tidy//clang_tidy:clang_tidy.bzl%clang_tidy_aspect
      build:clang-tidy --output_groups=report
      build:clang-tidy --@bazel_clang_tidy//:clang_tidy_config=//:clang_tidy_config
      # Use the clang-tidy executable from Pigweed's toolchain, and include
      # our sysroot headers.
      build:clang-tidy --@bazel_clang_tidy//:clang_tidy_executable=@pigweed//pw_toolchain/host_clang:copy_clang_tidy
      build:clang-tidy --@bazel_clang_tidy//:clang_tidy_additional_deps=@pigweed//pw_toolchain/host_clang:sysroot_root
      # Skip any targets with tags = ["noclangtidy"]. This allows a gradual
      # rollout.
      build:clang-tidy --build_tag_filters=-noclangtidy
      # We need to disable this warning to avoid spurious "#pragma once in main file"
      # warnings for header-only libraries. For another approach, see
      # https://github.com/mongodb-forks/bazel_clang_tidy/pull/2
      build:clang-tidy --copt=-Wno-pragma-once-outside-header

Now ``bazelisk build --config=clang-tidy //...`` will run clang-tidy for all
``cc_library`` targets in your repo!

As an example of this setup, see `the CL that added clang-tidy support to our
Quickstart repo <http://pwrev.dev/266934>`__.

Conversion warnings
===================
By default, upstream Pigweed is built with `-Wconversion
<https://clang.llvm.org/docs/DiagnosticsReference.html#wconversion>`__ enabled.
However, this was not always the case, and many Pigweed targets contain
``-Wconversion`` violations. (:bug:`259746255` tracks fixing all of these.)

Upstream allowlist
------------------
Do not add new ``-Wconversion`` violations to the Pigweed codebase.

If you write new code that fails to build because it includes a header with a
pre-existing ``-Wconversion`` violation, try to fix the pre-existing violation.

As a last resort, you may add the ``features = ["-conversion_warnings"]`` (note
the ``-``!) attribute to your ``cc_library`` or other build target:

.. code-block:: py

   cc_library(
      name = "…",
      features = ["-conversion_warnings"],
   )

This will disable ``-Wconversion`` for this target.

Downstream use
--------------
If you would like to enable ``-Wconversion`` in a downstream project that uses
Pigweed's toolchains, add a `REPO.bazel
<https://bazel.build/external/overview#repo.bazel>`__ file at the root of
your project, with the following content:

.. code-block:: py

   repo(
       features = ["conversion_warnings"],
   )

This will enable ``-Wconversion`` for all code in your project, but not for
code coming from any external dependencies also built with Bazel.

.. _module-pw_toolchain-bazel-compiler-specific-logic:

-----------------------------
Compiler-specific build logic
-----------------------------
Whenever possible, avoid introducing compiler-specific behaviors in Bazel
``BUILD`` files. Instead, prefer to design build logic against
more intentional :ref:`docs-bazel-compatibility`. For compiler-specific
behavior, this means defining and/or using compiler capabilities like
`@rules_cc//cc/toolchains/capabilities:supports_interface_shared_libraries <https://github.com/bazelbuild/rules_cc/blob/main/cc/toolchains/capabilities/BUILD>`__

If you need to expose a toolchain capability as a choice in a select, you
can use ``pw_cc_toolchain_feature_is_enabled``.

Example:

.. code-block:: py

   load(
       "@pigweed//pw_toolchain/cc/current_toolchain:pw_cc_toolchain_feature_is_enabled.bzl",
       "pw_cc_toolchain_feature_is_enabled",
   )

   pw_cc_toolchain_feature_is_enabled(
       name = "llvm_libc_enabled",
       feature_name = "llvm_libc",
   )

   cc_library(
       name = "libfoo",
       deps = select({
           ":llvm_libc_enabled": ["//foo:llvm_libc_extras"],
           "//conditions:default": [],
       }),
   )

If you absolutely must introduce a ``select`` statement that checks the current
compiler, use Pigweed's helper macros.

Example:

.. code-block:: py

   load(
       "@pigweed//pw_toolchain/cc/current_toolchain:conditions.bzl",
       "if_compiler_is_clang",
       "if_linker_is_gcc",
   )

   cc_library(
       copts = if_compiler_is_clang(
           ["-fno-codegen"],
           otherwise = [],
       ),
       linkopts = if_linker_is_gcc(
           ["-Wl,--delete-main"],
           otherwise = [],
       ),
       srcs = ["lib.cc"],
   )

---------------------------------
Compiler-specific toolchain flags
---------------------------------
In cases where foundationally different toolchains (e.g. Clang, GCC, MSVC) share
large pieces of project-wide configuration, you may want to conditionally add
flags that are compiler-specific. This can be done with the following steps:

#. **Express the type of compiler in your cc_toolchain.** This is how the
   rest of your toolchain rules will know what kind of compiler is active.

   .. code-block:: py

      cc_toolchain(
          name = "arm_clang_toolchain_cortex-a",
          # ...
          enabled_features = [
              "@pigweed//pw_toolchain/cc/capability:compiler_is_clang",
              "@pigweed//pw_toolchain/cc/capability:linker_is_clang",
          ],
      )

#. **Add the list of known toolchain types to your toolchain.** This ensures
   that the ``cc_toolchain`` passes feature correctness validations.

   .. code-block:: py

      cc_toolchain(
          name = "arm_clang_toolchain_cortex-a",
          # ...
          enabled_features = [
              "@pigweed//pw_toolchain/cc/capability:compiler_is_clang",
              "@pigweed//pw_toolchain/cc/capability:linker_is_clang",
          ],
          known_features = [
              "//pw_toolchain/cc/capability:known_toolchain_types",
          ],
      )

#. **Gate the arguments with requires_any_of.** ``cc_args`` gated by
   a ``requires_any_of`` constraint on a toolchain type will only be expanded
   in the compiler/linker invocation if the toolchain tool matches the required
   type. Keep in mind that ``compiler_is_clang`` and ``linker_is_clang`` types
   are offered separately to support cases where the compiler and linker types
   are not the same.

   .. code-block:: py

      cc_args(
          name = "clang_only_extra_pigweed_warnings",
          actions = [
              "@rules_cc//cc/toolchains/actions:compile_actions",
          ],
          requires_any_of = [
              "//pw_toolchain/cc/capability:compiler_is_clang",
          ],
          args = [
              "-Wshadow-all",
          ],
      )
