如何让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
相关产品推荐
相关产品推荐

