Tools I love: mise(-en-place)
65 points by micvbang
65 points by micvbang
I crossed my fingers and hoped that jj was one of the tools supported by mise and, to my delight, it was!
One thing I enjoy about NixOS is being able to ask this question in the shell:
$ jj
The program 'jj' is not in your PATH. It is provided by several packages.
You can make it available in an ephemeral shell by typing one of the following:
nix-shell -p jj
nix-shell -p jujutsu
It does this quickly by searching an SQLite database.
I wonder if you can get mise
to do a similar thing? Is the registry just a TOML file? Maybe it’s extremely easy.
For what it’s worth, Debian and Ubuntu do the same thing (but with apt
suggestions) on systems with the command-not-found
package installed (which is usually the case).
Fun fact: back during the Trusty Tahr days, there was apparently some bug with command-not-found; so any time I would mistype a command, I’d get this huge error output starting with “command-not-found has crashed!”.
Afaik mise doesn’t hook into the shell to suggest packages for missing commands. That would be a cool addition.
Searching is quick enough, though. If you type mise use
with no arguments, it’ll show all packages and quickly filter them as you type.
I jumped to mise after using asdf for years, just before asdf was rebuilt in go, but only coincidentally.
Often, outside of project work where versions are controlled, I just want my global session and random BS scripts to have the latest versions of various languages installed (mise use -g ruby@latest
). asdf’s core didn’t have a way to get the latest of everything in one shot (mise up
), and when it does, there’s no tracking of what installed versions may still be in use. mise makes you choose to trust project-local config files specifying tools and versions, and that trust system gives it the data to refcount installed versions and offer to clean up unused stuff.
I love mise but for my development machine I just reverted back to the standard managers of the different languages that I need: uv
, rustup
, nvm
.
For a tool like jj I’m fine with having Homebrew install a global version.
It’s kinda like Nix, except much less useful
I think “not having to deal with nixlang” is a nice feature (and I say this as someone with flake.nix files for all of her personal projects).
I’d say Homebrew the closest thing out there to “kinda like Nix without having to deal with nixlang/nixpkgs/etc.”, i.e.
% ldd /home/linuxbrew/.linuxbrew/Cellar/python@3.13/3.13.5/bin/python
libpython3.13.so.1.0 => /home/linuxbrew/.linuxbrew/lib/libpython3.13.so.1.0 (0xffff81c22000)
libc.so.6 => /home/linuxbrew/.linuxbrew/lib/ld.so (0xffff82200000)
Except homebrew’s python is not for you. (Independently discovering that problem when virtualenvs across my system broke after I went back to daily driving a Mac sometime in 2022 was what pushed me to pyenv. But I use uv now.)
Does brew behave differently on the Linux version to prevent that kind of problem?
Not sure how it was in 2021-2022, but these days lots of Python versions are available simultaneously. If you brew install python@3.9 python@3.10
you’ll always have 3.9 and 3.10 installs available that don’t conflict with newer versions.
Yeah, the problem isn’t the conflict with the versions. It’s that when they move from (say) 3.9.a
to 3.9.b
, for all the packages that wanted 3.9.a
, 3.9.a
goes away. And homebrew doesn’t know that you had 17 .venv
locations symlinked to (or rpath linked against) 3.9.a
that are now broken.
Which is to say, if you don’t have all your various projects registered as depending on the old minor release (Python notoriously doesn’t use semver) with Homebrew, Homebrew will clean up the old version once everyone who’s registered to use it has been upgraded. And fixing this by writing formulas for all your little projects no longer feels like “not having to deal with nixlang/nixpkgs/etc” because brew formulas feel quite a bit like the etc
in that statement.
I don’t dislike using homebrew for installing things I just want to run. But unless I’m making and installing homebrew formulae for the things I’m working on while I work them, for dev dependencies that come from homebrew, there’s an overly large chance it’ll break me, particularly for python.
mise provides 90% of the benefit at 1% of the effort. I gave up on nix-darwin earlier this year, replaced it with mise for projects, and have no regrets.
These days I’m fairly happy just making a flake.nix with a devShell, but tools like devenv seem to be a good place to start with the benefits of Nix, while providing more batteries for your dev environments.
At work, we have these long wiki pages telling developers how to configure their machine to work on our projects, and all the config is global (install XCode, install these specific homebrew packages, at this specific version, set up your shell like this).
devenv
or just nix flakes + nix-direnv
would make that setup “Install nix, install devenv with nix, now whenever you clone a repo and cd into it, it’ll ask you to direnv allow .
, and then your environment is _just there when you need it and disappears when you don’t”. But the definition of “your environment” when using devenv can be arbitrarily complex, it can include bringing up and taking down services and containers when you enter the dir etc.
Once you have your team using nix, you can start to speed up their build times by caching intermediate results on a local caching server, which when you work on FPGAs like we do, would probably save us a shitload of time.
I was a skeptic of Nix for a long time, but after managing to solve some real problems with it (cross-platform builds become much easier, and can be built in parallel on one machine, which makes our CI runs a lot quicker), it’s now the first tool I reach for when starting a new project; add a flake.nix, add the packages I need, and then not have to worry about what else is installed on my system. It took a while, but I now think it’s been worth it.
Sorry for the rambling reply, but I guess my point is: from the blog post, it looks like mise is a less powerful Nix shell, with nicer UX (which isn’t nothing, I’d love to see a nice interface to managing a flake.nix).
We started out with devenv last year under the same hope, but in practice, it didn’t seem to isolate us enough from nix, and then we had two things to learn instead of just one. We switched to local flakes, and it worked ok, but it always felt like a big hassle to make changes.
I think the goal of taming nix is worthwhile, and I hope something like devenv continues to improve.
But personally, I don’t need the nix features that mise is missing (for now). mise is less powerful, but also orders of magnitude less complicated.
Only in the short term. Long-term, Nix gets you a far larger benefit for significantly less effort.
I gave nix-darwin almost a year. I’m pretty dubious about the ROI tradeoff in nix’s current form.
nix obviously wants to be a universal tool, but imo hasn’t risen to the level of usability necessary yet to break out of its niche.
The future of building software is clearly something like nix, but I’m not sure it’ll be nix itself. I can easily see a new, better-designed, nix-inspired, nixpkgs-compatible tool displacing it.
As a full-time NixOS user for the past decade, I agree — I think something much better than Nix is possible, but doesn’t exist yet, and while the high value of nixpkgs makes it costly to switch to anything incompatible, I hope some project will find a way to thread the needle.
In the meantime, I don’t really expect 99% of people to embrace Nix to the extent that I have.
Something that I can use productively after 5 minutes of reading the docs is infinitely more useful than something so inscrutable that it still hasn’t caught on despite being, objectively, an amazing piece of tech.
I like mise-en-place a lot, having installed it in all my development machines (personal). It makes it easier for me to also use the tool versions others use on projects (for work), especially when I work with tool-trigger-happy kids. (I was on a project that used pnpm, bun and go, all in one repo)
For the two groups saying “Nix does this better” and “not needing to use the Nix language is nice”, you can have both with Flox: https://flox.dev/docs/
Disclaimer: I work there, but I’ve used Nix for years and all of my local development is with Flox at this point because it’s substantially nicer. You’ll also have a way easier time convincing your coworkers to try it than you will with Nix. I still use Nix for machine configuration though.
I’m in this camp.
How does flox compare to devenv? I tried it last year, and felt it still leaked too much nix.
With Flox you don’t need to know Nix at all. Your config file is TOML, and you can install/uninstall/search/etc just like you would with a typical package manager, it’s just that those operations are scoped to an “environment” tied to a particular directory (e.g. your repo). Your home directory is treated specially, which allows you to use Flox as a Homebrew/apt/etc replacement.
There are some minimal escape hatches at the moment that allow you to use Nix to fill in gaps where Flox doesn’t provide something. For example, Nixpkgs doesn’t provide nightly Rust toolchains, so if you need that you can install the output of a flake that provides it. It sounds more complicated than it is, so you can see the one line of config required to do that here: https://flox.dev/docs/cookbook/languages/rust/#how-do-i-use-nightly-compilers
We use mise
at work and … I guess it’s better than a pile of invoke scripts, barely. But people who care about this stuff more than me like it.
What do you not like about it? Or do you have another tool you prefer? Having never used this but recently dabbling in ASDF and some others for NxM (languages cross versions) support, whether you feel qualified or not, I’m still curious about your lived experience.
What I see happen a lot with “script wrappers” in general is people outright not forwarding arguments along properly. And since we tend to combine scripts, we often end up with wrapping scripts in other wrapped scripts, so you need to do argument forwarding twice.
Dependency graphs in tasks are also something of a mixed bag for me. Because of a deep dependency graph in our scripts we’ve ended up having per-script caching, and there, again, I sometimes hit issues with trying to cache bust and being unable to.
I don’t have great answers for things, but I’ve found as I’ve got older that it’s nice to configure projects so that, say, pytest
without any special wrapping “just works”. And lots of wrapper tools end up with people not doing that work.
The more you can do with “just” env args the more straightforward things are.
I’m not commenting on the tool install stuff in general though. I’ve not had issues.
I largely agree with this analysis and I use mise just for dependency management, not tasks. It is fairly pragmatic and useful for that. Where there’s pain it’s just the fact that random tools still exist only as e.g. Cabal (Haskell) packages and Mise doesn’t (yet) support them (and possibly never should. This is related to libraries vs executables).
mise doesn’t prevent you from invoking pytest straight. I agree its task dep graph is not that strong.
On the flip side, I find that specifying task one-liners helps me since I don’t have to remember all the command-line options I typically use. It’s especially good when I haven’t been in a repo in a while.
mise doesn’t prevent you from invoking pytest straight
Yeah it’s more that other people I’m working with will stick a bunch of command lines into the pytest
wrapper script that they put into mise. This is doubly painful when, say, pytest
already includes things like addopts
.
I’m not a maximalist/minimalist on this and I totally get where you’re coming from. I just think that generally tooling has improved enough to where we can reduce the amount of stuff in wrapper scripts. And the more we reduce wrapper script specifics, the more likely some other tool will work out of the box instead of requiring its own futzing.
I don’t mind that much in general and I’m usually able to make simplifications to wrapper scripts as I need to without any real pushback.
We’ve been using asdf at work in some projects for a while. We have scripts that invoke tools explicitly with asdf to make sure they get the right version.
A while ago I installed mise because it was Tuist’s recommended installation method. The mise setup in my shell environment made it spit out warnings every time I entered my work project directory. It saw asdf’s .tool-versions
file, assumed the file was meant for itself and was offended because it couldn’t find the tools the file specified.
How does this make any kind of sense? Why are mise and asdf using the same file?
Obviously I uninstalled mise immediately. I can’t replace asdf and I’m not interested in pacifying mise by adding extra Ruby installations on my computer.
It’s designed to replace asdf so it’s compatible with asdf configuration
I can see the idea but it leads into situations like this: either you replace asdf completely in everything you and your teammates touch, or you maintain double installations just to avoid getting error messages about things that have nothing to do with mise.
https://mise.jdx.dev/configuration/settings.html#override_tool_versions_filenames
Seems like this would solve your issue.
Sure. It’ll just make mise not work on any project that uses the default name, effectively locking me into asdf on any project that uses a .tool-versions
setup.
I guess this is not a major problem, or they’d have addressed it. It just feels like such a weird decision.
The two can co-exist; you just can’t use an Asdf config for Mise. Mise will ignore .tool-versions
by default when a mise.toml
config exists.
Mise was originally a drop-in replacement for Asdf. It used Asdf plugins; everything worked. Since then the two projects have gently diverged in increasingly incompatible directions. Asdf added new commands that conflicted with existing Mise commands, so not all Asdf plugins work with Mise anymore.
https://mise.jdx.dev/faq.html#how-compatible-is-mise-with-asdf
I use it to manage versions of ruby, node, etc. because I like the idea of having one tool for that, like asdf. Also started using the direnv capability in a project. mise is not life changing, and I would go use another tool if/when it comes along, but for now, it’s nice. Yeah I know could just use rbenv, nvm, or addf; direnv, etc. I tried all those and had no issues with them, just wanted to give this promising “but I can do it all” tool a shot, and it’s working fine.
Mise is notably faster than a lot of the dedicated language version management tools, too. On my work machine (with awful endpoint security that slows all disk access to a crawl) it shaves a couple seconds off Ruby boot time over rbenv.