How to get package maintainers of all installed packages?

So i just watched DistroTubes Video, where he elaborated on finding out, that 28% of the packages installed on his Arch Linux installation were packaged by Felix Yan.
So I was wondering if something similar to this is possible on NixOS.

I am able to get a list of store paths linked to /run/current-system/sw with nix-store -q --references /run/current-system/sw.

I am also able to get the package maintainers of a package with nix eval nixpkgs#legacyPackages.x86_64-linux.$(PACKAGENAME).meta.maintainers.

But i don’t know a way of finding out, the attribute path of a package just knowing the store path.

So i wondered if there was maybe another way to approach this.

Maybe it isn’t even possible.

3 Likes

Going from store paths to maintainers is a bit difficult, since the meta information doesn’t enter into their drv files. You’d need to build up a lookup table of drvPath to maintainer(s) which is certainly possible. Hydra would already have such a cache, but it doesn’t offer a way to query it for many store paths without essentially DoS-ing it. You could try using nix-env --json --out-path -qaP -f <nixpkgs used by your system> for building a lookup table from store path to attribute path.

Another approach is trying to pull out the relevant packages out of the evaluate configuration of your system. E.g. the following will give you all maintainers of packages in environment.systemPackages:

builtins.concatLists (
  builtins.map
    (x: x.meta.maintainers or [])
    (import <nixpkgs/nixos> {}).config.environment.systemPackages
)

This will of course be incomplete – there are packages installed on your system that don’t use systemPackages (think per user packages, services.*, …). Getting an exhaustive list may be prove difficult.

You can also try injecting a replacement for mkDerivation that logs all maintainers while evaluating your system configuration:

(import <nixpkgs/nixos> {
  configuration = { ... }: {
    imports = [ <nixos-config> ];
    config.nixpkgs.overlays = [
      (self: super: {
        stdenv = super.stdenv.override {
          mkDerivationFromStdenv = stdenv: args:
            let
              args' = if builtins.isFunction args then super.lib.fix args else args;
              res = (import (self.path + "/pkgs/stdenv/generic/make-derivation.nix") { inherit (self) config lib; }) stdenv args;
              m = builtins.concatStringsSep ", " (builtins.map (x: x.name) args.meta.maintainers or []);
            in builtins.trace "${args'.pname or args'.name}: ${m}" res;
        };
      })
    ];
  };
}).system

This will of course give you a lot of maintainers you don’t care about for build-time only dependencies and also log derivations that aren’t strictly packages (sources, patches, bootstrapping stuff, …).

1 Like

ok i did something now, similar to what you proposed but on a flake based system:

#!/usr/bin/env fish

# get json for all nixpkgs packages
nix search nixpkgs#legacyPackages.x86_64-linux --json > ./packages.json

# get all derviation paths in your nix store
echo /nix/store/*.drv | sed 's/ /\n/g' > drvs.txt

# get all pnames of all derivations that have one (e.g. source drvs wont)
echo -n > ./pnames.txt
for drv in (cat ./drvs.txt)
	cat $drv | sed 's/),(/\n/g' | grep '"pname","' | rev | cut -c 2- | rev | cut -c 10- | tee -a ./pnames.txt
end
sort -u -o ./pnames.txt ./pnames.txt 

# get all attribute paths of the extracted pnames (these found in nixpkgs)
echo -n > ./attrpaths.txt
set packagejsons = (cat ./packages.json)
for pname in (cat ./pnames.txt | uniq)
	echo $packagejsons | sed 's/},/},\n/g' | grep -m 1 "\"pname\":\"$pname\"" | cut -d '"' -f 2 | tee -a ./attrpaths.txt
end
sort -uo ./attrpaths.txt ./attrpaths.txt

# get all maintainers for the found attribute paths (for these that have maintainers)
echo [ > ./maintainers.txt
for attrpath in (cat attrpaths.txt)
	nix eval nixpkgs#$attrpath.meta.maintainers | tee -a ./maintainers.txt
end
echo ] >> ./maintainers.txt

# sometimes maintainers maintain a package twice
sed -i 's/«repeated»//g' ./maintainers.txt

# extract names of the maintainers
nix eval --impure --expr "map (x: x.name) (builtins.concatLists (import ./maintainers.txt))" | rev | cut -c 4- | rev | cut -c 4- > clean_maintainers.txt
sed -i 's/" "/\n/g' ./clean_maintainers.txt


# get number of packages that had maintainers
pkgnumber=$(cat attrpaths.txt | wc -l)

tailnum=20

echo The $tailnum maintainers maintaining most of the packages on your system are each maintaining the following numbers of packages:

# print the maintainers for your packages
cat clean_maintainers.txt | sort | uniq -c | sort -h | tail -n $tailnum

echo "of total $pkgnumber installed packages (from nixpkgs)"

This will, depending on the number of packages and the speed of your computer, take probably over an hour :skull: .
But because of the sorting and the tee instead of >>, you get somewhat of a progress indicator ;).
If anyone is interested: feel free to improve it.

my result:

22 Austin Seipp
24 Anderson Torres
24 Rahul Gopinath
25 Daniel Nagy
25 Hraban Luyat
25 Kasper Gałkowski
25 Luke Gorrie
26 Franz Pletz
26 Robin Gloster
32 Michael Weiss
39 Eelco Dolstra
42 Jason O’Conal
56 Daniel Șerbănescu
58 Tor Hedin Brønner
62 Maxine Aubrey
63 Bobby Rong
78 Michael Raskin
79 Paul Trehiou
96 Jan Tojnar
120 Thomas Tuegel
of total 1490 installed packages (from nixpkgs)

4 Likes

That’s a surprisingly short list! Very cool script and idea.

It’d be cool if nix had some kind of first party support for these metadata gathering tasks. Could be very useful for bill-of-material type things.

nah i used tail -n 20 to get the 20 most maintaining maintainers
it’s actually 394 maintainers in total
202 with one package, 66 with 2 packages, 30 with 3 packages and 96 with 4+ packages

I got bored and decided to make a Nix program for this:

let
  nixpkgs = builtins.getFlake "nixpkgs";
  inherit (nixpkgs) lib;

  packageNames = builtins.fromJSON (builtins.readFile ./packages.json);
  system = builtins.fromJSON (builtins.readFile ./system.json);

  packages = lib.mapAttrs' (path: _: {
    name = path;
    value = lib.attrByPath (lib.splitString "." path) (throw "not found: ${path}") nixpkgs;
  }) packageNames;

  packagesByDrv = lib.mapAttrs' (path: value: {
    name = builtins.unsafeDiscardStringContext value.drvPath;
    inherit value;
  }) (lib.filterAttrs (_: value: (builtins.tryEval value.drvPath).success) packages);

  toMaintainers = v: if v ? deriver then packagesByDrv.${v.deriver}.meta.maintainers or null else null;
  packagesMaintainers = lib.filter (x: x != null) (map toMaintainers system);

  sum = lib.foldl' (x: y: x + y) 0;
in lib.foldl' (acc: maintainers: let
  githubs = lib.listToAttrs (map ({ github, ... }: { name = github; value = 1; }) maintainers);
in lib.zipAttrsWith (_: sum) [acc githubs]) {} packagesMaintainers

Put the output of nix search nixpkgs#legacyPackages.x86_64-linux --json into packages.json, and the output of nix path-info --json -r /run/current-system in system.json, and this spits out an attrset whose keys are github names, and values are the number of packages they’re in the maintainers list of. It only took 9GB of RAM to calculate :stuck_out_tongue:

1 Like

i got { } bruh
packageMaintainers is [ ], probably every maintainer is null
i need to investigate further

There was an RFC to make this sort of thing a feature of nixos-rebuild (where the main use of the information would be to display unmaintained packages): [RFC 0081] Show unmaintained packages by Lassulus · Pull Request #81 · NixOS/rfcs · GitHub

1 Like

@quantenzitrone I guess maybe you’re packages.json and system.json are based on different nixpkgs versions?

Yes, they are different versions, but does it matter?
Wouldn’t the drv path for every package that hasn’t changed be the same?

Yea, but it’s not wildly uncommon for two nixpkgs versions to have basically zero packages in common aside from potentially bootstrap packages. Like whenever glibc is updated, that pretty much changes literally everything.

1 Like

ooooh, well that explains a lot

A bit late, but here’s one more way to do this with sbomnix:

# Generate meta.json for sbomnix to read the meta information from:
nix-env -qa --meta --json '.*' > meta.json

# Run sbomnix to generate sbom out of /run/current-system:
nix run github:tiiuae/sbomnix#sbomnix -- --meta=meta.json /run/current-system

# Query the generated sbom.csv for maintainers, output top-5:
nix-shell -p csvkit --run \
  'csvsql --verbose --query "select meta_maintainers_email from sbom" sbom.csv' \
  | tr ";" "\n" | sort | uniq -c | sort -nr | head -n5

As an example I get the following list of maintainers on my system:

    706 ""
    142 maxeaubrey@gmail.com
    136 rjl931189261@126.com
    134 torhedinbronner@gmail.com
    133 daniel@serbanescu.dk

What exactly are those 706 packages with no maintainer?:

# Remove tail at the end of pipeline to see the full list
nix-shell -p csvkit --run \
  'csvsql --verbose --query "select name,meta_maintainers_email from sbom where meta_maintainers_email is null" sbom.csv' \
  | uniq | tail

xprop-1.2.5,
xrandr-1.5.1,
xrdb-1.2.1,
xrefresh-1.0.6,
xscreensaver.pam,
xserver.conf,
xsession-wrapper,
xset-1.2.4,
xsetroot-1.1.2,
zlib-1.2.13,
1 Like

i would like to try this, but nix-env is defunct due to me switching to a purely flake based system:

error: selector '.*' matches no derivations

do you know how to do this with flakes?

(well just adding a channel again temporarily fixes this, but …)

You can add your nixpkgs input to NIX_PATH if you need integration with nix tools that depend on NIX_PATH: https://github.com/NobbZ/nixos-config/blob/0dc416d1e89b1d1b2390ce3e33726710c4856f11/nixos/modules/flake.nix#L29

I’m not really an expert here. Do you have a link you could point me to for trialing a system like yours?
I assume something like below also doesn’t work in your environment (?):

nix-env -qa --meta --json -f $(nix-shell -p nix-info --run "nix-info -m" | grep "nixpkgs: " | cut -d'`' -f2) '.*' > meta.json

i currently have this as my configuration for nix.
It enables the experimental features flakes nix-command and repl-flake, removes the autoupdated global flake registry and adds all my flake inputs as system registry.
Once you enabled flakes and nix-command, you basically can just use

$ nix registry list

to get a horribly unreadable output of the flake registries installed on your system, and

$ nix profile [...]

for managing the imparative-like packages on your system (as replacement for nix-env)

Or do you mean nix flakes in general?

The man pages for the nix commands are under man nix3-<command>-<subcommand> btw,
also accessible with nix <command> <subcommand> --help.