• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Starlark docgen
2
3Using the `sphinx_stardoc` rule, API documentation can be generated from bzl
4source code. This rule requires both MyST-based markdown and the `sphinx_bzl`
5Sphinx extension are enabled. This allows source code to use Markdown and
6Sphinx syntax to create rich documentation with cross references, types, and
7more.
8
9
10## Configuring Sphinx
11
12While the `sphinx_stardoc` rule doesn't require Sphinx itself, the source
13it generates requires some additional Sphinx plugins and config settings.
14
15When defining the `sphinx_build_binary` target, also depend on:
16* `@rules_python//sphinxdocs/src/sphinx_bzl:sphinx_bzl`
17* `myst_parser` (e.g. `@pypi//myst_parser`)
18* `typing_extensions` (e.g. `@pypi//myst_parser`)
19
20```
21sphinx_build_binary(
22    name = "sphinx-build",
23    deps = [
24        "@rules_python//sphinxdocs/src/sphinx_bzl",
25        "@pypi//myst_parser",
26        "@pypi//typing_extensions",
27        ...
28    ]
29)
30```
31
32In `conf.py`, enable the `sphinx_bzl` extension, `myst_parser` extension,
33and the `colon_fence` MyST extension.
34
35```
36extensions = [
37    "myst_parser",
38    "sphinx_bzl.bzl",
39]
40
41myst_enable_extensions = [
42    "colon_fence",
43]
44```
45
46## Generating docs from bzl files
47
48To convert the bzl code to Sphinx doc sources, `sphinx_stardocs` is the primary
49rule to do so. It takes a list of `bzl_library` targets or files and generates docs for
50each. When a `bzl_library` target is passed, the `bzl_library.srcs` value can only
51have a single file.
52
53Example:
54
55```
56sphinx_stardocs(
57    name = "my_docs",
58    srcs = [
59      ":binary_bzl",
60      ":library_bzl",
61    ]
62)
63
64bzl_library(
65   name = "binary_bzl",
66   srcs = ["binary.bzl"],
67   deps = ...
68)
69
70bzl_library(
71   name = "library_bzl",
72   srcs = ["library.bzl"],
73   deps = ...
74)
75```
76
77## User-defined types
78
79While Starlark doesn't have user-defined types as a first-class concept, it's
80still possible to create such objects using `struct` and lambdas. For the
81purposes of documentation, they can be documented by creating a module-level
82`struct` with matching fields *and* also a field named `TYPEDEF`. When the
83`sphinx_stardoc` rule sees a struct with a `TYPEDEF` field, it generates doc
84using the {rst:directive}`bzl:typedef` directive and puts all the struct's fields
85within the typedef. The net result is the rendered docs look similar to how
86a class would be documented in other programming languages.
87
88For example, a the Starlark implemenation of a `Square` object with a `area()`
89method would look like:
90
91```
92
93def _Square_typedef():
94    """A square with fixed size.
95
96    :::{field} width
97    :type: int
98    :::
99    """
100
101def _Square_new(width):
102    """Creates a Square.
103
104    Args:
105        width: {type}`int` width of square
106
107    Returns:
108        {type}`Square`
109    """
110    self = struct(
111        area = lambda *a, **k: _Square_area(self, *a, **k),
112        width = width
113    )
114    return self
115
116def _Square_area(self, ):
117   """Tells the area of the square."""
118   return self.width * self.width
119
120Square = struct(
121  TYPEDEF = _Square_typedef,
122  new = _Square_new,
123  area = _Square_area,
124)
125```
126
127This will then genereate markdown that looks like:
128
129```
130::::{bzl:typedef} Square
131A square with fixed size
132
133:::{bzl:field} width
134:type: int
135:::
136:::{bzl:function} new()
137...args etc from _Square_new...
138:::
139:::{bzl:function} area()
140...args etc from _Square_area...
141:::
142::::
143```
144
145Which renders as:
146
147:::{bzl:currentfile} //example:square.bzl
148:::
149
150::::{bzl:typedef} Square
151A square with fixed size
152
153:::{bzl:field} width
154:type: int
155:::
156:::{bzl:function} new()
157...
158:::
159:::{bzl:function} area()
160...
161:::
162::::
163