tree: 959730f413d8f1925a2f962659d3d503b007d46c [path history] [tgz]
  1. BUILD.bazel
  2. Doxyfile.template
  3. lib.cpp
  4. lib.h
  5. make_var_substitution.bzl
  6. README.md
examples/substitutions/README.md

Basic usage example

This is a basic example of how to use the doxygen rule with make variable substitutions. The advantage of using make variable substitutions instead of directly working over the values passed to the doxygen rule is that you can access informations from other rules and toolchains and use them in the documentation. Move to the parent directory and run the following command:

# Version: _empty_ , Description: "no stamp"
bazel build //substitutions:doxygen
# or
# Version: _empty_ , Description: "stamp fallback"
bazel build //substitutions:doxygen --stamp
# or
# Version: 1.0.0 , Description: "MyBuildDescription"
bazel build //substitutions:doxygen --stamp --//substitutions:build_description="MyBuildDescription" --embed_label=1.0.0

[!NOTE]
If --stamp is not passed, the version will be undetermined, usually an empty string or the last stamped value.

Custom substitutions

We are reading variables from three sources:

  • Hardcoded constant strings in the BUILD.bazel file (e.g. NAME).
  • String flags passed through the command line (e.g. --substitutions:build_description=MyBuildDescription).
  • The stamp information stored in the bazel-out/stable-status.txt file.

Make variables

For the first two kind of substitution we need to define a simple rule that will apply the make variable substitutions. Let's say we want to pass a description to the doxygen rule from a command line flag, but only if the build is stamped. So, first we use skylib to define a string flag.

# BUILD.bazel

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")

string_flag(
    name = "build_description",
    build_setting_default = "stamp fallback",
)

[!TIP]
You can define an alias for the flag to make it easier to use in the command line. Create a .bazelrc file in the root of your workspace with the following content:

build --flag_alias=build_description=//substitutions:build_description

Now you can use --build_description=MyBuildDescription instead of --//substitutions:build_description=MyBuildDescription.

Then we create a substitution rule.

# make_var_substitution.bzl

load("@aspect_bazel_lib//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")

def _make_var_substitution_impl(ctx):
    vars = dict(ctx.attr.variables)
    stamp = maybe_stamp(ctx)
    if stamp:  # If the build is stamped, use the value from --//substitutions:build_description as the build version.
        vars["BUILD_DESCRIPTION"] = ctx.attr._build_description[BuildSettingInfo].value
    else:  # Otherwise use the hardcoded value.
        vars["BUILD_DESCRIPTION"] = "no stamp"
    # Add the path of the label files to the variables.
    for key, value in ctx.attr.locations.items():
        vars[key] = '"%s"' % '" "'.join([file.path for file in (value[DefaultInfo].files.to_list())])
    return [platform_common.TemplateVariableInfo(vars)]

make_var_substitution = rule(
    implementation = _make_var_substitution_impl,
    attrs = dict({
        "locations": attr.string_keyed_label_dict(),
        "variables": attr.string_dict(),
        "_build_description": attr.label(default = "//substitutions:build_description"),
    }, **STAMP_ATTRS),
)

[!IMPORTANT]
The string_keyed_label_dict attribute is not available in bazel 7.0.0. As an alternative, you can use two lists, one for the keys (strings) and one for the values (labels).

Finally, the substitution rule can be used as a toolchain in the doxygen rule.

All configurations using the make variable syntax will be replaced with the values defined in the make_var_substitution rule.

Stamp information

To retrieve the stamp information from the bazel-out/stable-status.txt file, we have to define a genrule that reads the file and extracts the BUILD_EMBED_LABEL . That information will be used to modify our custom Doxyfile.template file we will provide to the doxygen rule.

# BUILD.bazel

# Use a shell script to read the version from the stable-status.txt file
# It will replace the pattern {{PROJECT_NUMBER}} in the Doxyfile.template file
genrule(
    name = "doxyfile_template",
    srcs = ["Doxyfile.template"],
    outs = ["NewDoxyfile"],
    cmd = "sed s/{{PROJECT_NUMBER}}/$(grep -oP '(?<=BUILD_EMBED_LABEL ).+' bazel-out/stable-status.txt)/ $(SRCS) > $@",
    cmd_ps = "Get-content $(SRCS) | ForEach-Object { $$_ -replace '{{PROJECT_NUMBER}}', (Select-String -Pattern '(?<=BUILD_EMBED_LABEL +)(.+)' bazel-out/stable-status.txt -AllMatches | Select-Object -Expand Matches | Select-Object -Expand Value) } > $@",
    stamp = -1, # Only update if --stamp is passed
)

Output files

It is also possible to use files produced by other rules as input files for the doxygen rule. For example, we can use a genrule to create an header.html file that will be used as the header for the doxygen documentation.

# BUILD.bazel
genrule(
    name = "header",
    srcs = ["header.html"],
    outs = ["header.html"],
    cmd = "echo '<h1>My Header</h1>' > $@",
)

We need to include the genrule in the locations attribute of a make_var_substitution rule, as well as including it in the srcs attribute of the doxygen rule and specifying the location substitution in the doxygen rule.

Final result

# BUILD.bazel

load("@doxygen//:doxygen.bzl", "doxygen")
load("//substitutions:make_var_substitution.bzl", "make_var_substitution")

# Use a shell script to create a header file
genrule(
    name = "header",
    srcs = ["header.html"],
    outs = ["header.html"],
    cmd = "echo '<h1>My Header</h1>' > $@",
)

# Use a shell script to read the version from the stable-status.txt file
# It will replace the pattern {{PROJECT_NUMBER}} in the Doxyfile.template file
genrule(
    name = "doxyfile_template",
    srcs = ["Doxyfile.template"],
    outs = ["NewDoxyfile"],
    cmd = "sed s/{{PROJECT_NUMBER}}/$(grep -oP '(?<=BUILD_EMBED_LABEL ).+' bazel-out/stable-status.txt)/ $(SRCS) > $@",
    cmd_ps = "Get-content $(SRCS) | ForEach-Object { $$_ -replace '{{PROJECT_NUMBER}}', (Select-String -Pattern '(?<=BUILD_EMBED_LABEL +)(.+)' bazel-out/stable-status.txt -AllMatches | Select-Object -Expand Matches | Select-Object -Expand Value) } > $@",
    stamp = -1, # Only update if --stamp is passed
)

# Use the make_var_substitution rule to define some other custom substitutions
make_var_substitution(
    name = "make_var_substitution",
    variables = {
        "NAME": "substitutions",
    },
    locations = {
        "HEADER": ":header",
    },
)

doxygen(
    name = "doxygen",
    srcs = glob([
        "*.h",
        "*.cpp",
    ]) + [":header"], # We need to indicate all the rules this doxygen rule depends on
    project_brief = "$(BUILD_DESCRIPTION)", # => "no stamp" or "//substitutions:build_description" if the build is stamped
    project_name = "$(NAME)", # => "substitutions"
    doxyfile_template = ":doxyfile_template",
    html_header = "$(HEADER)", # => "header.html"
    toolchains = [":make_var_substitution"],
)