Edit: Want to execute a .nix like “./whatever”?
Use this and add parameters to the inner arguments. There must always be at least one.
#! /usr/bin/env nix-shell
#! nix-shell -i "nix-shell -v" -p ""
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
This was edited nonlinearly and may contain inconsistencies.
To my massive irritation, calling nix-shell on a nix file that isn’t in $PWD
fails:
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN/lol]$ ../uncursed.nix
evaluating file '/nix/store/ppgcg2ns9sqq5dqzm05s6x8xddmb2q2l-nix-2.3.10/share/nix/corepkgs/derivation.nix'
error: getting status of '/run/user/1000/tmp.6T3BRBy0aN/default.nix': No such file or directory
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN/lol]$ cat ../uncursed.nix
#! /usr/bin/env nix-shell
#! nix-shell -v
{pkgs ? (import (import ./nix/sources.nix).nixpkgs {}) }: {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [ git ];
};
}
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN/lol]$ nix-shell ../uncursed.nix
evaluating file '/nix/store/ppgcg2ns9sqq5dqzm05s6x8xddmb2q2l-nix-2.3.10/share/nix/corepkgs/derivation.nix'
error: getting status of '/run/user/1000/tmp.6T3BRBy0aN/default.nix': No such file or directory
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN/lol]$ cd ..
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN]$ nix-shell uncursed.nix
evaluating file '/nix/store/ppgcg2ns9sqq5dqzm05s6x8xddmb2q2l-nix-2.3.10/share/nix/corepkgs/derivation.nix'
error: getting status of '/run/user/1000/tmp.6T3BRBy0aN/default.nix': No such file or directory
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN]$ ls
cursed.nix lol nix uncursed.nix
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN]$ mv uncursed.nix shell.nix
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN]$ nix-shell shell.nix
evaluating file '/nix/store/ppgcg2ns9sqq5dqzm05s6x8xddmb2q2l-nix-2.3.10/share/nix/corepkgs/derivation.nix'
evaluating file '/run/user/1000/tmp.6T3BRBy0aN/nix/sources.nix'
evaluating file '/nix/store/rd5ha76r9bia75p3b8znxkll84qsxdp6-source/default.nix'
evaluating file '/nix/store/rd5ha76r9bia75p3b8znxkll84qsxdp6-source/lib/minver.nix'
...
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN]$ cd lol/
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN/lol]$ cat ../cursed.nix
#! /usr/bin/env nix-shell
#! nix-shell -i "bash -c 'set -x; fr=$(realpath \"$1\"); dir=$(dirname \"$fr\"); fname=$(basename \"$fr\"); shift; pushd \"$dir\"; nix-shell -v \"$fname\" \"$@\";' -- " -v -p ""
# The shebang does work just maybe not in the way you/I think?
{pkgs ? (import (import ./nix/sources.nix).nixpkgs {}) }: {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [ git ];
};
}
[nix-shell:/run/user/1000/tmp.6T3BRBy0aN/lol]$ ../cursed.nix
evaluating file '/nix/store/ppgcg2ns9sqq5dqzm05s6x8xddmb2q2l-nix-2.3.10/share/nix/corepkgs/derivation.nix'
evaluating file '/nix/store/b44y8fqa9523hcs3krlq4qarsw6nqgxy-nixos-21.05pre275822.916ee862e87/nixos/default.nix'
evaluating file '/nix/store/b44y8fqa9523hcs3krlq4qarsw6nqgxy-nixos-21.05pre275822.916ee862e87/nixos/lib/minver.nix'
...
evaluating file '/nix/store/b44y8fqa9523hcs3krlq4qarsw6nqgxy-nixos-21.05pre275822.916ee862e87/nixos/pkgs/development/libraries/ncurses/default.nix'
+ f ../cursed.nix
++ realpath ../cursed.nix
+ fr=/run/user/1000/tmp.6T3BRBy0aN/cursed.nix
++ dirname /run/user/1000/tmp.6T3BRBy0aN/cursed.nix
+ dir=/run/user/1000/tmp.6T3BRBy0aN
++ basename /run/user/1000/tmp.6T3BRBy0aN/cursed.nix
+ fname=cursed.nix
+ shift
+ pushd /run/user/1000/tmp.6T3BRBy0aN
/run/user/1000/tmp.6T3BRBy0aN /run/user/1000/tmp.6T3BRBy0aN/lol
+ nix-shell -v cursed.nix
evaluating file '/nix/store/ppgcg2ns9sqq5dqzm05s6x8xddmb2q2l-nix-2.3.10/share/nix/corepkgs/derivation.nix'
...
I probably messed up and misdiagnosed something and broke something else and misunderstood a third thing, but here goes:
https://github.com/NixOS/nix/blob/8803753666023882515404177b08f3f8bdad52a0/src/nix-build/nix-build.cc#L250 means we can’t have nice things because is searches for shell.nix
or default.nix
in $PWD or however the heck lstat()
works (because pathExists()
calls lstat()
).
To start remedying our relative path problem, we see we can use -p
with a dummy value to trigger “stop searching in my current directory kthx” mode.
Since the pathExists()
checks are after the exec()
s, we can pull off the rest of our chain.
Next we need to actually interpret our nix file somehow.
- The nix shebang recognition procedure is described at: https://github.com/NixOS/nix/blob/8803753666023882515404177b08f3f8bdad52a0/src/nix-build/nix-build.cc#L106
- nix
exec()
s itself here: https://github.com/NixOS/nix/blob/8803753666023882515404177b08f3f8bdad52a0/src/nix-build/nix-build.cc#L217
Since the exec()
uses a simple string substitution, since shebang mode is enabled when the nested nix is called with our executable shebanged file as its first argument, we can call arbitrary code through the -i
interpreter argument (thanks @grahamc ), which is only enabled in shebang mode (see heuristic).
To execute arbitrary code but be able to consume our arguments as well, we use a bash expression:
"bash -c '<arbitrary code here>; nix-shell -v \"$filename\" \"$@\" ' -- "
To prevent the shebang being interpreted again we pass -v as a noop-ish item to push the path out of the first-argument position then we pass the filename and remaining arguments. The --
at the end means the arguments the parent nix-shell passes at the end of the interpreter string are all arguments to the script/-c
we pass to bash.
An alternative approach @sternenseemann and @lukegb recognized involves naming the file such that it contains the substring nix-shell
(“Why did my files suddenly stop working when I started naming them with nix-shell?”). This however results in the shebang not being processed.
If there is a will there is a way.
Und ich will nicht upstream patchen for things that should work.