You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Bazel遍历指定目标的依赖图以生成Doxygen文档?

问题

我使用rules_doxygen生成C++项目文档,目前只能通过glob指定文件路径,要求文件必须集中存放。希望实现:指定目标(如//:main)后,Bazel自动遍历其完整依赖图(包括依赖的cc_library A、B、C),收集所有关联的头文件和源文件,传递给Doxygen生成文档。

项目依赖结构:

cc_binary MAIN - main.cpp
    |
cc_library  A  - A.cpp, A.hpp
    |
cc_library  B  - B.cpp, B.hpp
    |
cc_library  C  - C.cpp, C.hpp

当前Doxygen配置(仅支持glob指定文件):

doxygen(
    name = "doxygen",
    srcs = glob(["A/*.hpp", "B/*.hpp", "C/*.hpp"]),
    project_brief = "Example project for doxygen",
    project_name = "base",
    configurations = [
        "GENERATE_HTML = YES",
        "GENERATE_LATEX = NO",
        "EXTRACT_ALL = YES",
    ],
    tags = ["manual"]
)

期望自定义规则调用方式:

my_doxygen(
    name = "my_cool_docs",
    deps = [":main"]  # 支持跨包目标如//:main
)

执行命令:

bazel build //:my_cool_docs
# 或在docs目录下执行
bazel build //docs:my_cool_docs

现有代码仅能收集当前BUILD文件内的cc_library/cc_binary文件,无法遍历指定目标的完整依赖链。


解决方案

核心思路是利用Aspect递归遍历整个依赖图,收集所有关联的C++源文件和头文件,再将这些文件传递给底层的doxygen规则。

1. 编写递归收集文件的Aspect

在cc_srcs.bzl中定义Aspect,实现依赖链的文件收集:

# 自定义Provider,用于传递收集到的文件
CcSrcs = provider(
    fields = {
        "srcs": "depset类型,存储所有C++源文件",
        "hdrs": "depset类型,存储所有C++头文件",
    },
)

def _match_file_ext(f, extensions):
    return f.extension.lower() in extensions

def _extract_target_files(target_ctx, src_ext, hdr_ext):
    """提取单个目标的源文件和头文件"""
    srcs = []
    hdrs = []
    for attr_name in ["srcs", "hdrs"]:
        if hasattr(target_ctx.rule.attr, attr_name):
            for file_group in getattr(target_ctx.rule.attr, attr_name):
                for f in file_group.files.to_list():
                    if _match_file_ext(f, src_ext):
                        srcs.append(f)
                    elif _match_file_ext(f, hdr_ext):
                        hdrs.append(f)
    return srcs, hdrs

def _cc_srcs_aspect_impl(target, ctx):
    # 定义需要收集的文件后缀
    src_extensions = ["c", "cc", "cpp", "cxx", "c++"]
    hdr_extensions = ["h", "hh", "hpp", "hxx", "h++"]

    # 收集当前目标的文件
    current_srcs, current_hdrs = _extract_target_files(ctx, src_extensions, hdr_extensions)

    # 递归收集依赖目标的文件
    for dep in ctx.rule.attr.deps:
        if CcSrcs in dep:
            current_srcs.extend(dep[CcSrcs].srcs.to_list())
            current_hdrs.extend(dep[CcSrcs].hdrs.to_list())

    # 返回封装好的Provider
    return [CcSrcs(
        srcs = depset(current_srcs, order = "topological"),
        hdrs = depset(current_hdrs, order = "topological"),
    )]

# 关键配置:attr_aspects指定遍历目标的deps属性,实现递归依赖链遍历
cc_srcs_aspect = aspect(
    implementation = _cc_srcs_aspect_impl,
    attr_aspects = ["deps"],
)

2. 封装自定义my_doxygen规则

在docs.bzl中编写宏,整合文件收集和doxygen调用:

load("@rules_doxygen//doxygen:defs.bzl", "doxygen")
load("//:cc_srcs.bzl", "CcSrcs", "cc_srcs_aspect")

def _gather_docs_files_impl(ctx):
    """辅助规则:收集所有依赖的文件"""
    all_files = []
    for dep in ctx.attr.deps:
        if CcSrcs in dep:
            all_files.extend(dep[CcSrcs].srcs.to_list())
            all_files.extend(dep[CcSrcs].hdrs.to_list())
    return [DefaultInfo(files = depset(all_files))]

_gather_docs_files = rule(
    implementation = _gather_docs_files_impl,
    attrs = {
        "deps": attr.label_list(providers = [CcInfo], aspects = [cc_srcs_aspect]),
    },
)

def my_doxygen(name, deps, project_name = "My Project", project_brief = "", configurations = [], tags = ["manual"]):
    """自定义规则:自动遍历依赖链生成Doxygen文档"""
    # 第一步:收集所有依赖的文件
    _gather_docs_files(
        name = "%s_gather" % name,
        deps = deps,
    )

    # 第二步:调用底层doxygen规则生成文档
    doxygen(
        name = name,
        srcs = [":%s_gather" % name],
        project_name = project_name,
        project_brief = project_brief,
        configurations = configurations + [
            "EXTRACT_ALL = YES",
            "GENERATE_HTML = YES",
            "GENERATE_LATEX = NO",
        ],
        tags = tags,
    )

3. 使用示例

在项目BUILD文件中调用自定义规则:

load("//:docs.bzl", "my_doxygen")

# 原有目标定义
cc_binary(
    name = "main",
    srcs = ["main.cpp"],
    deps = [":A"],
)

cc_library(
    name = "A",
    srcs = ["A.cpp"],
    hdrs = ["A.hpp"],
    deps = [":B"],
)

cc_library(
    name = "B",
    srcs = ["B.cpp"],
    hdrs = ["B.hpp"],
    deps = [":C"],
)

cc_library(
    name = "C",
    srcs = ["C.cpp"],
    hdrs = ["C.hpp"],
)

# 生成文档
my_doxygen(
    name = "my_cool_docs",
    deps = [":main"],
    project_name = "My Cool Project",
    project_brief = "Full documentation for my C++ project",
)

执行构建命令生成文档:

bazel build //:my_cool_docs

关键技术点

  • Aspect的attr_aspects:指定遍历目标的deps属性,是实现递归依赖链遍历的核心。
  • 自定义Provider:用CcSrcs在Aspect和规则之间传递文件集合,避免重复收集。
  • 宏与规则配合:利用宏封装辅助规则和doxygen调用,简化用户使用流程。

内容的提问来源于stack exchange,提问作者tenspd137

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 01:42:03