Finding functions in nixpkgs
It is a poorly guarded secret that the most effective way to get documentation on nixpkgs or nix functions is to read the source code. The hard part of that is finding the right source code. I'll document a few static and dynamic analysis strategies to find the documentation or definition of things in nixpkgs going from trivial to some effort.
Simple
These work on functions that have no wrappers around them, which account for
most library functions in nixpkgs. The option for ctags
is a little bit
better because it will just show you the source code which you can subsequently
trace through.
Static
ctags
As of version 0.5.0 (released 2021-07-03), nix-doc
supports emitting ctags
files with nix-doc tags .
from a nixpkgs checkout.
This lets you :tag
things in vim or other editor supporting ctags and
instantly jump to them by name, as well as CTRL-]
to jump to the symbol under
the cursor. It's clever enough to distinguish functions from other values at a
syntax level, but like every ctags tool that's about where it stops.
Search tools
Static analysis is, in my view, slightly slower, since you can't be sure you're
getting the function you're seeing in the interactive environment in nix repl
or elsewhere.
There are two tools for this that are capable of parsing nix source, nix-doc
(my project) and manix
, both of which are in nixpkgs.
Note that nix-doc
will only work from within a nixpkgs checkout or if you
pass it a second parameter of where your nixpkgs is. This is mostly because
nix-doc
's main focus has switched to being mainly on providing a function in
the REPL.
There is also a fork of rnix-lsp
which
provides manix
based documentation from within editors.
# your nixpkgs checkout
$ cd ~/dev/nixpkgs; nix-shell -p manix nix-doc
[nix-shell:~/dev/nixpkgs]$ manix foldl
manix
output
Here's what I found in nixpkgs: lib.lists.foldl'
lib.lists.foldl lib.foldl
lib.foldl' haskellPackages.foldl-statistics
haskellPackages.foldl-transduce-attoparsec
haskellPackages.foldl-incremental haskellPackages.foldl-exceptions
haskellPackages.foldl haskellPackages.foldl-transduce
Nixpkgs Comments
────────────────────
# foldl (lib/lists.nix)
“left fold”, like `foldr`, but from the left:
`foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`.
Type: foldl :: (b -> a -> b) -> b -> [a] -> b
Example:
lconcat = foldl (a: b: a + b) "z"
lconcat [ "a" "b" "c" ]
=> "zabc"
# different types
lstrange = foldl (str: int: str + toString (int + 1)) "a"
lstrange [ 1 2 3 4 ]
=> "a2345"
NixOS Documentation
────────────────────
# lib.lists.foldl' (foldl' :: (b -> a -> b) -> b -> [a] -> b)
Strict version of `foldl`.
NixOS Documentation
────────────────────
# lib.lists.foldl (foldl :: (b -> a -> b) -> b -> [a] -> b)
“left fold”, like `foldr`, but from the left:
`foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`.
Arguments:
op: Function argument
nul: Function argument
list: Function argument
Example:
lconcat = foldl (a: b: a + b) "z"
lconcat [ "a" "b" "c" ]
=> "zabc"
# different types
lstrange = foldl (str: int: str + toString (int + 1)) "a"
lstrange [ 1 2 3 4 ]
=> "a2345"
lconcat = foldl (a: b: a + b) "z"
lconcat [ "a" "b" "c" ]
=> "zabc"
# different types
lstrange = foldl (str: int: str + toString (int + 1)) "a"
lstrange [ 1 2 3 4 ]
=> "a2345"
manix
includes the file path with the documentation from the nixpkgs sources
but no line number. It also includes NixOS manual documentation, which I
appreciate.
[nix-shell:~/dev/nixpkgs]$ nix-doc foldl
nix-doc
output
“left fold”, like `foldr`, but from the left:
`foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`.
Type: foldl :: (b -> a -> b) -> b -> [a] -> b
Example:
lconcat = foldl (a: b: a + b) "z"
lconcat [ "a" "b" "c" ]
=> "zabc"
different types
lstrange = foldl (str: int: str + toString (int + 1)) "a"
lstrange [ 1 2 3 4 ]
=> "a2345"
foldl = op: nul: list: ...
# ./lib/lists.nix:80
nix-doc
basically gets you the same thing, but it is missing foldl'
, which
is a bug in the nix-doc
command line interface, I think. It does, however,
give you a source path with line number so you can use middle click or C-w F
or similar to go directly to the function's source in your editor.
Dynamic
This is the wheelhouse of nix-doc
. It adds the two functions demonstrated
below (added by the nix-doc
Nix plugin, see the README for installation
instructions):
nix-repl> n = import <nixpkgs> {}
nix-repl> builtins.unsafeGetLambdaPos n.lib.foldl
{ column = 11; file = "/nix/store/...-nixpkgs-.../nixpkgs/lib/lists.nix"; line = 80; }
nix-repl> builtins.doc n.lib.foldl
“left fold”, like `foldr`, but from the left:
`foldl op nul [x_1 x_2 ... x_n] == op (... (op (op nul x_1) x_2) ... x_n)`.
Type: foldl :: (b -> a -> b) -> b -> [a] -> b
Example:
lconcat = foldl (a: b: a + b) "z"
lconcat [ "a" "b" "c" ]
=> "zabc"
different types
lstrange = foldl (str: int: str + toString (int + 1)) "a"
lstrange [ 1 2 3 4 ]
=> "a2345"
func = op: nul: list: ...
# /nix/store/...-nixpkgs-.../nixpkgs/lib/lists.nix:80
null
You can also get this information using builtins.unsafeGetAttrPos
, an
undocumented built-in function in Nix itself:
nix-repl> builtins.unsafeGetAttrPos "foldl" n.lib
{ column = 25; file = "/nix/store/...-nixpkgs-.../nixpkgs/lib/default.nix"; line = 82; }
Functions without documentation
nixpkgs has a few of these. Let's pick on Haskell infrastructure because I am most familiar with it.
First let's try some static analysis to try to find the signature or source for
nixpkgs.haskell.lib.disableLibraryProfiling
:
[nix-shell:~/dev/nixpkgs]$ manix disableLibraryProfiling
[nix-shell:~/dev/nixpkgs]$ nix-doc disableLibraryProfiling
[nix-shell:~/dev/nixpkgs]$ rg disableLibraryProfiling
pkgs/development/haskell-modules/configuration-common.nix
50: ghc-heap-view = disableLibraryProfiling super.ghc-heap-view;
51: ghc-datasize = disableLibraryProfiling super.ghc-datasize;
1343: graphql-engine = disableLibraryProfiling( overrideCabal (super.graphql-engine.override {
pkgs/development/haskell-modules/lib.nix
177: disableLibraryProfiling = drv: overrideCabal drv (drv: { enableLibraryProfiling = false; });
pkgs/development/haskell-modules/configuration-nix.nix
97: hercules-ci-agent = disableLibraryProfiling super.hercules-ci-agent;
Oh dear! That's not good. Neither the manix
nor nix-doc
command line tools
found the function. This leaves rg
, which is not based on the Nix abstract
syntax tree, and for functions that are used a lot of times, the definition of
a function will get buried among its uses. This is not ideal.
I believe that in the case of nix-doc
it may have found
it but ignored the function since it had no documentation. Let's test that.
Results of adding a comment to see what happens
[nix-shell:~/dev/nixpkgs]$ nix-doc disableLibraryProfiling
disable library profiling
disableLibraryProfiling = drv: ...
# ./pkgs/development/haskell-modules/lib.nix:178
Yep.
Well, time to pull out the dynamic analysis again. Like in the simple
case, you can get the source code with builtins.unsafeGetAttrPos
or the functions added by nix-doc
. On my system nixpkgs where there is no
documentation comment for the function, this is what I get:
nix-repl> builtins.doc n.haskell.lib.disableLibraryProfiling
func = drv: ...
# /nix/store/...-nixpkgs-.../nixpkgs/pkgs/development/haskell-modules/lib.nix:177
Although there is no documentation, nix-doc
has pulled out the signature,
which may already be enough to guess what the function does. If not, there is a
source code reference.
Indirection
The hardest class of functions to find documentation for are ones which are
wrapped by some other function. These can be frustrating since the AST pattern
matching for functions as used by nix-doc
and manix
will fall apart on
them.
An example function like this is nixpkgs.fetchFromGitLab
, but any arbitrary
package created via callPackage
will also work for this, as they are not
really functions, but you do want to find them.
manix
knows of the function, but does not know from whence it came, whereas
nix-doc
's CLI does not see it at all:
[nix-shell:~/dev/nixpkgs]$ manix fetchFromGitLab
Here's what I found in nixpkgs: pkgsMusl.fetchFromGitLab
fetchFromGitLab fetchFromGitLab.override
fetchFromGitLab.__functor fetchFromGitLab.__functionArgs
pkgsHostTarget.fetchFromGitLab pkgsBuildBuild.fetchFromGitLab
pkgsStatic.fetchFromGitLab pkgsTargetTarget.fetchFromGitLab
targetPackages.fetchFromGitLab gitAndTools.fetchFromGitLab
__splicedPackages.fetchFromGitLab buildPackages.fetchFromGitLab
pkgsHostHost.fetchFromGitLab pkgsBuildHost.fetchFromGitLab
pkgsBuildTarget.fetchFromGitLab pkgsi686Linux.fetchFromGitLab
[nix-shell:~/dev/nixpkgs]$ nix-doc fetchFromGitLab
Time to get out the dynamic analysis again!
nix-repl> n = import <nixpkgs> {}
nix-repl> builtins.doc n.fetchFromGitLab
error: (string):1:1: value is a set while a lambda was expected
nix-repl> builtins.typeOf n.fetchFromGitLab
"set"
nix-repl> n.fetchFromGitLab
{ __functionArgs = { ... }; __functor = «lambda @ /nix/store/...-nixpkgs-.../nixpkgs/lib/trivial.nix:324:19»; override = { ... }; }
That didn't work! It's a set with the __functor
attribute that makes it
callable.
Even if we try pointing nix-doc
at the __functor
, it will tell us about
setFunctionArgs
, which is not what we were looking for.
From what I understand of the Nix internals from writing the plugin, there is not really a nice way of getting the source of a function wrapped like this, since the information is already lost by the time the value enters the dumping function as Nix only stores lambda and attribute definition locations so once you have taken the value of the attribute that information is no longer available.
This could be resolved with a new REPL command as those take strings of source which could be split to get the attribute name and attribute set, but custom REPL commands are not supported so some kind of modification would have to be made to Nix itself to add this feature.
Therefore, I have to use the last trick up my sleeve, unsafeGetAttrPos
, to
find the definition of the attribute:
nix-repl> builtins.unsafeGetAttrPos "fetchFromGitLab" n
{ column = 3; file = "/nix/store/...-nixpkgs-.../nixpkgs/pkgs/top-level/all-packages.nix"; line = 466; }
This tells me, albeit in an annoying to copy format, that the next breadcrumb
is at pkgs/top-level/all-packages.nix:466
, which is
fetchFromGitLab = callPackage ../build-support/fetchgitlab {};
Then, I can look in pkgs/build-support/fetchgitlab
and find a default.nix
which will have the definition I want.