blob: ba4ab516f505739f07d049ab86f0c91a8e7e8b2c [file] [log] [blame] [view] [edit]
# 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_bzl`
* `myst_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 {rst:directive}`bzl:typedef` 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:
:::{bzl:currentfile} //example:square.bzl
:::
::::{bzl:typedef} Square
A square with fixed size
:::{bzl:field} width
:type: int
:::
:::{bzl:function} new()
...
:::
:::{bzl:function} area()
...
:::
::::