maralorn / nix-output-monitor

Pipe your nix-build output through the nix-output-monitor a.k.a nom to get additional information while building.
GNU Affero General Public License v3.0
939 stars 29 forks source link

nix, nix-*, nixos-rebuild, home-manager command aliases/wrappers #108

Open zeorin opened 1 year ago

zeorin commented 1 year ago

I just discovered your project, it looks very nice. I immediately wanted the full JSON-powered experience when calling home-manager switch, so I took a stab at implementing that.

Once I had that done, I nerd-sniped myself into trying to get it to work for nixos-rebuild, too. I could get this to work when using the --fast or --no-build-nix flags. Without those flags, nixos-rebuild builds/fetches a new version of nix and uses that instead.

I then created wrappers for the various nix commands, too. There's probably some edge cases in this code, but it's a starting point:

{ config, pkgs, lib, ... }:

let
  collectPathArgs = ''
    paths=()
    while [ "$#" -gt 0 ]; do
      arg="$1"
      [[ "$arg" =~ ^--?.+ ]] && break
      paths+=("$arg"); shift
    done
  '';
  pathArgs = ''"''${paths[@]}"'';
  collectFlakeFlags = ''
    flakeFlags=()
    while [ "$#" -gt 0 ]; do
      arg="$1"
      case "$arg" in
        ${
          builtins.concatStringsSep "|" [
            "build"
            "bundle"
            "copy"
            "daemon"
            "derivation"
            "develop"
            "doctor"
            "edit"
            "eval"
            "flake"
            "fmt"
            "hash"
            "help"
            "help-stores"
            "key"
            "log"
            "nar"
            "path-info"
            "print-dev-env"
            "profile"
            "realisation"
            "registry"
            "repl"
            "run"
            "search"
            "shell"
            "show-config"
            "store"
            "upgrade-nix"
            "why-depends"
          ]
        })
          break
          ;;
        *)
          flakeFlags+=("$arg"); shift
          ;;
      esac
    done
  '';
  flakeFlags = ''"''${flakeFlags[@]}"'';
  nixNomArgs = "--log-format internal-json --verbose";
  nixBuildCmdWithNomArgs = buildCmd: ''
    ${collectPathArgs}
    ${buildCmd} ${pathArgs} ${nixNomArgs} "$@"
  '';
  nixShellCmdWithNomArgs = shellCmd: ''
    ${shellCmd} ${nixNomArgs} "$@"
  '';
  nixStoreCmdWithNomArgs = storeCmd: ''
    operation="$1"; shift
    case "$operation" in
      --realise|-r)
        ${collectPathArgs}
        ${storeCmd} "$operation" ${pathArgs} ${nixNomArgs} "$@"
        ;;
      *)
        ${storeCmd} "$operation" "$@"
        ;;
    esac
  '';
  nixWithNomArgs = nix:
    pkgs.symlinkJoin {
      name = "nix-with-nom-args-${nix.version}";
      paths = (lib.attrsets.mapAttrsToList pkgs.writeShellScriptBin {
        nix = ''
          program="$(basename $0)"
          case "$program" in
            nix)
              ${collectFlakeFlags}
              command="$1"; shift
              case "$command" in
                build)
                  ${nixBuildCmdWithNomArgs "${nix}/bin/nix ${flakeFlags} build"}
                  ;;
                shell)
                  ${nixShellCmdWithNomArgs "${nix}/bin/nix ${flakeFlags} shell"}
                  ;;
                store)
                  ${nixStoreCmdWithNomArgs "${nix}/bin/nix ${flakeFlags} store"}
                  ;;
                *)
                  ${nix}/bin/nix ${flakeFlags} "$command" "$@"
                  ;;
              esac
              ;;
            *)
              "${nix}/bin/$program" "$@"
              ;;
          esac
        '';
        nix-build = nixBuildCmdWithNomArgs "${nix}/bin/nix-build";
        nix-shell = nixShellCmdWithNomArgs "${nix}/bin/nix-shell";
        nix-store = nixStoreCmdWithNomArgs "${nix}/bin/nix-store";
      }) ++ [ nix ];
    };
  nixNomPkgs = { nix ? null, nixos-rebuild ? null, home-manager ? null }:
    lib.attrsets.mapAttrs pkgs.writeShellScriptBin ((if nix != null then {
      nix = ''
        program="$(basename $0)"
        case "$program" in
          nix)
            ${collectFlakeFlags}
            command="$1"; shift
            case "$command" in
              build|shell|develop)
                ${pkgs.nix-output-monitor}/bin/nom ${flakeFlags} "$command" "$@"
                ;;
              *)
                ${nix}/bin/nix ${flakeFlags} "$command" "$@"
                ;;
            esac
            ;;
          *)
            "${nix}/bin/$program" "$@"
            ;;
        esac
      '';
      nix-build = ''
        ${pkgs.nix-output-monitor}/bin/nom-build "$@"
      '';
      nix-shell = ''
        ${pkgs.nix-output-monitor}/bin/nom-shell "$@"
      '';
      nix-store = ''
        ${nixWithNomArgs nix}/bin/nix-store "$@" \
          |& ${pkgs.nix-output-monitor}/bin/nom --json
      '';
    } else
      { }) // (if nixos-rebuild != null then {
        nixos-rebuild = ''
          ${pkgs.expect}/bin/unbuffer \
            ${
              nixos-rebuild.override (old: { nix = nixWithNomArgs old.nix; })
            }/bin/nixos-rebuild "$@" \
            |& ${pkgs.nix-output-monitor}/bin/nom --json
        '';
      } else
        { }) // (if home-manager != null then {
          home-manager = ''
            PATH="${nixWithNomArgs pkgs.nix}/bin:$PATH" \
              ${pkgs.expect}/bin/unbuffer \
              ${home-manager}/bin/home-manager "$@" \
              |& ${pkgs.nix-output-monitor}/bin/nom --json
          '';
        } else
          { }));
  nomAliases = pkgs:
    lib.attrsets.mapAttrs (name: pkg: "${pkg}/bin/${name}") (nixNomPkgs pkgs);
  wrapWithNom = let inherit (pkgs) symlinkJoin;
  in (pkgs:
    symlinkJoin {
      name = "wrapped-with-nom";
      paths = (builtins.attrValues (nixNomPkgs pkgs))
        ++ (builtins.attrValues pkgs);
    });

in {
  home.shellAliases =
    nomAliases { inherit (pkgs) nix nixos-rebuild home-manager; };

  # or

  home.packages = [
    (lib.hiPrio
      (wrapWithNom { inherit (pkgs) nix nixos-rebuild home-manager; }))
  ];
}

Personally, I only use the aliases, since if something isn't working due to the way I've wrapped it, I can just use command to use the un-wrapped version, e.g. command home-manager switch.

It works quite well so far. home-manager always triggers nix-output-monitor error: DerivationParseError "string" but other than that it's flawless.

home-manager switch:

asciicast

nixos-rebuild build --fast:

asciicast

ChanceHarrison commented 1 year ago

Related: #76