My setup is usually like this:
shell.nix:
{ nixpkgs ? import (fetchGit {
url = https://github.com/NixOS/nixpkgs-channels;
ref = "nixpkgs-unstable";
}) {} }:
with nixpkgs;
mkShell {
buildInputs = [ ... ];
SOME_ENV_VAR = "foo";
shellHook = ''
# usually link in a nix-built vendor directory or other housekeeping
'';
}
for the already mentioned direnv, my super sophisticated .envrc:
use nix
# optionally also some env vars, if they're supposed to be secret, otherwise they go into `shell.nix`.
My editor of choice is spacemacs, and I’m using emacs-direnv to automatically load my nix-shell for each buffer so all dependencies are found.
For caching the nix-shell I’m using the persistent cached shell variant of use nix, but that’s pretty optional, just speeds things up a lot.
Depending on who I’m working with, I might try to keep all other nix things in a nix/ directory in the project, but I think having a shell.nix and default.nix at top-level is just good practice and easier to work with. So in the subdir go things like a common pinned nixpkgs version, dockertools, overlays, gemset.nix, etc…
To be honest I’m still experimenting with what I find the most comfortable project layout to work with, but after a few years this has been pretty much the standard I build on.