For system that is so proud of allowing multiple versions of dependencies to coexist, it seemed surprisingly difficult to actually find and use specific versions of a dependency in, you know, applications.
There are at least two answers.
The most popular one, which I disagree with, is to find an older nixpkgs hash and just use that. This works of you don't care about having all dependencies downgraded or security updates of anything in nix. There is even this tool to help you do it: https://lazamar.co.uk/nix-versions/
However, I dislike that method a lot.
The correct method is via overrideAttrs possible with an overlay. This post explains that pretty well: https://discourse.nixos.org/t/how-to-override-package-versio...
So AIUI the second, recommended method, it's overwriting the source location for that particular package, and then letting nix build that with an otherwise unchanged nix recipe/script/thing? Am I responsible (at the point of use) to make any other changes required to make that version build successfully? Do dependents of that package get rebuilt too?
IE python = super.python...
It could also completely override the entire definition of something if you wanted.
IE python = super.stdenv.mkDerivation { ... };
But you can also add new names, and not modify the existing definitions.
IE python-for-me = super.python.overrideAttrs (...);
In the last case you can reference your modified copy while other things in nixpkgs can use the one built upstream.
(Tool support would be nice, but nixpkgs has no standard way to specify packages across languages and runtimes, so not possible right now.)
There are alternatives in other projects that bridge build system and package management while directly addressing versioning of dependencies e.g. Spack, Conda, ... The first in particular does exactly what I want, so I suppose I should just be happy with it. Still would be nice for Nix to compete in the same space, esp. with the really slick translation from local to containerized environment displayed in here
There's benefits, too, though - you know the C compilers, build flags, versions and libraries of everything that goes into making a Python library run. It's easy to change any of it, integrate with your own custom libraries or other languages and test the whole contraption in its entirety.
That's just not that common in other build tools.
This is answered in a sibling comment here: https://news.ycombinator.com/item?id=28243409
The tool does exactly that.
However:
> find and use specific versions of a dependency in, you know, applications.
This is not what nixpkgs currently provides, or aims to provide.
nixpkgs is like the Debian, Gentoo, etc. package repos: In most cases, it provides one version of a package. If you want to use an older one, you have to use older version of all dependent packages, as they were at that time; this in practice means using software with security vulnerabilities that are only fixed in current nixpkgs.
nixpkgs is not currently designed to let you compose your application from libraries taken from different nixpkgs versions. It is insecure (see above) and slow (evaluating more than a few different nixpkgs versions takes a long time).
The current correct way is to use a current nixpkgs version as a base, and then use an override to update the sources of the packages you want to have a different version of explicitly. Or use a tool that does this automatically for your programming ecosystem based on that one's package manager, like `stack2nix` for Haskell, `yarn2nix` for Javascript, and so on. Nixpkgs makes such overrides easy.
It's very common to pin dependencies to specific versions. And upgrade them carefully, also to specific versions.
If I use a dependency at version X.Y, a vulnerability is discovered in it, and fixed in version X.Y.Z, I don't want to "start from base nixpkgs". I want to upgrade that specific dependency to a very specific version.
From how you described it, it's either not possible or extremely cumbersome to do with nix. Which begs the question: what's the point?
Yes, this is what I am arguing you should do, by overriding its source to that version (which I described as being easy).
You should not try to pull in a version of nixpkgs (whole OS) that has X.Y.Z, and combine it with another version of nixpkgs (whole OS) that has some other library's version A.B.C.
In the same way as you don't try to combine 3 different versions of Debian to get specific versions of 3 different Python libraries.
Concrete example:
Current nixpkgs has libpng-1.3 and libjpeg-4.6. For building your app you desire libpng-1.2.3 and libjpeg-4.5.6.
To get them, you use the current nixpkgs, and override `libpng` and `libjpeg` to the versions you want using e.g. `libpng.override { src = fetchGit ... }`. You do NOT do `libpng = (import <older-nixpkgs-version-1> {})` and `libjpeg = (import <older-nixpkgs-version-2> {})`. Because that way you will also end up with 3 different versions of e.g. `glibc`, which may be incompatible.
I hope this clarifies it!
How to find out what versions of `libpng` are available to be used in `shell.nix` or `shell.flake` with nix tools?