Starlark docgen
Using the sphinx_stardoc rule, API documentation can be generated from bzl
source code. This rule requires both MyST-based markdown and the sphinx_bzl
Sphinx extension are enabled. This allows source code to use Markdown and
Sphinx syntax to create rich documentation with cross references, types, and
more.
Configuring Sphinx
While the sphinx_stardoc rule doesn’t require Sphinx itself, the source
it generates requires some additional Sphinx plugins and config settings.
When defining the sphinx_build_binary target, also depend on:
@rules_python//sphinxdocs/src/sphinx_bzl:sphinx_bzlmyst_parser(e.g.@pypi//myst_parser)typing_extensions(e.g.@pypi//myst_parser)
sphinx_build_binary(
name = "sphinx-build",
deps = [
"@rules_python//sphinxdocs/src/sphinx_bzl",
"@pypi//myst_parser",
"@pypi//typing_extensions",
...
]
)
In conf.py, enable the sphinx_bzl extension, myst_parser extension,
and the colon_fence MyST extension.
extensions = [
"myst_parser",
"sphinx_bzl.bzl",
]
myst_enable_extensions = [
"colon_fence",
]
Generating docs from bzl files
To convert the bzl code to Sphinx doc sources, sphinx_stardocs is the primary
rule to do so. It takes a list of bzl_library targets or files and generates docs for
each. When a bzl_library target is passed, the bzl_library.srcs value can only
have a single file.
Example:
sphinx_stardocs(
name = "my_docs",
srcs = [
":binary_bzl",
":library_bzl",
]
)
bzl_library(
name = "binary_bzl",
srcs = ["binary.bzl"],
deps = ...
)
bzl_library(
name = "library_bzl",
srcs = ["library.bzl"],
deps = ...
)
User-defined types
While Starlark doesn’t have user-defined types as a first-class concept, it’s
still possible to create such objects using struct and lambdas. For the
purposes of documentation, they can be documented by creating a module-level
struct with matching fields and also a field named TYPEDEF. When the
sphinx_stardoc rule sees a struct with a TYPEDEF field, it generates doc
using the directive and puts all the struct’s fields
within the typedef. The net result is the rendered docs look similar to how
a class would be documented in other programming languages.
For example, a the Starlark implemenation of a Square object with a area()
method would look like:
def _Square_typedef():
"""A square with fixed size.
:::{field} width
:type: int
:::
"""
def _Square_new(width):
"""Creates a Square.
Args:
width: {type}`int` width of square
Returns:
{type}`Square`
"""
self = struct(
area = lambda *a, **k: _Square_area(self, *a, **k),
width = width
)
return self
def _Square_area(self, ):
"""Tells the area of the square."""
return self.width * self.width
Square = struct(
TYPEDEF = _Square_typedef,
new = _Square_new,
area = _Square_area,
)
This will then genereate markdown that looks like:
::::{bzl:typedef} Square
A square with fixed size
:::{bzl:field} width
:type: int
:::
:::{bzl:function} new()
...args etc from _Square_new...
:::
:::{bzl:function} area()
...args etc from _Square_area...
:::
::::
Which renders as: