Looking for clarity on Nix developer shells and their design

While digging deeper, I found a few interesting GitHub issues and PRs:

A recurring theme in these discussions is that there are two very different use cases at play:

I guess in general there are two major use cases:

  1. debug a build
  2. create a dev environment with tools suitable for developing on a project

@DavHau in NixOS/nix#4609

nix develop comes with a bunch of assumptions - it’s designed to provide the build environment of a derivation.
That’s great when you want to poke at a package, rebuild quickly, or debug a derivation.
But those assumptions don’t necessarily match what we need for everyday development work, especially for projects that don’t produce Nix derivations.
And as @zimbatm noted in cachix/devenv#240, maybe nix develop should have been called nix debug, since it’s really about entering a build environment rather than setting up a developer workspace.

This made me wonder whether we’re stretching nix develop beyond what it was designed for - and if there’s any real reason to.
What exactly does Nix core need to do here that can’t be done entirely in userland (e.g. in nixpkgs)?

For debugging, sure - nix develop is convenient.
But for developer environments, I couldn’t find a reason this couldn’t live completely outside of core Nix.

This led me to experiment with a minimal userspace implementation that looks something like this:

pkgs: let
  inherit (pkgs) lib;
  mkRcFile = {
    name,
    shellHook,
    packages,
  }:
    pkgs.writeTextFile {
      name = "${name}-rc";
      text = ''
        [ -n "$PS1" ] && [ -e ~/.bashrc ] && source ~/.bashrc;

        shopt -u expand_aliases

        out='$PWD/outputs/out'
        export NIX_BUILD_TOP="$(mktemp -d -t nix-shell.XXXXXX)"
        ${lib.toShellVar "nativeBuildInputs" packages}
        . "${pkgs.lib.escapeShellArg pkgs.stdenvNoCC}/setup"

        ${shellHook}
        shopt -s expand_aliases
      '';
    };
  mkDeveloperShell = {
    name ? "developer-shell",
    packages ? [],
    shellHook ? "",
    bash ? pkgs.bashInteractive,
    ...
  }:
    pkgs.writeScriptBin name ''
      #!${lib.getExe bash}
      ${pkgs.lib.getExe bash} --rcfile ${mkRcFile {inherit name shellHook packages;}}
    '';
in
  mkDeveloperShell {
    shellHook = ''
      echo $JAVA_HOME
      exit
    '';
    packages = [
      pkgs.jdk_headless
    ];
  }

It gives the same power (including the option to use stdenv with setup hooks) but offers more flexibility - because it can evolve independently of Nix itself.

So here’s the question:
If developer environments can be expressed entirely in userspace, is using nix develop or nix-shell for that purpose an anti-pattern?

1 Like