| # 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: | |
| * `//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 = [ | |
| "//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:dir}`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() | |
| ... | |
| ::: | |
| :::: |