Hi everyone,
Since I have a good amount of Clojure experience, and enough Guix experience to be dangerous, I thought I’d take a swing at responding broadly to some of the points raised here.
In short: instead of dealing with multiple language-specific tools to set up dev and build environments, using Guix means you only need one.
At my work, we have a bunch of projects which are written in Clojure; with IaC definitions written in Python; which get deployed to AWS with CDK. In order for this to work, you need:
- NVM, to install the right version of Node (CDK is implemented in NodeJS, and runs on Node, even if your IaC isn’t JS).
- NPM to install the CDK CLI and its dependencies.
- pyenv, to install the right version of Python.
- Poetry, to install Python dependencies for the project IaC.
- OpenJDK.
- Leiningen, to install Clojure & Java dependencies, build the code, etc.
- Homebrew, to install the language tooling, so it can install the
Basically, every language’s tooling is solving extremely similar problems, but only within the narrow scope of that one language. Now, I understand why this is so, and the tools can be nice within the scope they’re applicable, but the complexity — particularly when these systems combine & overlap — leaves much to be desired.
For example, Poetry clobbers a bunch of the environment when you activate its venv, so you learn the hard way that to get a working dev environment, the correct order is Poetry, then NVM, then aws sso login
, because stuff breaks in inconsistent and non-obvious ways if you don’t.
For another example, the versions of the Node CDK CLI and the Python CDK SDK need to match, otherwise the SDK creates a version of the output the CLI doesn’t understand how to process.
Guix solves these problems by, essentially, giving you a system-wide virtual environment. You can write a manifest for a project saying that it needs OpenJDK 11, and Node 18, and Clojure 1.11.1, and left-pad 5.4.99, and then guix shell -m manifest.scm
and you get a subshell where those are installed. And if another project needs OpenJDK 17 and Node 21, you can handle that the same way, and the concerns of the other project just don’t apply, because nothing is installed “on the system” like it is with other distros.
Just to connect this up to a mechanism you’re probably already familiar with: When Leiningen downloads a project’s dependencies, they all land in ~/.m2/repository
. This can contain any mix of artifacts, different versions of the same library, etc. And this isn’t a problem, because Leiningen knows which JARs a project needs and constructs a classpath which points at them. Guix is the same idea, applied to the whole system: everything goes into /gnu/store
, and guix shell
changes the environment so (I’m grossly simplifying here, but this is the essence) $PATH
points to the right locations, such that java
runs the desired version within a given context.
There are, of course, other tools addressed at this same problem space; asdf seems to be a popular one. I haven’t used it, so I can’t compare it; I’ll just note that it’s yet another third-party addon, and it still relies on OS-level packages in order to work, so it can’t be a single-system solution in the way Guix (when run as a complete distro) is.
While it’s certainly possible to use language-specific tooling, it’s disharmonious with the way things are supposed to work in Guix, and forfeits the most compelling features. For Guix to manage projects in the way I outlined, it needs to install all required tooling, libraries, etc. To do that, it needs Guix packages.
I can’t say I’m always satisfied with the Guix journey, but the destination is pretty nice.