-
Notifications
You must be signed in to change notification settings - Fork 1
Lesson 12: Stardoc
In the last few lessons, we've extended Bazel with two custom toolchains, a handful of rules, a macro, and a provider. Previously, when we've installed toolchains, we've referred to the source documentation to learn how to use them. As toolchain authors, we have the same responsibility to our users. In this lesson, we'll cover documentation in Starlark and how we can generate documentation artifacts for our users.
In Lesson 7, we began authoring our own rules.
The .bzl files we wrote our rules in began with a docstring.
For example, the noop_binary rule located in rules/noop.bzl started with the following:
"""
A build rule which does nothing.
"""Writing a module docstring in this fashion attaches information to the module; however, it doesn't attach anything to the rules and methods defined in the module.
That type of annotation is provided through the doc attribute.
Many constructs in Starlark accept a doc attribute, which can be used to attach contextual documentation to specific parts of our code.
This differs from plain docstrings in many programming languages, which may only be attached to functions, methods, or files.
To see an example of doc in practice, let's write some documentation for the noop_binary rule:
"""
A build rule which does nothing.
"""
def _noop_binary_impl(_):
pass
noop_binary = rule(
doc = "A rule which does and returns nothing",
implementation = _noop_binary_impl,
)Now the noop_binary rule carries that metadata with it.
In our case, we've maintained a one-to-one relationship between .bzl files and the rules contained within them.
Further, the noop_binary rule doesn't do anything, so our first pass at documentation isn't very interesting.
Let's try adding documentation to a rule which defines attrs to see how we can document our expectations from users.
For example, rules/greeter.bzl:
"""
A build rule which generates a file with a greeting to a specified user.
"""
def _greeter_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello, {}\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
greeter = rule(
doc = "Writes a greeting file to `username`",
implementation = _greeter_impl,
attrs = {
"username": attr.string(doc = "The name of the user we're greeting"),
},
)Like the API for rule, the typed attr API also allows us to pass doc values.
This attaches documentation directly to the username attribute.
In naive docstring constructs, the direct association between documentation and the thing being documented is often lost.
Later in this lesson, we'll see why that is important.
For now, let's demonstrate another construct which supports the doc attribute: providers.
The product rule defined in rules/product.bzl created a NumberInfo provider.
Let's document the information it passes along to its dependents:
"""
Generates the product of arguments of dependencies.
"""
NumberInfo = provider(doc = "Stores the numeric value for dependents", fields = ["number"])
def _product_impl(ctx):
result = ctx.attr.number
for dep in ctx.attr.deps:
if NumberInfo in dep:
result = result * dep[NumberInfo].number
ctx.actions.write(output = ctx.outputs.out, content = str(result))
# Return the provider with result, visible to other rules.
return [NumberInfo(number = result)]
_product = rule(
doc = "Produces the product of all dependencies as a NumberInfo",
implementation = _product_impl,
attrs = {
"number": attr.int(default = 1, doc = "The value all dependencies will be multiplied by. Defaults to 1."),
"deps": attr.label_list(doc = "A list of NumberInfo provider dependencies to multiply the number by."),
"out": attr.output(doc = "The output location the result should be written to."),
},
)
def product(**kwargs):
_product(out = "{name}.product".format(**kwargs), **kwargs)We'll now take a shortcut and iterate through and document everything we've built in Lesson 7, Lesson 9, Lesson 10, and Lesson 11.
The actual docstrings will be visible in the diff for the v12 tag.
Now that we've attached documentation to our work, we can generate API documentation!
Following the previous section, we'll start with the noop_binary rule.
For Starlark code, Stardoc is the most common API documentation tool.
We'll use the rules contained in that repository to create markdown documents from our .bzl files.
Like the other tools used in this repository, we'll first need to add them as a dependency.
We'll add the following to our MODULE.bazel:
## Rules to generate API documentation for Starlark
bazel_dep(
name = "stardoc",
version = "0.8.0",
)Now we need to load the stardoc rule in rules/BUILD and create a rule target for the documentation we want to create.
"""
Example usage of our custom rules.
"""
load("@stardoc//stardoc:stardoc.bzl", "stardoc")
load(":hello.bzl", "hello")
load(":hello_temp.bzl", "hello_temp")
load(":noop.bzl", "noop_binary")
load(":print.bzl", "print_binary")
load(":product.bzl", "product")
noop_binary(name = "noop")
stardoc(
name = "noop_binary_docs",
out = "noop_binary_docs.md",
input = "noop.bzl",
)This rule target will read noop.bzl and produce an artifact named noop_binary_docs.md in the rules package.
Let's build it and see what is generated.
$ bazelisk build //rules:noop_binary_docs
INFO: Analyzed target //rules:noop_binary_docs (33 packages loaded, 326 targets configured).
INFO: Found 1 target...
Target //rules:noop_binary_docs up-to-date:
bazel-bin/rules/noop_binary_docs.md
INFO: Elapsed time: 0.424s, Critical Path: 0.00s
INFO: 1 process: 2 action cache hit, 1 internal.
INFO: Build completed successfully, 1 total actionNow we can view the markdown file we created by viewing bazel-bin/rules/noop_binary_docs.md:
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
A build rule which does nothing.
<a id="noop_binary"></a>
## noop_binary
<pre>
load("@bite-sized-bazel//rules:noop.bzl", "noop_binary")
noop_binary(<a href="#noop_binary-name">name</a>)
</pre>
A rule which does and returns nothing
**ATTRIBUTES**
| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="noop_binary-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |Stardoc gives us everything we'd need to get started as consumers of the rule:
- Where the rule is located
- How the rule is invoked
- What attributes the rule supports
In the generated output, you may have noticed a reference to skydoc.
Skydoc was an earlier tool for API documentation generation that was officially deprecated in 2019- with stardoc being the most common replacement.
Before we write documentation targets for everything we've built, let's talk about Skylib.
Skylib is a Starlark utility library published by Bazel's core team. As we begin to take on more complex and robust tasks in Starlark, we'll leverage the tools contained in that repository to simplify our work.
To compare your progress, you can view these changes on GitHub.
This repository, documentation, and all included code is licensed under the MIT License. See the LICENSE file for more details.
Please consider supporting this repository.
Return to the repository here.
- Home
- Glossary
- Further Reading
- Lessons
- Lesson 1: Installing Bazel
- Lesson 2: Creating Packages
- Lesson 3: Building Artifacts
- Lesson 4: Dependency Management
- Lesson 5: Executing Tests
- Lesson 6: General Rules
- Lesson 7: Authoring Rules
- Lesson 8: Build Tools
- Lesson 9: Symbolic Macros
- Lesson 10: Toolchains
- Lesson 11: Advanced Toolchains
- Lesson 12: Stardoc
- Lesson 13: Aspects
- Lesson 14: Modifying The Source Tree
- Repository States