I feel like the answer here depends a lot on the context.
One context is “using software”. For example, you want to use a tool, written in Python, and the tool has import request
inside and somehow assumes that the request Python package is available. In this cases, the software should packaged the nix-way, using a nix expression, and probably upstreamed to GitHub - NixOS/nixpkgs: Nix Packages collection & NixOS as well. You do want system-wide reproducibility and isolation for system-wide applications. Admittedly, the process of wrapping software which is not already in nixpkgs is annoying (as nixpkgs really wants to be a monorepo of all the things), but hopefully things will become easier in the flakes world.
Another context is “developing software”. If you are a Python developer, you might be working on three different projects, and each project has its own set of dependencies, etc. Here, I am pretty sure that language-specific build tool/package manager is the way to go, if the language has good tooling. I work with rust primarily, and rustup
and cargo
are perfect for me to hack on my or other people’s projects. I don’t see how nix can add anything here. There’s also a fundamental tension here that, when you develop software, you need to handle both variability and reproducibility. For Rust, in Cargo.toml I specify effectively a range of versions of dependencies my project works with, but then I have a separate Cargo.lock which pins dependencies exactly. In this context, it’s important to make sure not only that the single point, Cargo.lock, is valid, but that the whole range works (using testing with deliberately older versions of libraries). The way I see it, nix is a very good tool to handle Cargo.lock
aspect of this workflow, but doesn’t have vocabulary for Cargo.toml
aspect. That said, when I need to use software written in rust (ripgrep
or exa
), I install it using nix, as that falls into the first context.
The specific case of Python is different though – I haven’t been active in Python community for a while, but my understanding is that building & dependencies story doesn’t have a clear cut definitive answer. Virtual environments were added not from the start, and undergone several iterations, pip didn’t have (and still hasn’t?) lockfiles, etc. In this situation, it might be plausible that using nix
as a substitute for the language’s native infra would be beneficial. Though, I’d try to stick to default tooling nonetheless.
Finally, the third context is when you are developing software in a specific language, but it also depends on some system libraries. This is most typically manifested by having apt-get install libfoo libXbar
in project’s readme. For such cases, using nix to manage the extra dependencies is the right answer, because nix allows you to have such global deps isolated per project. When I need to work with such project, I either use direnv and a project-specific shell.nix
, or (if this is one off thing) I use my default shell.nix
which is just a grab bag of common system dependencies.