2

I'm trying to build a rule for bazel which emulates the CMake *.in template system.

This has two challenges, the first is generate the template output. The second is make the output available to both genrules, filegroups and cc_* rules. The third is to get that dependency to transitively be passed to further downstream rules.

I have it generating the output file version.hpp in genfiles (or bazel-bin), and I can get the initial library rule to include it, but I can't seem to figure out how to make my cc_binary rule, which depends on the cc_library and transitively the header_template rule to find the header file.

I have the following .bzl rule:

def _header_template_impl(ctx):
    # this generates the output from the template
    ctx.actions.expand_template(
        template = ctx.file.template,
        output = ctx.outputs.out,
        substitutions = ctx.attr.vars,
    )

    return [
            # create a provider which says that this
            # out file should be made available as a header
            CcInfo(compilation_context=cc_common.create_compilation_context(
                headers=depset([ctx.outputs.out])
            )),

            # Also create a provider referencing this header ???
            DefaultInfo(files=depset(
                [ctx.outputs.out]
            ))
        ]

header_template = rule(
    implementation = _header_template_impl,
    attrs = {
        "vars": attr.string_dict(
            mandatory = True
        ),
        "extension": attr.string(default=".hpp"),
        "template": attr.label(
            mandatory = True,
            allow_single_file = True,
        ),
    },
    outputs = {
        "out": "%{name}%{extension}",
    },
    output_to_genfiles = True,
)

elsewhere I have a cc_library rule:

load("//:tools/header_template.bzl", "header_template")

# version control
BONSAI_MAJOR_VERSION = '2'
BONSAI_MINOR_VERSION = '0'
BONSAI_PATCH_VERSION = '9'
BONSAI_VERSION = \
    BONSAI_MAJOR_VERSION + '.' + \
    BONSAI_MINOR_VERSION + '.' + \
    BONSAI_PATCH_VERSION

header_template(
    name = "bonsai_version",
    extension = ".hpp",
    template = "version.hpp.in",
    vars = {
        "@BONSAI_MAJOR_VERSION@": BONSAI_MAJOR_VERSION,
        "@BONSAI_MINOR_VERSION@": BONSAI_MINOR_VERSION,
        "@BONSAI_PATCH_VERSION@": BONSAI_PATCH_VERSION,
        "@BONSAI_VERSION@": BONSAI_VERSION,
    },
)

# ...

private = glob([
        "src/**/*.hpp",
        "src/**/*.cpp",
        "proto/**/*.hpp",
    ])
public = glob([
        "include/*.hpp",
        ":bonsai_version",
    ])

cc_library(
    # target name matches directory name so you can call:
    #  bazel build .
    name = "bonsai",
    srcs = private,
    hdrs = public,
    # public headers
    includes = [
        "include",
    ],

    # ...

    deps = [
        ":bonsai_version",
        # ...
    ],

    # ...
)

When I build, my source files need to be able to:

#include "bonsai_version.hpp"

I think the answer involves CcInfo but I'm grasping in the dark as to how it should be constructed.

I've already tried add "-I$(GENDIR)/" + package_name() to the copts, to no avail. The generated header still isn't available.

My expectation is that I should be able to return some kind of Info object that would allow me to add the dependency in srcs. Maybe it should be a DefaultInfo.

I've dug through the bazel rules examples and the source, but I'm missing something fundamental, and I can't find documentation that discuss this particular.

I'd like to be able to do the following:

header_template(
    name = "some_header",
    extension = ".hpp",
    template = "some_header.hpp.in",
    vars = {
        "@SOMEVAR@": "value",
        "{ANOTHERVAR}": "another_value",
    },
)

cc_library(
   name = "foo",
   srcs = ["foo.src", ":some_header"],
   ...
)

cc_binary(
   name = "bar",
   srcs = ["bar.cpp"],
   deps = [":foo"],
)


and include the generated header like so:

#include "some_header.hpp"

void bar(){
}

2 Answers 2

5

The answer looks like it is:

def _header_template_impl(ctx):
    # this generates the output from the template
    ctx.actions.expand_template(
        template = ctx.file.template,
        output = ctx.outputs.out,
        substitutions = ctx.attr.vars,
    )

    return [
            # create a provider which says that this
            # out file should be made available as a header
            CcInfo(compilation_context=cc_common.create_compilation_context(

                # pass out the include path for finding this header
                includes=depset([ctx.outputs.out.dirname]),

                # and the actual header here.
                headers=depset([ctx.outputs.out])
            ))
        ]

elsewhere:

header_template(
    name = "some_header",
    extension = ".hpp",
    template = "some_header.hpp.in",
    vars = {
        "@SOMEVAR@": "value",
        "{ANOTHERVAR}": "another_value",
    },
)

cc_library(
   name = "foo",
   srcs = ["foo.cpp"],
   deps = [":some_header"],
   ...
)

cc_binary(
   name = "bar",
   srcs = ["bar.cpp"],
   deps = [":foo"],
)
1
  • This worked for me! Is there a way to include a prefix so that the generate header can be accessed like: #include "my_custom_prefix/some_header.hpp"
    – GGhe
    Commented Apr 23, 2020 at 1:45
0

If your header has a generic name (eg config.h) and you want it to be private (ie srcs instead of hdrs), you might need a different approach. I've seen this problem for gflags, which "leaked" config.h and affected libraries that depended on it (issue).

Of course, in both cases, the easiest solution is to generate and commit header files for the platforms you target.

Alternatively, you can set copts for the cc_library rule that uses the generated private header:

cc_library(
   name = "foo",
   srcs = ["foo.cpp", "some_header.hpp"],
   copts = ["-I$(GENDIR)/my/package/name"],
   ...
)

If you want this to work when your repository is included as an external repository, you have a bit more work cut out for you due to bazel issue #4463.

PS. You might want to see if cc_fix_config from https://github.com/antonovvk/bazel_rules works for you. It's just a wrapper around perl but I found it useful.

2
  • it's a public header, so that behavior was desired, but I suppose I could add an option that would move the inclusion in the rule for public/private. for whatever reason, the GENDIR trick wasn't working for me. routing around genfile directories is fraught because I don't think the header gets generated when the template changes.
    – mikeestee
    Commented Mar 29, 2019 at 23:00
  • If your library has a dependency on some_header.hpp and the file doesn't exist in the source tree, Bazel should ensure it's up-to-date before building the library, but maybe there's some weirdness or bug in play. However, if using CcInfo works for your case then it's certainly less error-prone than messing around with copts and GENDIR. Commented Apr 1, 2019 at 9:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.