Show HN: Asdf Clone Written in Rust
I think that asdf (https://asdf-vm.com) was a great idea for a project. It helps consolidate installing and running different programming languages into a similar UX. It also is built with a plugin interface that makes it easy to build support for new languages.
However it is so slow. I was just testing `node -v` and it was taking ~900ms. That kind of overhead is completely unusable. My shell prompt uses runtimes inside of it for various things so this effectively makes every command take multiple seconds to complete.
So I rebuilt it in Rust but using the same plugin ecosystem so it should be a drop-in replacement. I also added a couple of features that I wanted from asdf (aliases and fuzzy-matching).
Let me know what you think! Just know that people have only been using this for a few days so if you see any bugs, they're likely not big hairy issues, just overlooked edge-cases and will be fixed soon.
36 comments
[ 2.4 ms ] story [ 13.9 ms ] threadI occasionally hit 2sec until my node version is resolved.
I’m trying to fix them while also supporting their syntax where I can, muscle memory is strong. So I support “plugins install” and “plugins add” If you notice any aliases that you think should exist, please mention them.
Do note that asdf re-uses existing tooling, so trying to combine ruby-build and node version manager and asdf with node and ruby plug-ins is likely to crash and burn (because you end up forcing existing tooling to fight with itself).
Not sure if this was what caused your problems - but my asdf-life got a lot more pleasant when just asdf all the things (that I use).
Does mean that things like vs code needs the right environment / local tool versions and language servers to work with your projects and vs code plug-ins.
EDIT: OK, I see the main trick is to use PATH instead of shims.
> rtx does not use shims and instead updates PATH so that it doesn't have any overhead when simply calling binaries
There is a good reason `asdf-vm` uses shims, and is that it does not have to interplay or worry about other tools that set PATH and tools that need a reference to an executable could be simply set to `~/.asdf/shims`.
Ill take it for a spin, but this choice might have a lot of consequences that are not easy to foresee. A good example is `direnv` which as you mention in the README now requires to be set in `.envrc` and then disable global `rtx` hook I guess.
There are some short term issues but I think I can resolve them in a way that direnv can't since I know exactly what PATH variables I added and didn't add. (Just remove everything with ~/.local/share/rtx prefixes.)
I could also just implement shims like asdf. The performance cost would be negligible (2-3ms of overhead for me running `rtx exec -- node -v`). I hate shims because they break `which node` though.
This, like shims, would require “reshimming” anytime a new binary is added by pip/npm. I really want to avoid that as I see juniors (and sometimes experienced devs) get in a bad state fairly often forgetting (or not understanding) reshimming.
I’m also not sure how you deal with subprocesses calling binaries. If they’re only shell functions they only exist to the shell.
> save the extra exec
rtx is already over 100x faster than asdf so this wouldn’t be necessary really. I could load test and optimize for setups with a ton of plugins and stuff if needed. My reason for avoiding shims is entirely UX related, not performance. Though I do think asdf would not have its problems if it didn’t use shims. This really is just because asdf is in bash and rtx is in rust. (I think rtx is already a lot more LOC though, and not as clean).
But in regards to PATH, I think I can get rtx to not screw up anything else that would be modifying PATH (slightly different algo than what’s currently used right now). I can’t guarantee that nothing will screw up rtx, but I can prevent direnv in particular from doing it.
It may be a game of whack-a-mole testing with different tools that modify PATH to get them to not conflict. That’s fine, it’s such a better solution.
I also think if rtx is loaded after other hooks it shouldn’t get messed with. It’s only if it gets called since potentially those tools could remove rtx paths and there isn’t much I could do about that.
> aliases and fuzzy-matching
What exactly does fuzzy matching mean? The only examples I see in your README are of the nature
How is this different from doing specifying `nodejs@20.0.x` (which I guess asdf doesn't support)?In any case, I hope that, if I try to install a package/plugin and it doesn't exist, it won't just try to install a plugin/package whose name is similar? :nervous_look.jpg:
asdf has a handful of commands that let you do prefix matching like `asdf install nodejs latest:20.0` but they're not supported in `.tool-versions` files. This basically just adds that functionality.
I didn't feel like requiring `latest:` was necessary. I felt it was clear that `nodejs 20.0` means `nodejs 20.0.*`. This is how npm has always worked.
Figured I'd mention it, because I haven't tried tea yet, but I'm an avid asdf and brew user and I've been looking at asdf alternatives.
I also really like that it has really clean install output.
[1]: https://asdf.common-lisp.dev
I'm wondering if the lack of slowness I've seen could be due to my simplistic setup: globally set nodejs version, single nodejs version installed, with other tools like git installed by the distro package manager. I also have golang and some other stuff like rclone installed using asdf, but not much more.
Did you also have a similar experience when initially installing node? Did you notice what triggered it or when did asdf start to become slow?
I like asdf and would like to avoid making it slow on my laptop.
Oh and can I safely assume that your home directory is on an SSD and not on an HDD?
This was with a half dozen plugins and use_legacy_file disabled. Hardly an edge-case. Though I'll note in other tests it was around 200ms (which is the number I put in the README to be fair). @danfritz in this thread said his can take over 2 seconds!
Out of curiosity, what is the difference between `time node -v` and `time ~/.asdf/installs/nodejs/*/bin/node -v`? I've never seen it not add at least 100ms.
I'm on a T480s with i5 CPU and SSD:
From what I can tell by looking at the documentation of `rtx install`,
and of `rtx plugins install`, e.g.: `rtx install nodejs@18` will autoinstall the nodejs pluginI take it there is still no way in rtx to simply install all plugins and runtimes listed in .tool-versions, without listing them one by one on the command line? Put differently, I would expect `rtx install` to install all runtimes specified in .tool-versions, including any plugins that are not installed yet.
Would this be something that you would consider adding? I'm asking because not being able to install all plugins mentioned in .tool-versions automatically has been my biggest gripe with asdf.
That said, I was thinking about this yesterday and that it’s currently painful to have to type out each plugin one by one. I added this ticket for making `plugins install` default to installing all plugins in the local tool-versions: https://github.com/jdxcode/rtx/issues/50
What do you think? Is that discoverable and useful enough? I could go a step further and add a `--plugins` argument to install or something.