I'm a Python, Linux, Nix/NixOS, JavaScript, Rust, ... basically everything open-source enthusiast.
Generate package info

Generate Nix packages information with Nix expressions

Lets go right to it!

System requirement is Nix.

Save this Nix expression code to

1
packages.nix
or some other file, every function is commented with examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
let
  # set allowBroken and allowUnfree to true, so that we minimize error output later on
  pkgs = import <nixpkgs> { config = { allowBroken = true; allowUnfree = true; }; };

  # catch exceptions on isDerivation function
  tryDrv = a: builtins.tryEval (pkgs.lib.isDerivation a);
  isDerivation = a: let t = tryDrv a; in t.success && t.value == true;

  # catch exceptions on isAttrs function
  tryAttrs = a: builtins.tryEval (pkgs.lib.isAttrs a);
  isAttrs = a: let t = tryAttrs a; in t.success && t.value == true;

  # iterate through attributeset's names (one-level deep)
  # example:
  # mapValues (name: value: name) pkgs
  # => [ "bash" "zsh" "gitFull" ... ]
  mapValues = f: set: (
    map (attr: f attr (builtins.getAttr attr set)) (builtins.attrNames set)
  );

  # recurse into attributeset (search for derivations)
  # example #1:
  # mapAttrsRecursiveDrv
  #   (path: value: path) pkgs.pythonPackages ["pkgs" "pythonPackages"] []
  # => [ [ "pkgs" "pythonPackages" "searx" ] [ "pkgs" "pythonPackages" "tarman" ] ... ]
  # example #2:
  # mapAttrsRecursiveDrv (path: value: path) pkgs ["pkgs"] []
  # => [ [ "pkgs" "bash" ] [ "pkgs" "zsh" ] [ "pkgs" "pythonPackages" "searx" ] [ "pkgs" "pythonPackages" "tarman" ] ... ]
  mapAttrsRecursiveDrv = f: set: path: list:
    let
      recurse = path: set: visitList:
        let
          visitedFun = a: path:
            let
              isAtt = isAttrs a;
              isDrv = isDerivation a;
              success = if isAtt && !isDrv then pkgs.lib.any (
                element: element == a
              ) visitList else false;
              not = !success;
              list = if not then (visitList ++ [a]) else visitList;
            in
              { inherit list not isAtt isDrv; };

          g = name: value:
            let
              visited = visitedFun value path;
            in
            if visited.isDrv then
              f (path ++ [name]) value
            else if (visited.not) && (checkForEnterable value) then
              recurse (path ++ [name]) value visited.list
            else
              {
                error = "not derivation or not enterable";
                attrPath = pkgs.lib.concatStringsSep "." (path ++ [name]);
              };
        in mapValues g set;
    in (recurse path set list);

  # check if attributeste has attribute named "recurseForDerivations"
  #   therefore has derivations
  # examples:
  # checkForEnterable pkgs.bash => false
  # checkForEnterable pkgs.pythonPackages => true
  checkForEnterable = a:
    let
      t = builtins.tryEval (
        (pkgs.lib.isAttrs a) &&
        (pkgs.lib.hasAttr "recurseForDerivations" a)
      );
    in
      (t.success && t.value == true);

  # main function
  # example:
  # recurseInto "pkgs.pythonPackages"
  # => [
  #   { attrPath = "pkgs.pythonPackages.tarman"; name = "python2.7-tarman-0.1.3"; out = "/nix/store/<hash>-python2.7-tarman-0.1.3"; }
  #   { attrPath = "pkgs.pythonPackages.searx"; name = "python2.7-searx-dev"; out = "/nix/store/<hash>-python2.7-searx-dev"; }
  #   { attrPath = "pkgs.pythonPackages.isPy27"; error = "not derivation or not enterable"; }
  # ]
  recurseInto = attrPath:
    let
      path = pkgs.lib.splitString "." attrPath;
      attrs = pkgs.lib.getAttrFromPath path pkgs;
    in
      pkgs.lib.flatten (mapAttrsRecursiveDrv
        (path: value:
          let
            attrPath = pkgs.lib.concatStringsSep "." path;
            tOutPath = builtins.tryEval value.outPath;
            tName = builtins.tryEval value.name;
          in
            (if tOutPath.success && tName.success then
              { out = tOutPath.value; name = tName.value; inherit attrPath; }
            else
              { error = "tryEval failed"; inherit attrPath; })
        )
        attrs
        path
        []);

  # just strips away values with attribute "error"
  removeErrors = builtins.filter (x: (if pkgs.lib.hasAttr "error" x then
      (builtins.trace "error '${x.error}' at attribute ${x.attrPath}" false)
    else true));

in
  removeErrors (recurseInto "pkgs")

And run it as:

$ nix-instantiate packages.nix --eval --strict --show-trace

It should take up to 15 seconds (well that depends on your system).

Output

Output should look like this, but much more of it (around 3MB if you redirect the stdout to file):

[
    {
        attrPath = "pkgs.pythonPackages.tarman";
        name = "python2.7-tarman-0.1.3";
        out = "/nix/store/<hash>-python2.7-tarman-0.1.3";
    } {
        attrPath = "pkgs.pythonPackages.searx";
        name = "python2.7-searx-dev";
        out = "/nix/store/<hash>-python2.7-searx-dev";
    } {
        attrPath = "pkgs.pythonPackages.isPy27";
        error = "not derivation or not enterable";
    }
    .
    .
    .
]

Every valid item in list has

1
attrPath
which represent attribute path in
1
pkgs
structure.

If there are some errors, they will be in

1
error
attribute, always beside
1
attrPath
. There are two error messages currently:

  1. 1
    
    not derivation or not enterable
    
    : when attribute set can not be recursable further.

  2. 1
    
    tryEval failed
    
    : when there is eval error on attribute.outPath or attribute.name.

And if there is no error for that package, following attributes are available:

  1. 1
    
    attrPath
    
    : path, with
    1
    
    .
    
    seperators, this can be used for package id (this never changes per package).

  2. 1
    
    name
    
    : package name with version.

  3. 1
    
    out
    
    : out path, this can be used as as unique id further on (can be different per same version of package).

Conclusion

The last line of

1
packages.nix
code can be changed from

removeErrors (recurseInto "pkgs")

to

builtins.toJSON (removeErrors (recurseInto "pkgs"))

to get JSON encoded string to feed data to some other application.

If you need some other info beside

1
attrPath
,
1
name
and
1
out
, just add attributes around the line
1
96
. For example if you want to add
1
meta
attribute add
1
meta = value.meta;
like so:

{ out = tOutPath.value; name = tName.value; inherit attrPath; meta = value.meta; }

But beware, this can increase output data size correspondly.

This is it …

.. and feel free to leave a comment.