Nix-shell and output path

If I try to investigate a working build with nix-shell, e.g.

$ nix-shell -A pkgs.openttd
$ echo $out
/nix/store/9kwby2bc06hr33jys9jla5knl1m7c631-openttd-1.9.2

This is the same $out as nix-build:

$ nix-build -A pkgs.openttd --dry-run
these paths will be fetched (16.22 MiB download, 34.37 MiB unpacked):
  /nix/store/9kwby2bc06hr33jys9jla5knl1m7c631-openttd-1.9.2

What worries me is I’ll go into nix-shell, mess up some stuff (impurely) and run e.g. genericBuild which will interfere with existing files at $out and break the closure.

Something like

$ nix-build -A pkgs.foo
$ nix-shell -A pkgs.foo
$ touch $out/new_file

Trying it out, it looks like I get “permission denied”, which is probably making it safe, but it means I can’t manually test-run genericBuild (and if I put chmods in my derivations might not be safe?)

Perhaps when running in the shell an extra dummy attribute could be set in the derivation so that the checksum is different?

All your discoveries are right. The point is that, usually, nix-shell is only used to test compilation, and possibly run tests (checks) but not running the actual install, or the post-install tests (installChecks).

If you want to do that, changing the hash is not enough, as any path in the store is read-only. A good example is given by nix itself, where prefix and other configuration flags are overridden to install in ./dist instead of whatever store path was computed. See https://github.com/NixOS/nix/blob/6b83174ffffbdfc3f876d94d5178e0b83f675cae/shell.nix#L18-L24 for details.

1 Like

https://github.com/NixOS/nix/pull/3036

2 Likes

If you want to run genericBuild you can just say something like out=$(mktemp -d) first.

I have a hook that changes all output variables for a derivation when included in its build inputs, removing the permission error on the store:

addEnvHooks "$hostOffset" dropIntoBuildShell

dropIntoBuildShell(){
  test -n "$NIX_SET_LOCAL_OUTPUTS" && return

  # Note: we override TMPDIR to avoid auditTmpdir failure
  # (outputs cannot be children of $TMPDIR)
  export base=$(mktemp -t -d $name.XXXXXXXXXX)
  export TMPDIR=$base/tmp
  mkdir -p $TMPDIR

  echo "dropIntoBuildShell: settings outputs in $base directory"
  for output in $outputs
  do
    export $output="$base/output-$output"
  done

  echo "dropIntoBuildShell: moving to $TMPDIR"
  cd $TMPDIR

  echo "dropIntoBuildShell: will automatically run genericBuild"
  export shellHook+=" genericBuild"

  export NIX_SET_LOCAL_OUTPUTS=1
}

It is a bit cumbersome because you have to add the hook to the build inputs of the derivation you are hacking. This can be done, when hacking hello in a nixpkgs checkout:

nix-shell -E 'with import ./. {}; hello.overrideAttrs ({ nativeBuildInputs ? [], ...} : { nativeBuildInputs = nativeBuildInputs ++ [ dropIntoBuildShellHook ]; })'

Assuming dropIntoBuildShellHook points to the hook. This could be simplified by adding a wrapper in all-packages.nix, or even better: adding an attribute to mkDerivation that includes the hook in the build inputs (thus allowing to type something like: nix-build -A hello.shellDrop)

It also drops the shell in a temporary directory. I added this step because if you have e.g src = ./directory, the genericBuild’s unpackPhase will try to create directory in the current folder while it exists and fail.
It also automatically runs genericBuild, because I always did it by hand.