Hi! I’ve been a happy user of NixOS for quite some time, but I couldn’t quite polish my nix-shell workflow and I am wondering if you have any practical tips?
I think that my problems with nix-shell boil down to the fact that it serves two purposes:
it can be used to hack on derivations for nixos itself. I.e, if I want to hack on a nix package, I can use nix-shell to recreate the derivation environment and poke around.
it can be used to hack on the usual software, which is not package as a derivation. I.e, the foo project can have something like sudo apt-get libbar libbaz libqux in it’s README.md, and I can use nix-shell to install the said packages to make the usual make workflow work.
Now, I use nix-shell solely for the second use-case; I hack on projects which needs stuff like openssl, zlib and pkg-config to build. However, it seems to me that nix-shell was designed with the first use-case in mind. So, here’s the list of specific problems I face when using nix-shell:
it’s not really intuitive to me how to write default.nix. For Cargo, I have
It seems that name = "cargo" and cargoEnv bits don’t actually have any relevance, that is, it’s possible to use arbitrary names in their place. I generally just copy-paste this to over projects I am working on, because I can’t really write it from scratch.
today’s problem: when I run nix-shell --run zsh, my $SHELL changes to bash from zsh, which breaks other tools
problem a friend of mine faced: if you run nix-shell --run zsh, the gcc there is actually a wrapper which injects some nix-specific flags (for example, for hardening), and those flags might break compilation
there’s no way do create a “default, system-wide dev environment”, I need to launch nix-shell explicitly. This is not ideal when you want to create a second terminal tab, or launch an IDE from the launcher and not from the console.
So, does anyone know a better workflows for developing on NixOS? It seems like I am looking for something like “nix-shell for humans”…
I use this dirty hack to save my direnv “use nix” environments from being garbage collected:
savedrv () {
if [ "$out" ]
then
drv="$(nix show-derivation "$out" | perl -ne 'if(/"(.*\.drv)"/){print$1;exit}')"
if [ "$drv" ] && [ -e "$drv" ]
then
ln -fs "$drv" .drv
ln -fs "$PWD/.drv" "/nix/var/nix/gcroots/per-user/$LOGNAME/$(basename "$PWD")"
fi
fi
}
Works fine with shell.nix, default.nix and also “use nix -p …”. Comments welcome, I don’t know a more elegant way to get the derivation from $out than by show-derivation.
{ 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.
I had read them, they did not handle the “nix-shell -p” case and I did not want to fiddle with nix-instantiate to duplicate what nix-shell already does.
You don’t have to add that { cargoEnv = foo; } bit – just foo is enough, i.e. the shell.nix file evaluates just to a mkDerivation call directly (with with and optionally let parts before it).
For me creating those shell.nix files just works; I always call nix-shell --pure -Q in that directory. I assume it depends on the particular projects and desired workflows.
It is basically a tool to bootstrap your nix-shell development environment with autoenv support and templates.
It may address some of your issues, but I have to admit that documentation is poor.
You should have a look at how the templates are set up in source code and see if that works for you.
It should at least cover your first two points in the list (maybe 4), but I only tested it with zsh and GitHub - zpm-zsh/autoenv: Autoenv for zsh but I’m guessing it’ll work with other shells / autoenvs / direnvs just as well (after bug fixing … )
The tool was built for myself, but I’m happy to look at any issues / pull requests.
This is really helpful. The part that I resonate with is
Roughly, I found a tension between the style nixpkgs expects and the style conducive to development
Yes, I don’t think there’s a super simple pathway here. I actually think it needs it’s own topic! Basically there’s no clear “this is what you add to your repo, and he’s how it ends up in a nixpkgs PR”
I don’t think there is a single example of projects that people have nix expression in their repository, and are also contained in nixpkgs. Why? Surely it would make sense. Do you have any examples?
I also updated my use_nix based on the latest version in the wiki. The only things I changed is to make it less likely for project names to collide and no dependency on jq.
I came to conclusion that nix-build (not nix-shell, because scripts of nix-shell interactive workflow tend to diverge with the derivation code, so a programmer has to maintain both) should have the following command-line options
--nix-build-top=___ to specify the working dir of $NIX_BUILD_TOP
--no-daemon for not sending job to nix-daemon as nixbld1 user will likely have no access to user’s project dir (--nix-build-top=___ might imply --no-daemon)
--phases=___ to choose which phases to perform
Then (in any shell):
Download and unpack sources: nix-build --nix-build-top=./ --phases=unpackPhase,patchPhase
Alternative to ./configure (can be run many times with the same working dir) nix-build --nix-build-top=./ --phases=configurePhase
Alternative to make (can be run many times with the same working dir reusing .o files) nix-build --nix-build-top=./ --phases=buildPhase
Alternative to make test (can be run many times with the same working dir) nix-build --nix-build-top=./ --phases=checkPhase
Alternative to make install (requires root privileges, dangerous as it impurely writes the result (which might be manually patched between phases) to nix store; overriding of $out to an user-writable directory can make it safe) nix-build --nix-build-top=./ --phases=installPhase,fixupPhase,installCheckPhase
That would nice, nix-shell is good for setting project-specific environments but it’s not easy to test and debug the building process. But will the dependency to stdenv become a problem?
I don’t use NixOS, but I rely on Nix packages on macOS (Darwin), primarily for the isolated development environments use case. I’ve had pretty good luck using the approach of direnv along with a default.nix file in a project directory. It’s a bit slow when switching into the directory, but otherwise tolerable.
Problem: In some projects, I do not want to have Nix developer tools to be available. This happens when the default OS developer tools or environment are actually the ones that should be used to do some project-specific building, and Nix tools get in the way. What should I do if I do not want Nix-specific Clang or anything else to be prepended to the shell’s default path?
Sample default.nix for reference:
with import <nixpkgs> {};
let
deps = [
imagemagick
mysql57
];
in
stdenv.mkDerivation {
name = "my-project";
src = null;
buildInputs = deps;
configurePhase = "";
buildPhase = "";
}
I’m no expert, but as far as I know nix-shell “just” install stuff in
the nix-store and fiddels around with your environment variables.
And if you are using direnv you can manipulate your environment
variables, right after you ran use-nix.
Either you do something like PATH=/usr/bin:$PATH (or where every your
compiler ist) or you fiddel around in you PATH variable and remove the
compiler paths, from the nix-store.
I guess you have to fix more than just the PATH variable.