4 minutes
TIL: how to generate NixOS module docs
As it’s a national holiday I’m going to keep this short, seeing as how I’m somewhat obligated to fulfill some outdated but not entirely inaccurate stereotypes.
Documentation
This past week or so I’ve been beefing up the docs for Ethereum.nix. And as you might expect I reached a point at which I wanted to generate some documentation for our NixOS modules.
That’s when I came across nixosOptionsDoc.
This nifty little lib
function traverses all those options
declarations in your modules, extracts the names and descriptions
and so on that we all studiously ensure are well written and up to date, and spits them out in a variety of different formats.
For my use case I wanted them in markdown so I could import them into an MkDocs based website. And in doing so I learned an important lesson about how to structure my modules going forward.
Separate your options
In order to generate documentation for our modules we must first evaluate them. We do that with lib.evalModules
:
{ lib, ...}: let
eval = lib.evalModules {
modules = [
./module-a.nix
./module-b.nix
./module-c.nix
];
};
in {
# ...
}
If, like me, you have been writing your modules with a mix of options
and config
sections in the same file, you will
hit the same problem I did: you must include any modules your config refers to.
This means including many of the standard NixOS modules for things such as networking and so on. Which in turn means the generated documentation you get will also include those modules.
This is a lot of extra fluff we are not interested in. We only want documentation for our modules. So what’s the solution?
Separate your options into a separate file.
Once we have our options separated out, we can safely evaluate them and in turn generate their docs.
{ lib, runCommand, nixosOptionsDoc, ...}: let
# evaluate our options
eval = lib.evalModules {
modules = [
./options-a.nix
./options-b.nix
./options-c.nix
];
};
# generate our docs
optionsDoc = nixosOptionsDoc {
inherit (eval) options;
};
in
# create a derivation for capturing the markdown output
runCommand "options-doc.md" {} ''
cat ${optionsDoc.optionsCommonMark} >> $out
''
With our options doc in markdown format we can symlink this into an MkDocs site structure and job done!
{ lib, pkgs, ...}: let
inherit (pkgs) stdenv mkdocs python310Packages;
options-doc = pkgs.callPackage ./options-doc.nix {};
in stdenv.mkDerivation {
src = ./.;
name = "docs";
# depend on our options doc derivation
buildInput = [options-doc];
# mkdocs dependencies
nativeBuildInputs = [
mkdocs
python310Packages.mkdocs-material
python310Packages.pygments
];
# symlink our generated docs into the correct folder before generating
buildPhase = ''
ln -s ${options-doc} "./docs/nixos-options.md"
# generate the site
mkdocs build
'';
# copy the resulting output to the derivation's $out directory
installPhase = ''
mv site $out
'';
}
Get creative
You aren’t necessarily restricted to putting all of your options documentation in one markdown file (or even markdown at all).
If you look here in Ethereum.nix you can see
I’m being a bit fancier by traversing the file system looking for options.nix
files and then generating a separate
markdown file for each.
This lets me have a separate section for each module.
Summary
I’ve shown how easy it is to generate documentation from your NixOS modules.
I’ve also highlighted how you need to be careful about separating out your options
declarations if you want to reduce
the scope of the documentation being generated.
And on that note, I have somewhere else to be.
Sláinte 🍻.
— Edit: 2023-03-17 20:00 —
As was pointed out over on discourse,
if I had spent a bit more time reviewing the arguments for lib.evalModules
I would not have needed to separate the
options out, as you can instead pass it check = false
.
Alternatively, you can ensure one of the included modules includes { _module.check = false; }
.
On reflection I have to admit I kinda like having the separation, and I have seen other projects doing something similar. But don’t go splitting out your options just to satisfy the constraints of generating your docs.