+++
date = "2021-02-19"
draft = false
path = "/blog/finding-functions-in-nixpkgs"
tags = ["nix"]
title = "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.
### Static
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`](https://github.com/elkowar/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"
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-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.
[nix-shell:~/dev/nixpkgs]$ nix-doc disableLibraryProfiling
disable library profiling
disableLibraryProfiling = drv: ...
# ./pkgs/development/haskell-modules/lib.nix:178
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
```nix
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.