Doxygen contributor’s guide#

This guide shows Upstream Pigweed maintainers how to contribute content to Pigweed’s C/C++ API reference, which is powered by Doxygen.

For Doxygen style rules, see Doxygen style guide.

Overview#

Most of pigweed.dev is built with Sphinx. The C/C++ API reference however is built with Doxygen. rules_doxygen orchestrates the Doxygen build (//docs/doxygen:build) hermetically within Bazel.

The C/C++ API reference is organized along the lines of Pigweed modules. This is achieved through Doxygen groups. The groups are defined in //docs/doxygen/modules.h. Header files are annotated with @module and @submodule (aliases defined in docs/doxygen/Doxyfile) to group APIs into their respective Pigweed modules.

Quickstart#

  1. In your module’s main BUILD.bazel file (e.g. //pw_bytes/BUILD.bazel) create a filegroup named doxygen. For srcs, list all Doxygen inputs:

    filegroup(
        name = "doxygen",
        srcs = [
            "public/pw_bytes/alignment.h",
            "public/pw_bytes/array.h",
            "public/pw_bytes/bit.h",
            "public/pw_bytes/byte_builder.h",
            "public/pw_bytes/endian.h",
            "public/pw_bytes/packed_ptr.h",
            "public/pw_bytes/span.h",
            "public/pw_bytes/suffix.h",
            "public/pw_bytes/units.h",
        ],
    )
    

    Important

    The Doxygen build is hermetic and all inputs are explicitly defined. If you forget to include a header at this stage, Doxygen won’t know that the header exists.

  2. Add your target to //docs/doxygen:srcs:

    filegroup(
        name = "srcs",
        srcs = [
            # …
            "//pw_bytes:doxygen",
            # …
        ]
    )
    
  3. In each of the headers that you’ve listed in your module’s doxygen target, add @module or @submodule annotations inside the innermost namespace that declares the public symbols (Doxygen won’t attach declarations inside a nested namespace block to an outer group). Always explicitly close with @endmodule or @endsubmodule.

    For simple modules where you want the entire API listed on a single page, use @module:

    namespace pw {
    
    /// @module{pw_foo}
    
    // …
    
    /// @endmodule
    
    }  // namespace pw
    

    For more complex modules with large APIs, use @submodule:

    namespace pw {
    
    /// @submodule{pw_bytes,ptr}
    
    // …
    
    /// @endsubmodule
    
    }  // namespace pw
    

    @submodule{pw_bytes,ptr} will organize the API under the pw_bytes_ptr group that’s defined in docs/doxygen/modules.h.

    The lack of whitespace between pw_bytes and ptr is important!

    Warning

    Doxygen publishes everything between @module / @submodule and @endmodule / @endsubmodule, even declarations that have no /// Doxygen comment. While symbols starting with _ (such as _PW_* macros) are automatically excluded by EXCLUDE_SYMBOLS in docs/doxygen/Doxyfile, generally keep internal and detail namespaces outside of @module / @submodule blocks (closing and reopening the block if needed rather than moving code around). Do not use @cond to hide internal APIs; reserve @cond for Doxygen parser workarounds. See Public API only.

  4. If your module uses @submodule, define its submodule groups (such as the pw_bytes_ptr subgroup shown below) in the manual section of docs/doxygen/modules.h after // categories-end:

    /// @defgroup pw_bytes_ptr Packed pointers
    /// @ingroup pw_bytes
    /// @brief Store data in unused pointer bits
    

    See pw_bytes to view the rendered example. The pw_bytes group controls the module’s API reference landing page. The main page is organized by “submodules”. The pw_bytes_ptr group is one of the submodules. On that page, all APIs related to packed pointers are presented.

    For non-module C/C++ APIs (such as //third_party/... integrations), attach the manual @defgroup directly to @ingroup category_<id> (see third_party_freertos in docs/doxygen/modules.h).

  5. Run bazelisk run //docs/common/metadata:sync to auto-generate your module’s top-level @defgroup entry in docs/doxygen/modules.h from //<module>/module.toml:

    /// @defgroup pw_bytes pw_bytes
    /// @ingroup category_encoding
    /// @brief Utilities for manipulating binary data
    
  6. Follow the style rules in Doxygen style guide.

Troubleshooting#

Each section below is a verbatim error message thrown by Doxygen. … represents variable text. The list at the start of a section explains how to resolve the error. The first item is the most probable fix, the second item the second most probable, etc.

General tips:

  • The tagfile generated at //bazel-bin/docs/doxygen/tag/index.tag indexes all documented public API symbols. Note that index.tag omits undocumented symbols that Doxygen still publishes on module group pages (group__*.html) when they appear inside @module or @submodule blocks.

  • Tweak the Doxygen config (//docs/doxygen/Doxyfile) as needed and then re-run the build.

… has @param documentation sections but no arguments#

  • Remove all @param documentation because the function has no args.

@copybrief or @copydoc target … not found#

  • Make sure that the target exists. It may have been removed recently.

  • Disambiguate the namespace of the target:

    -  /// @copydoc internal::GenericIntrusiveList<ItemBase>::clear
    +  /// @copydoc containers::internal::GenericIntrusiveList<ItemBase>::clear
    
  • Remove backticks from the target name.

    -  /// @copydoc `BucketBase::Remove`
    +  /// @copydoc BucketBase::Remove
    
  • Specify the exact signature of the target method.

    -  /// @copydoc Base::Method
    +  /// @copydoc Base::Method(int)
       void Method(int x);
    
  • Check if the target has a custom annotation and then look for that annotation in the PREDEFINED list in //docs/doxygen/Doxyfile. For example, the following @copydoc was technically correct:

    /// @copydoc MultiBufChunks::push_front
    void PushFrontChunk(OwnedChunk&& chunk) {
      MultiBufChunks::push_front(std::move(chunk));
    }
    

    The MultiBufChunks class did indeed have a push_front method. The issue was that MultiBufChunks was annotated with PW_MULTIBUF_DEPRECATED and Doxygen was instructed to ignore this definition. See Preprocessing.

    PREDEFINED             = … \
                             PW_MULTIBUF_DEPRECATED=
    

Argument … from the argument list of … has multiple @param documentation sections#

  • Document each @param name only once.

  • There must be a space between @param and the parameter name. The text in brackets (e.g. @param[*]) is only for specifying direction. The only 3 valid direction values are in, out, and in,out.

    -  /// @param[dest] The memory area to copy to.
    +  /// @param dest The memory area to copy to.
    

Argument … of command @param is not found in the argument list#

  • Match the @param name to the function signature name exactly.

    -  /// @param foo The integer...
    +  /// @param value The integer...
       constexpr size_t EncodedSize(T value)
    
  • If the function signature does not name the parameter, provide a name and use that for @param. Avoid unused parameter warnings by keeping it unnamed in the implementation.

    /// @param Status A status...
    
    -  void FinalizeRead(Status) override;
    +  void FinalizeRead(Status status) override;
    
  • Delete the @param documentation if the parameter has been recently removed from the code.

  • Use @tparam for template parameters, not @param.

    -  /// @param kMaxSize Size of the largest allocation...
    +  /// @tparam kMaxSize Size of the largest allocation...
       template <size_t kMaxRequests, size_t kMaxSize>
       auto ArbitraryRequests()
    
  • When using @copydoc, the function signatures need to match.

Detected potential recursive class relation#

  • Hide the inheritance from Doxygen using the @cond and @endcond directives. Also leave a comment pointing to b/513051956.

    template <typename T, size_t kCapacity>
    class MyClass
    // TODO: b/513051956 - Fix `recursive class relation` error
    /// @cond
        : public MyClass<T, kGenericSized>
    /// @endcond
    {
    

Note

This warning occurs when a class template inherits from another instantiation of itself (for example, a capacity-specific version inheriting from a generic-capacity version). Doxygen’s parser struggles to resolve template specializations and suspects circular inheritance.

Documented symbol … was not declared or defined#

  • Instruct Doxygen to expand or ignore the symbol by adding it to the PREDEFINED list in docs/doxygen/Doxyfile.

    To ignore the symbol (expand to nothing):

    PREDEFINED             = ... \
                             MY_SYMBOL(...)= \
    

    To define it to a specific value:

    PREDEFINED             = ... \
                             MY_SYMBOL(...)=value \
    

Note

Doxygen’s parser can get confused by macros that look like function calls or are placed in locations where they might resemble C++ syntax (e.g. trailing return types). This often happens when macro arguments contain complex C++ syntax (e.g., -> inside arguments).

Doxygen group '…' is not under any category#

  • Check @module{…} and @submodule{…} annotations for typos in the module or submodule name.

  • Place @module and @submodule inside the innermost namespace block that declares the public symbols. Placing @module outside a nested namespace creates an empty Doxygen group that sync won’t categorize.

  • If using @submodule, declare its @defgroup and @ingroup in the manual section of docs/doxygen/modules.h after // categories-end, then run bazelisk run //docs/common/metadata:sync.

End of file while inside a group#

  • Close all @module and @submodule groups with @endmodule or @endsubmodule.

  • Close all explicit group start markers (@{) with group end markers (@}).

  • If a file contains @defgroup and @{, check if the group should be removed from the header and instead defined centrally in //docs/doxygen/modules.h via @module or @submodule.

  • Ensure @cond and @endcond do not wrap a group start or end marker (@module, @endmodule, @submodule, @endsubmodule, @{, or @}). If a closing marker is inside a @cond block, Doxygen ignores it and reports an unclosed group at the end of the file.

  • Avoid C-style inline comments inside of @code blocks.

    No:

    /// @code{.cpp}
    ///   Layout MyGetLayoutFromPointer(const void* ptr) { /* ... */ }
    /// @endcode
    

    Yes:

    /// @code{.cpp}
    ///   Layout MyGetLayoutFromPointer(const void* ptr) {
    ///     …
    ///   }
    /// @endcode
    

Note

Pigweed does a lot of customization to force Doxygen to organize everything along the lines of Pigweed modules. The module-based organization is mainly achieved through the @module and @submodule groups defined in docs/doxygen/modules.h. These are custom aliases; see docs/doxygen/Doxyfile to understand what the aliases expand to. Most grouping errors are fixed by using @module and @submodule properly. Headers occasionally use explicit start and end markers (@{ and @}) to create custom groups, which can also lead to grouping errors.

Found ')' without opening '(' for trailing return type#

Found documented return type for … that does not return anything#

  • Do not use @return or @returns for constructors, destructors, or functions returning void.

Found recursive @copybrief or @copydoc relation for argument …#

  • Break the cycle by documenting one of the entities directly instead of using @copydoc or @copybrief, and have the other entity copy from the now-directly documented one.

Found unknown command#

  • Check Special Commands to verify that the command is valid.

  • Use @p <name> to refer to a parameter, or wrap <name> in single backticks. Do not use @<name> because Doxygen will interpret it as a command.

    -  /// Creates a combined view of a @code_point and its encoded @size.
    +  /// Creates a combined view of a @p code_point and its encoded `size`.
    
  • Do not use @important. Use @attention or @warning` instead. @important was introduced in a later version of Doxygen than what Pigweed uses.

    -  /// @important This is...
    +  /// @attention This is...
    

Include file … not found#

  • Make sure that all inputs that Doxygen depends on are explicitly declared in the doxygen target of a relevant BUILD.bazel file.

Missing title after \defgroup#

  • Usually, defgroup should not be used at all. Instead, define a @submodule in //docs/doxygen/modules.h.

  • If the defgroup must exist in the header for some reason, adding a title after the group ID should fix the missing title warning.

    -  ///   @defgroup foo
    +  ///   @defgroup foo Foo
    

No uniquely matching class member found#

  • If the error is related to a friend declaration, wrap the friend line in @cond and @endcond directives and add a comment explaining the suppression.

    +  // Suppress `no uniquely matching class member found` Doxygen error
    +  /// @cond
       friend typename Base::Base;
    +  /// @endcond
    
  • If the error is on out-of-line template method definitions at the bottom of a header (for example, methods defined on a template specialization like Vector<T, vector_impl::kGeneric>::clear()), wrap those out-of-line method definitions in @cond and @endcond.

Note

This error occurs when Doxygen’s parser fails to resolve a type or member name, often in friend declarations involving nested template aliases (e.g. friend typename Base::Base;), common names (e.g. friend internal::Channel<T>;), or out-of-line template specialization method definitions. Because Doxygen only needs the class’s in-line member declarations to generate public API documentation, you can safely hide these from Doxygen.

Refusing to add group … to itself#

  • Look for an extra @module or @submodule. E.g. 2 @module annotations but only 1 @endmodule.

Unbalanced grouping commands#

  • Check if another file in the same @module or @submodule group is not explicitly closing with @endmodule or @endsubmodule. The error may not actually be in the file that Doxygen is reporting.

Unsupported xml/html tag#

  • Wrap any text that uses angle brackets in single backticks to format it as inline code. Otherwise Doxygen will interpret the text as inline HTML or XML.

    -  /// Container-based version of the <algorithm> functions
    +  /// Container-based version of the `<algorithm>` functions