Seriously the only sane way language package managers should work is by hooking into the system package management, maybe with an option to install in the user directory.
Seriously the only sane way language package managers should work is by hooking into the system package management, maybe with an option to install in the user directory.
What are you supposed to do if you're developing an application and you want to use the latest version of requests for Python but some system utility your OS relies upon is using the distro-packaged version of requests, which is older? Your distro package manager doesn't give you the means to install dependencies on an application-by-application basis. Either everything uses it or nothing uses it. So you get virtualenvs. Back in the old C world, you would have just built and installed some .so files into a project-specific deps directory and set LD_LIBRARY_PATH when running your application for testing. virtualenv is doing exactly the same thing but automating it. And, of course, there is now conan for native libraries if you're still doing C development.
Heck, rpm won't even let you install into your own directories if you're lucky enough to get a relocatable package because you have to be root to acquire the global rpm db lock. What distro package manager lets you install temporary dependencies on an application-by-application basis? Possibly Guix?
> causes python devs to often be way too specific in version specs
I don't think anything could fix that, because it is totally up to library developers to decide whether they want to make breaking change or not. So being able to use different exact version of dependencies for different projects are important.
BTW, I find it annoying that many distro package manager doesn't make it easy for user to install package for themselves without root. Even Nix requires you to create /nix IIRC.
They then say they need virtualenvs to solve that problem.
The funny thing is the same people who do this then turn around and complain about some instability because some c library changed their API after 10 years.
I've successfully used cargo-ebuild in the past to create ebuilds automatically, it's a breeze. I'd be surprised if similar tools didn't exist for deb/rpm based distros.
I'm always surprised how many problems people have with python on other distributions. On Gentoo I can install multiple versions at the same time, install libraries for any of those or just a specific python version of my choosing (including pypy), set a default python implementation (pypy) and of course I can pick the version of the libraries I need, too. It's not as flexible as nix, but quite close. Not a python dev though, so YMMV.
Let's suppose that some language decides to use the system package manager to install packages. They add support for RPM. Then for Apt. Then for Portage. Then for Pacman. Then for Zypper. If they're not too exhausted at this point and have any spare time between trying to keep things working on every flavor of Linux around, they decide they want people to be able to use their language on Macs. So they add support for Homebrew. Except that some Mac users don't like Homebrew and want to use MacPorts. Some others use Fink. So the intrepid language devs add support for those two as well. Now our poor maintainers are supporting eight system package managers that all have their own quirks and oddities that makes keeping all this working a massive nightmare. And all of this work is not done just once, it has to be repeated for every single package that someone creates for the language.
Oh wait, but users still can't even install language packages on Windows! You know — the most popular OS of all. Oops.
Perhaps this sheds light on why no programming language does it this way. Languages have to allow package installation on not only your personal favorite distro of Linux, but also on vastly more popular OSes — Windows and MacOS — that don't even have standard package installers that they can use. The only scalable way to do this is to allow installation of the language and its internal package manager on each platform and then use a consistent internal system that is abstracted from the platform for installing language packages. Which, not coincidentally, is exactly what every programming language does. Python is a little odd in that it doesn't have a single blessed package manager, and that causes a lot of problems, but using the system package manager is not the solution.
However this article is about making pip to better interact with the system package manager on Linux. To me this seems like a bandaid solution instead of generating a rpm/deb file and installing this. You essentially get protection against overwriting other package files, but also protection against the package manager overwriting pip installed files for free. Instead we put a hack in place to add another db (a file) to check for both the system package manager and pip.
Clearly I'm missing something but why is nobody proposing this solution at least for this context?
The thing is simply if I need to install system packages I want them to show up in the system tools. The problem with pip and co doing their own thing is it never shows up in the system tools. That's the same on windows, I don't see it those in the software manager. As a user/admin that means I now need to keep track of one more tool to see what is installed on my system.
I do concede that Linux package managers should make it easy to install as users as well.
Nix markets itself as a "purely functional package manager", but after using it for years I dislike this characterisation. I think of Nix as a build tool, like Make, but scalable & composable enough to "build" an entire OS (or fleet of cloud services, if you use NixOps).
In this context, we can build Python interpreters with our desired packages 'baked in' to their site-packages dir:
rec {
nixpkgs = import <nixpkgs> {};
my-scientific-python = nixpkgs.python3.withPackages (pyPkgs: [
pyPkgs.matplotlib pyPkgs.numpy
]);
my-game-dev-python = nixpkgs.python3.withPackages (pyPkgs: [ pyPkgs.pygame ]);
}
These are similar to venvs, but they're immutable and created atomically.We can build our own bin/ directories with arbitrary contents, e.g.
nixpkgs.buildEnv {
name = "my-binaries";
paths = [
nixpkgs.gcc
nixpkgs.valgrind
nixpkgs.shellcheck
my-scientific-python
];
}
This results in a folder containing a bin/gcc, bin/valgrind, bin/shellcheck, bin/python3, etc. where that python3 binary has our desired packages available.If we want to make an "application", e.g. to "install" on our system, we don't need to expose these underlying details; e.g. there's no need for any 'global consistency'. For example, we can write scripts which "bake in" references to their dependencies, e.g.:
writeScript "hello" ''
#!${my-game-dev-python}/bin/python3
import pygame # This is guaranteed to be available, thanks to our shebang
# We can splice in references to any file/directory we like
import json
with open("${./my-config.json}", "r") as f:
config = json.loads(f.read())
import subprocess
subprocess.run(["${nixpkgs.firefox}/bin/firefox", "http://news.ycombinator.com"])
''
When we build scripts like this, those ${} splices will be replaced by the paths to real files/directories built by other Nix builders. If we add the above script somewhere on our $PATH, e.g. called "hello", then we can run it via the command "hello", and it will start up a particular Python interpreter with Pygame installed, then launch Firefox on Hacker News; yet Python and Firefox themselves will not be "installed" (i.e. they won't be on our PATH). Other scripts and tools are free to use their own Python interpreters if they like.We can do the same with any language. The 'writeScript' builder used above is pretty trivial (writes the given string to a file with the given name, and sets its executable bit); we can use more complicated builders, to do any compiling, testing, etc. we like. The 'runCommand' builder is a general-purpose way to run Bash code, e.g.
nixpkgs.runCommand "my-pip-env"
{
buildInputs = [ gcc gnutar gzip python3Packages.pip ];
}
''
mkdir "$out"
pip install -t "$out" my-favourite-package
wget "https://example.com/source.tar.gz"
tar xf source.tar.gz
cp source/my-extracted-file.txt "$out/sacred.txt"
mkdir -p "$out/foo/bin"
gcc -o "$out/foo/bin/runFoo" source/foo.c
# etc.
''
Nix actually has built-in support for downloading tarballs, checking out git repos, etc. so the above is just illustrative (we should also split up these separate-looking steps into separate builders, for better caching)Despite this, it can still be used in small, gradual ways. Some nice starting points:
nix-shell as a #! interpreter, e.g.
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p gnuplot -p "python3.withPackages (p: [ p.numpy p.scipy ])"
import numpy as np
...
This is a standalone .py script (not a .nix file). When executed it will run nix-shell, which will set up a temporary environment whose PATH contains gnuplot and a python3 executable with numpy and scipy in its site-packages (the '-p' arguments). It will then run the script using the python3 command (the '-i' argument), as if the #! line had been '#!/path/to/that/particular/python3' all along. These dependencies will come from Nixpkgs by default, which is one of the largest and most up-to-date package repos in the world (see top-right of https://repology.org/repositories/graphs )Another nice "baby step" is again to use nix-shell, but this time we write a .nix file defining all of the development tools we want to use, e.g.
with import <nixpkgs> {};
mkShell {
packages = [ gcc gnumake valgrind shellcheck emacs ];
}
If we call this file 'shell.nix', then running 'nix-shell' within that directory will start a shell with all those packages available in its PATH. This is useful when we don't want to install a bunch of tools system-wide (or, as we get more specific and explicit, it lets us use different versions of tools for each project).