A script that I make with `writeScript` executes when I enter nix-shell

What I’m trying to accomplish here is to have a utility script that is part of my nix shell for a project. I think that I should be able to create it using writeScript, but the script actually executes as I enter the shell even though what I want is to write it to disk in the nix store and just have it in my path. Here’s a minimal example (replace pkgs-20.03 with your own naming scheme):

let
  pkgs = import <pkgs-20.03> {};
  runner = pkgs.writeScript "run.sh" ''
    #! ${pkgs.stdenv.shell}
    echo ABORT!
    exit 1
    '';
in pkgs.mkShell {
  buildInputs = [
    runner
  ];
}

And then I start the shell with nix-shell:

[] savanni@garnet:~/s/a/core (savanni/saving *) $ nix-shell demo.nix 
ABORT!

This occurs when I put the script in buildInputs and in nativeBuildInputs. I can see the script gets written to the nix store, though. So, how do I just keep it from executing as I enter the shell?

1 Like

I would try using writeScriptBin instead which will place the script under $out/bin which will be then added to path. From a quick look it seems that the magic in stdenv will try and source an input if it’s a regular file (I could be mistaken on this though).

2 Likes

That works perfectly!

I didn’t expect that, actually. I see writeScript used in so many places in nixpkgs, and some of the places that I checked looked to me as though they were supposed to be for scripts to be used at runtime, not build time. Though maybe that’s different in mkDerivation instead of mkShell.

A little background on why this happens…

writeScriptBin and friends are all based on the writeTextFile function:

writeScript will create a script file directly in the nix store (destination = "" for writeTextFile).

writeScriptBin will create a directory in the nix store, containing a bin directory, which contains the script itself, which is much more like a conventional “package” than what you get from writeScript. This is achieved by setting destination = "/bin" for writeTextFile.

stdenv handles these differently when used in buildInputs: a plain script will be sourced as a so-called “setup hook”, intended to provide additional functionality within stdenv. I’m not sure this behaviour is still used within nixpkgs, as there’s another mechanism for setup hooks which is a little easier to understand: a package can provide the setupHook attribute which refers to such a script. This is most commonly done by build tools like cmake. Meanwhile, a directory containing a bin subdirectory will be interpreted as a tool used explicitly by the build, and the bin subdirectory will be added to PATH in the build environment (this behaviour may be restricted to nativeBuildInputs only in the future).

3 Likes