I have a perfectly good package manager on my system. But many of the programming languages I work with graft package management capability onto their buildsystems, and the result is a mess. I’d much rather they be able to build site packages for other package managers, but this battle appears to have been lost long ago.

For my own reference (and hopefully that of others as well), here are some hammers for making these language packagers integrate better.

Python

I’m going from what I expect is most familiar to people to least, which means Python gets to go first. There are two tools I want to talk about here: pip, which is almost always used, and virtualenv, which can be used in conjunction with pip.

virtualenv

virtualenv helpfully describes itself as a tool to “create virtual Python instances”. What they mean by this is that it allows an installation environment segregated from the global installation environment. It’s actually quite nice for development.

virtualenv will install its own pip which knows to install Python packages into the current virtualenv. By default this environment is essentially unpopulated, which means that everything will be installed from PyPI. This is a great behavior if you don’t trust your distro to provide stable packages, and a terrible behavior if you do. Fortunately, this is togglable: the flag --system-site-packages allows passthrough from the virtualenv to the system installed python packages. (Or, if it’s easier to think about, the virtualenv is pre-populated with the Python packages installed on your system already.)

pip

Run pip install as root and you’ll get packages (and their dependencies) installed from PyPI onto your system. When working with a requirements.txt, this is fine - just install each dependency from the system package manager and then install the package with pip. If you don’t have such a file, or if some of those dependencies themselves aren’t packaged, then it’s more of a problem. There don’t seem to be any flags for dry-run or pretend or anything like that.

The workaround I usually use is to set up a virtualenv with system passthrough, try to install the package with pip, and see what it pulls in for dependencies. (pip list to show the installed packages is also useful here.) Then delete the virtualenv, install from the system package manager, and only then call pip globally.

RPM note

Because of virtualenv, the Python packaging is the only one I will discuss here that it is not insane to use on Fedora/CentOS/RHEL. The problem that you will encounter with any of the others is that these three distros do not believe in the /usr/local hierarchy, which means that not only will yum/dnf be willing to uninstall packages that the language-specific manager installed, but the language-specific manager will be willing to uninstall packages installed from yum/dnf. You can imagine how well this goes.

Ruby

Ruby package management is done through gem. If the “rubygems-integration” package is present (Debian), then gem and the system package management seem to play nicely together - gem list --local will show things installed by both gem and APT, as you’d expect. I didn’t check what happens if you don’t have that installed.

At first glance, gem dependency seem to do exactly what I’d want here, which is to say, it prints the dependencies of a gem. Weirdly enough, though, it only does this for installed gems, which makes me wonder what it’s intended for.

Fortunately, there’s a dry-run verb: --explain. Per the documentation, passing --conservative and --minimal-deps (what is the difference between these? The documentation sure doesn’t say) will cause it to do… the thing I’d’ve expected to be default… and not touch packages that are acceptable already. However, my experience suggests a caveat: when working with both --explain and one of the “don’t touch my stuff that’s good enough” flags, --explain sort of just ignores the other flag. So you get the simulated result as if it were going to upgrade some things that are already acceptable, or, weirdly enough, install some things that are already present. I really don’t know what’s going on here.

Haskell

Another language, a package manager that doesn’t have an uninstall verb. (gem doesn’t either, I just find it more painful with cabal because I use ruby more.) What it does, thankfully, have is a simulate verb: --dry-run.

Fortunately, Haskell projects are the simplest of any so far here: requirements need to be specified as part of the projectname.cabal file. So calling out to the system package manager isn’t too bad here. And root installations of packages will end up in the system package cache as well, so dependencies can also be handled this way if desired.

A related tool that bears mentioning here is ghc-pkg. To see the system packages and their versions, one doesn’t call cabal, but rather ghc-pkg list. And to check the integrity of the system package set, ghc-pkg check. This latter is actually how one uninstalls pacakges: remove the related files, and then use ghc-pkg check to make sure you got everything, followed by ghc-pkg unregister.

Rust

Rust doesn’t have package management capability yet. It will soon though. That’s not to say it doesn’t do dependency management - it does - but rust programs are currently compiled as an entire unit. This means that currently there’s nothing to package. Future expansion needed.

Final thoughts

I’m not really happy about any of these, but I’m least happy with what ruby is doing. I feel like I had to fight the tooling to get it to do what I wanted here, and (though all of them fail in this regard), there was of course no man page for anything.