tenspd137
tenspd137

Reputation: 393

How to get bazel to walk the dependecy graph of a target?

I have been using https://github.com/TendTo/rules_doxygen/tree/main from the BCR to generate docs. I am trying to figure out how to get bazel to walk the dependency graph of a target so I can extract a list of all the headers to feed to doxygen for documentation. So far, I have only been able to get doxygen to work if I specify everything in a glob - but that kind of assumes everything sits in one place. I'd like to have something like:

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

Now - something like this works:

doxygen(
    name = "doxygen",   # Name of the rule, can be anything
    srcs = glob(["A/*.hpp", "B/*.hpp", "C/*.hpp"]),
    project_brief = "Example project for doxygen",  # Brief description of the project
    project_name = "base",                          # Name of the project
    configurations = [                              # Additional configurations to add to the Doxyfile
        "GENERATE_HTML = YES",                      # They are the same as the Doxyfile options,
        "GENERATE_LATEX = NO",                      # and will override the default values
        "EXTRACT_ALL = YES",
    ],
    tags = ["manual"]  # Tags to add to the target. This way the target won't run unless explicitly called
)

But I should be able to wrap that so that I could do something like:

my_doxygen(
    name = "my_cool_docs",
    deps = [":main"]   ( or maybe in a docs folder "//:main" )
)

bazel build //:my_cool_docs or bazel build //docs:my_cool_docs

Have it walk the deps of main, keep a list of files, and then supply to doxygen under the hood.

So far, I have figured out how to make this work on all cc_library and cc_binary files inside a BUILD file using aspects:

load("//:cc_srcs.bzl", "CcSrcs", "cc_srcs_aspect")

def _gather_src_files_impl(ctx):
    srcs = []
    hdrs = []

    for dep in ctx.attr.deps:
        srcs.extend(dep[CcSrcs].srcs.to_list())
        hdrs.extend(dep[CcInfo].compilation_context.direct_headers)

    hdrs = [h for h in hdrs if h.is_source]

    src_list = srcs + hdrs
    info = [DefaultInfo(files=depset(src_list))]
    return info

_gather_src_files = rule(
    implementation = _gather_src_files_impl,
    fragments = ["cpp"],
    attrs = {
        "deps": attr.label_list(providers = [CcInfo], aspects = [cc_srcs_aspect]),

    },
    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
)

def gather_src_files(name = "gather_src_files"):
    rule_types = [rule["kind"] for rule in native.existing_rules().values()]
    if "_gather_src_files" in rule_types:
        fail("rule already defined")

    deps = []
    for rule_name, rule in native.existing_rules().items():
        print(rule_name + ":" + rule["kind"])
        if rule["kind"] in ["cc_binary", "cc_library"]:
            deps.append(rule_name)

    _gather_src_files(
        name = name,
        deps = deps
    )

cc_srcs.bazel

CcSrcs = provider(
    fields = {
        "srcs": "depset of top-level cpp sources",
        "package": "the package root",
    },
)

def extract_cc_files(target, ctx):
    srcs_target_list = getattr(ctx.rule.attr, "srcs", [])
    srcs_file_list = [f for t in srcs_target_list for f in t.files.to_list()]
    return srcs_file_list

def extract_cc_srcs(target, ctx):
    return [s for s in extract_cc_files(target, ctx) if s.extension in ["c", "cc", "cpp", "cxx", "c++", "C"]]

def _cc_srcs_aspect_impl(target, ctx):
    srcs = []
    if CcInfo in target: srcs = extract_cc_srcs(target, ctx)

    # Get the root name: either ws root for external repos, or package for internal repos
    root_name = ctx.label.workspace_root
    if not root_name: root_name = ctx.label.package

    return [CcSrcs(
        srcs = depset(srcs, order = "topological"),
        package = depset([root_name]),
    )]

cc_srcs_aspect = aspect(
    implementation = _cc_srcs_aspect_impl,
)

But I can not figure out how to access / walk the dependency graph of a given target - and I am not even sure what terms to look for in the Bazel docs at this point.

Any ideas? Thanks!

Upvotes: 0

Views: 137

Answers (1)

 tenspd137
tenspd137

Reputation: 393

After looking closer at the example - I figured out all I needed to do was add:

FileCountInfo = provider(
    fields = {
        'count' : 'number of files',
        'srcs'  : 'depset of files' <-----
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    srcs = []
    # Make sure the rule has a srcs attribute.
    print("Fudge")
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
                    srcs.append(f)  <-----------
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
        a = dep[FileCountInfo].srcs
        srcs.extend(dep[FileCountInfo].srcs.to_list()) <----------
    return [FileCountInfo(count = count, srcs=depset(srcs))] <--------
    enter code here

Between that and what I already have - I think I have a slightly more solid base.

It's a process, and I don't think Bazel has met a problem it hasn't solved with another layer of indirection yet.

Upvotes: 1

Related Questions