`buildInputs` doesn't work

My flake.nix
{
  description = "";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-24.11";
    nixpkgs-unstable.url = "nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    {
      nixpkgs,
      nixpkgs-unstable,
      flake-utils,
      ...
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
      in
      rec {
        formatter = pkgs.nixfmt-rfc-style;

        packages.default = pkgs.stdenv.mkDerivation rec {
          pname = "test.sh";
          version = "0.1.0";

          src = ./.;

          installPhase = ''
            mkdir -p $out/bin
            chmod +x test.sh
            cp test.sh $out/bin/test.sh
          '';

          buildInputs = with pkgs; [
            bash
            python313
          ];
        };
      }
    );
}
My test.sh
#!/bin/bash

python --version || :
python3 --version || :
python3.13 --version || :

Why can’t my script find Python 3.13? It is right there in the buildInputs section! Am I doing something wrong? The example doesn’t get any more minimal than that and I still don’t see the issue.

Output of the script:

Python 3.10.12
Python 3.10.12
/nix/store/6r6bqzbvw9q5dd55cl52dizb99y7cc80-test.sh-0.1.0/bin/test.sh: line 5: python3.13: command not found

Interestingly, this is what I get when trying to echo the PATH variable (I run the flake with nix run):

/home/user/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/home/user/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/home/user/.cargo/bin:/home/user/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:/home/user/Scripts:/home/user/.local/bin:/var/lib/flatpak/exports/bin

There is not a single store there! This is just my normal regular PATH. As if I am running the script without nix at all.

UPD: I’ve updated nix from 2.26 to 2.27.1. It didn’t solve it. I might be doing something wrong in my flake. Btw, the only configuration I have in ~/.config/nix/nix.conf is experimental-features = nix-command flakes.

The problem you encounter is that you are running the script “without” nix. Look at the contents of the file /nix/store/6r6bqzbvw9q5dd55cl52dizb99y7cc80-test.sh-0.1.0/bin/test.sh. They are exactly what you have in the test.sh in your repository. The buildInputs are available to your derivation, but you need to somehow tell your script to use them. There are several ways to do this: you could make a wrapper around the script, that sets the PATH using makeWrapper, or you could patch the test.sh file itself by using something like substituteAll. There are surely more methods, but this might give you an idea.

2 Likes

Solved :white_check_mark:

I had to add this piece of code.

nativeBuildInputs = with pkgs; [
  makeWrapper
];

postFixup = ''
  wrapProgram $out/bin/test.sh \
    --prefix PATH : ${lib.makeBinPath [ pkgs.python313 ]}
'';

Although I am still very confused why using buildInputs didn’t modify path for me. Isn’t that the whole point of buildInputs??? Using wrapProgram feel wrong and looks like a hack.

This should absolutely be mentioned in the documentation somewhere… I got the impression that you only have to include dependencies in the buildInputs and the rest is done for you…

No, that’s not how nix builds work. buildInputs is to get your build inputs into the build scope, i.e., make them available to link against in a C-style build.

Once the builder has finished, nix then reads the resulting artifact (all files in $out, binary or not), and looks for nix store paths in the literal file contents.

If a store path from the buildInputs (or any of the other *Inputs) is found, nix then includes that store path in the closure.

In other words, nix does not include /nix/store/<hash>-python in the closure just because it’s in buildInputs. It also needs to be referenced somewhere in the output of the build.


In practice, for an interpreter like python, it’s rarely referenced by its absolute path, so it’s not normally included in output closures. Even if nix included python in the output closure, it wouldn’t help you very much, since including it in the output closure would not add it to $PATH - it’d be inert in your nix store, uselessly taking up disk space, while your script still fails to find it.

There are two solutions:

  1. Use wrapProgram to write a wrapping script which inserts that store path into $PATH
    • Now we have a reference to the store path in one of the output files, and so nix will successfully include python in the output closure.
    • We also have it in $PATH, so if you use call python in your bash script, bash can successfully figure out what that means.
  2. Patch the script in the patchPhase to resolve all references to python to its absolute path in the nix store
    • This will also work, and is sometimes more appropriate. Usually the only good option if the script uses e.g. /usr/bin/python, but it’s more cumbersome with general purpose scripts that don’t use absolute paths.

There is also propagatedBuildInputs, which sounds like it’d make runtime dependencies available. In practice, anything added to that list is just added to a bonus file in the output, which then makes it available for linking against in things that depend on the derivation which uses it - this still doesn’t help with interpreters, though.


By the way, adding bash to your buildInputs is also pointless. The only reason that “works” is that presumably your shebang resolves to a valid shell at runtime. You could remove the bash from that and nothing would change.

If you wanted to explicitly use the bash from the nix build, you would have to patch your scripts’ shebang to use this. Something like:

patchPhase = ''
  sed -i '1c\#!${lib.getExe pkgs.bash}'
'';

You should not include bash or python in buildInputs, in either case, unless you link against them with a compiler or suchlike. If you use them during the build, they should be in nativeBuildInputs, otherwise you’ll need to patch them into your output somehow.

1 Like

Mostly agreed, except runtimeBuildInputs isn’t a thing.

Also for some more context, some hooks (e.g. autoPatchelfHook) will do extra things with the contents of buildInputs, but usually the builder (mkDerivation in this case) itself won’t.

1 Like

Ahhhh I keep doing that when I mean native.