How can I make a shell.nix that runs some code, only using inbuilt derivation function?

266 views Asked by At

I basically want to create my own simple version of mkShell, so now I'm trying to understand how it would even be possible to execute some code before the shell actually pops up. I googled for a bit, and came up with this:

let pkgs = import <nixpkgs> {}; in
derivation {
  name = "hello";

  shellHook = ''
    echo "hello world!"
  '';

  system = builtins.currentSystem;

  builder = "${pkgs.bash}/bin/bash";
  args = [ ./setup.sh ];
}

...where setup.sh contains echo "hello" > $out.

From nix-shell documentation:

If the derivation defines the variable shellHook, it will be run after $stdenv/setup has been sourced. Since this hook is not executed by regular Nix builds, it allows you to perform initialisation specific to nix-shell. For example, the derivation attribute

shellHook =
  ''
    echo "Hello shell"
    export SOME_API_TOKEN="$(cat ~/.config/some-app/api-token)"
  '';

But that doesn't output anything, although if I replace derivation {} with pkgs.mkShell {}, then it does. How can I make this behaviour work without actually using pkgs.mkShell?

2

There are 2 answers

4
Charles Duffy On

One means to avoid this issue is to use the newer replacement for nix-shell, nix develop, to invoke a flake devShell. As a working example, after creating the following flake.nix:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = import nixpkgs { inherit system; };
    in {
    devShells = {
      exampleDevShell = derivation {
        inherit system;
        name = "so-question-77656854";
        builder = "${pkgs.bash}/bin/bash";
        args = [ "-c" ''touch "$out"'' ];
        outputs = [ "out" ];
        shellHook = ''
          echo "Hello, world"
        '';
      };
    };
  });
}

...running nix develop .#exampleDevShell prints Hello, world to stdout.

0
Wynell On

The question got answered on the NixOS forum.

So, now I know that I have to override stdenv to run make this work.

nix-shell sources the $stdenv/setup script. And then before shells open runs runHook shellHook, so runHook function can also be defined in that $stdenv/setup script.

So if I

  1. make a myStdEnv/setup file and put there for example echo "hello world"
  2. pass stdenv = ./myStdenv; in the derivation{}

Then it will be enough to output "hello world" when I run nix-shell.

Complete code:

shell.nix

let pkgs = import <nixpkgs> {}; in
derivation {
  name = "hello";

  system = builtins.currentSystem;

  builder = "${pkgs.bash}/bin/bash";
  args = [ ./setup.sh ];

  stdenv = ./myStdEnv;
}

myStdEnv/setup

echo "hello world"

(This is enough for me to understand what I wanted to understand, but I suppose it would be good to also add a runHook function there)