Difference between buildInputs and packages in mkShell

TLDR

  • WTF (exuse my frustration boiling over) is the difference between buildInputs and packages in the context of mkShell?

  • Is there any truth to the fairy tale that Claude told me (see below)?

  • Where can I find the authoritative answer, and how could I have found it myself in a reasonable amount of time?

The gory details

The difference between buildInputs and packages has never been clear to me. It’s 2025, so I asked Claude sonnet 3.5. The fundamental part of its response was [WARNING: THE FOLLOWING WAS GENERATED BY AN AI, DON’T BELIEVE A WORD OF IT WITHOUT CHECKING AN AUTHORITATIVE SOURCE]

  • buildInputs packages are available during both build and runtime. They get added to $PATH but also have additional integration:

    • Their binary directories are added to $PATH
    • Their library directories are added to $LD_LIBRARY_PATH
    • Their include directories are added to compiler include paths
    • Their pkg-config files are made available
    • Other standard Unix paths (man pages, etc.) are properly linked
  • packages are simpler - they just get added to $PATH during runtime. They don’t get the deeper system integration that buildInputs provides.

Which sounds clear, nice and authoritative, and gives me a feeling that I might, finally be able to understand the difference between the two. But how do I know any of this is true?

First, I try a little experiment and it suggests that Claude might be lying: I do not have lolcat installed on my system. I activate a shell defined in a flake in which lolcat is in packages but not buildInputs, so ‘man pages etc.’ is something that should be missing according to the above description, but the man pages work fine. But man pages are not really deep integration like library paths, pkg-config etc. Ok, but I really can’t be bothered to come up with more involved experiments … if all else fails, read the manual!

This is what the current version of the Nixpkgs Reference Manual has to say on packages in mkShell

  • packages (default: ). Add executable packages to the nix-shell environment.

Hmm, ok, that might agree with what Claude told me, but it might not.

This is what the same manual has to say about buildInputs in mkShell

that is to say, absolutely nothing. Ok, ok, I’m being facetious, buildInputs isn’t specific to mkShell so I should look elsewhere for its documentation.

Now, with ‘most’ things, if I type something like “<technology name> <specific thing>” into any semi-decent search engine, I tend to get the manual entry for that thing somewhere in the first few hits. Sadly, this is rarely the case with Nix-related searches. After having CTRL-F/Gd through a bazillion irrelevant occurrences of buildInputs in the aforementioned (almost infinitely long) single-page manual and other similar tiresome failures to find what I want, I give up.

And here I am, yet again: I was doing something, I had a doubt about some Nix details, searched for information, found some kind of dubious answer, looked for an authoritative answer, miserably failed to find it, and half an hour of my life has been wasted on this yet again, without making any progress whatsoever.

packages is just directly translated to nativeBuildInputs, nothing more, nothing less.

The manual covers the theory and difference between the various input types extensively here.

The tl;dr however is, anything you expect to run on the host you are on should go into nativeBuildInputs. That’s why they’re called native, because they are run on the host’s native architecture. They also often include bonus hooks that enable integration in the first place, such as the linking of pkg-config paths your LLM digested from somewhere.

Since things in a shell are virtually always expected to run on the host the shell is built on, it almost never makes sense to use buildInputs in a mkShell call. That’s why packages exists, so that there is an obvious and less confusing argument to put your packages in; people have just been cargo-culting buildInputs since long before that argument existed, so there are still a lot of examples with it floating around.

Most of the time either happens to work, but packages is more correct and enables various hooks to work properly that may otherwise cause you to scratch your head at why stuff isn’t working.

Technically you could choose to separate libraries out into buildInputs, but this in turn only matters for cross-architecture shenanigans, which you’re not going to run into with your everyday shell scenario, and would take significant additional configuration and understanding anyway.

So, in a nutshell, just use packages and forget the other inputs for mkShell exist.

8 Likes

I understand your frustration. That said, 30 minutes on the complexity of builds isn’t particularly much, that’s just the software world in a nutshell IME. Our industry has worked itself into a corner of needless, comprehensive complexity, whenever you try to look even slightly closer at anything this is what you will run into.

If I have any advice to get over this, understanding the fundamental building blocks of the nix ecosystem (basically the NixOS fixed-point module system and stdenv.mkDerivation, plus maybe some of the more important hooks like the one from pkg-config) in detail is all that I need most of the time. The nix pills, while a bit advanced for newbies, are actually good at explaining a good deal of that.

Any other problems can then be solved by studying the source, which is usually at least browsable, if not easy to read, and the ultimate authorative source on nix ecosystem knowledge.

2 Likes

I love the sound of this simple advice!

So when the manual says

Add executable [emphasis mine] packages to the nix-shell environment.

… it’s not being really helpful, it seems.

Totally agreed. But with Nix I find myself giving up before getting to the bottom of something, and then going through the same failure on my next attempt a few months later, far more often than with other technologies. I also find myself cargo culting far more than elsewhere.

I guess that this is partly because once I get something to work, somehow (which can often be a very long, painful and costly process), I can often rely on it for years to come without having to think about it. So the motivation and time to get to the bottom of something can be hard to find.

So when you realize you’ve spent 30 minutes on failing to understand the same thing for the Nth time, it gets a bit depressing.

Half an hour would be very little, if there had been a positive result.

Thanks. I hope I’ll remember to come back to this advice the next time I have the time to think about Nix itself, rather than merely needing Nix to get something else done quickly.

The trouble with using the source as reference documentation is that I can sometimes (and sometimes not, as frequently I am too ignorant of the context to gain insight in the time available) glean what happens, but rarely how it’s meant to be used and why it is like that.

3 Likes

Yep! Having context of the underlying building blocks is probably the missing link. I appreciate that reading source code isn’t ideal for every user, and it’d be great if there was better, authorative, high-level documentation. Good luck whenever you next need it :slight_smile:

1 Like

nativeBuildInputs!

4 Likes

I definitely didn’t have “buildPackages” in there an edit or two ago either… Thanks!

I don’t know where you got the answer from, but gcc would not go in the shell that way. If you want to override the C compiler, you override the stdenv (and gcc is the default C compiler on Linux in any case).

The authoritative answer is always in the code; I think @TLATER’s explanation does a good job of explaining the code and the reasoning behind it. Essentially mkShell is just a specialized case of stdenv.mkDerivation which exposes two more args: packages (nativeBuildInputs alias) and inputsFrom (a list of packages to pull inputs and shell hooks from). There’s also mkShellNoCC which is the stdenvNoCC.mkDerivation counterpart if you don’t need a C compiler.

If you want the general difference between nativeBuildInputs, buildInputs, etc., I discussed it further here: Frustrations about splicing - #21 by waffle8946

This reeks of LLM slop…

6 Likes