rv, a new kind of Ruby management tool
81 points by steveklabnik
81 points by steveklabnik
Seems to be really early stages but I like the idea. There’s a good opportunity here to unify and simplify some things.
For example:
.ruby-version
, the Gemfile
already has a section for thatmise, rbenv, etc
), gems with another (gem
) and handle project dependency with yet another (bundler
)?In my mind the two best tools I’ve used from a dev experience perspective are go
and bun
. With Go everything is done via go
, everything: toolchain, dependencies, binaries, formatting, documentation, testing, even monorepo handling. You only need a single binary to setup the whole project and get to work.
With bun
you have a lot of convention over configuration and ties into the dev environment really well where most things are automatic. It also follows in go
’s footsteps by providing every day tools out of the box.
Rust is alright but it has separate rustup/cargo
tools which, from a DX standpoint, I don’t really care to separate. I haven’t used uv
.
In general I believe this unification is the direction every language is going to. Go is still the golden standard to me.
Why do we need to manage Ruby with one tool (mise, rbenv, etc), gems with another (gem) and handle project dependency with yet another (bundler)?
Why not? They’re completely different use cases and implementations. It’s like trying to merge runtime manager, deps manager, linter and LSP into one tool, because they work on the same language. You could, but… what’s the benefit? Why not separate tools with a small dispatcher script instead if a single entry point is preferred?
There’s really no need for .ruby-version, the Gemfile already has a section for that
As someone that does a lot of testing and debugging with ruby prereleases, the setting the ruby version in the Gemfile is a huge pain in the ass.
.ruby-version
is much more friendly to my workflow given most version manager will understand it as a default and allow me to chose another Ruby anyway with chruby <version>
(or similar tool).
I know my use case is a bit niche, but still. There are a lot of annoying behavior in bundler that result of it assuming a given Gemfile is only ever bundled with a specific Ruby version, while it’s often not true, particularly for libraries.
How should a conflict be resolved though. For an app in prod: if .ruby-version disagrees with Gemfile.lock it seems like you want the Gemfile.lock version since you can have a stronger guarantee that’s what was actually used as a runtime.
But if you do that, it negates your primary use case of wanting to be able to easily manage using .ruby-version.
If you default to .ruby-version in a codebase where not everyone uses a toolchain that supports it, it might end up unexpectedly using a different version of Ruby than the developer intended.
How should a conflict be resolved though.
By an early error?
At Shopify I built the tooling so that in Gemfile we’d set a minimum version (e.g. ruby ">= 3.3"
), and my in-house Docker image builder (think cloud native buildpacks, but a bit different) would look for a ruby version in .ruby-version
, but you could override the version to be used with a build argument.
This allowed to build multiple docker images for the same app, each with different Ruby version, ideal for ruby-head CI or even deploy ruby-head to a small % of production.
If the ruby version was defined in the Gemfile, the above would be impossible as bundler would immediately error out if you tried to build with a different Ruby.
It worked well, except that the bundler dependency resolution somewhat assume whatever ruby version you are bundling with is the only one that will ever be used, which is a problem with precompiled gems as they have a maximum Ruby version support. But Samuel Giddins improved that quite a bit lately, so it’s less of a problem now.
By an early error?
That’s not a bad default to start with. The error mesage to the user could suggest:
.ruby-version
into the Gemfile and running bundle update --ruby
orGemfile.lock
orruby_version_source = "Gemfile.lock"
I’d like source Gem.default_sources.first
and ruby file: '.ruby-version'
to be default unless specified otherwise.
Now that "https://rubygems.org/"
and .ruby-version
are very well established, it seems nice to drop the boilerplate in favor of the modern conventions.
It’s not that uncommon a use case. When my company upgrades Ruby versions, we usually have a few weeks where our CI is running the specs with both the old version and new version. There are workarounds; I guess we could have a Gemfile for each version during that period and remind everyone to update both of them? That’s what we have to do for Rails upgrades.
You can have version operators in the ruby
directive:
ruby '~> 3.4.4' # will accept ruby 3.4.5 as well
Having two gemfiles isn’t a satisfactory solution, because you want to ensure every single dependency is of the same version.
The ideal solution would be for bundler to be able to generate a Gemfile.lock that satisfy multiple Ruby version.
It mostly works today, but often run into issues with precompiled native gems.
There’s really no need for .ruby-version, the Gemfile already has a section for that
And Gemfile supports using ruby file: ".ruby-version"
to use the .ruby-version
file as the source of truth. There is a benefit in keeping that information in a separate file that only contains the ruby version. It is easy to get that information in dumb scripts, ej. shell. For example, when I used chruby I used chruby $(<.ruby-verison)
as a way to set the ruby version in a ‘version agnostic’ way.
Note that Gemfile and Gemfile.lock files are not ‘structured’ data ej. json/toml. If Gemfile.lock was JSON for example, one could extract the ruby-version information reliably from different programming languages (jq in shell, but pretty much any language has a JSON parser)
Rust is alright but it has separate rustup/cargo tools which, from a DX standpoint,
Users of a rust programs don’t need rustup/cargo installed to use it.
Here’s the link to their repo: https://github.com/spinel-coop/rv
(In case anyone else had trouble finding it)
I haven’t had a chance to dig in yet, but this week has me fighting with Ruby build tooling, so I am definitely curious.
Uv really solves well a number of problems in Python… that don’t exist in Ruby. I’m not convinced that creating a Ruby version is such a good idea. I use uv extensively, and have never wished for a Ruby version. Another tool to abstract existing tools that work really well?
Neat. The .ruby-version file is mentioned as supported. I’m curious if Ruby version in the Gemfile.lock will be in the future as well and if so: how would a conflict be resolved?
love this new trend of dynamic languages writing their tooling in fast compiled languages. I used to be a fan of “write language tools in the target language” but the speed gain makes a huge difference to everyone’s workflow.
there’s also a chicken and egg situation if you’re trying to install a ruby interpreter for your ruby based tool with your ruby based tool that needs an interpreter
mostly I’ve seen a “system ruby” installed by the os package manager and used to run things that need ruby installed, and a “dev ruby” managed by a user space version manager and used for development. so you could bootstrap via the system ruby.
that breaks down the moment you think about the people stuck on windows. Not that it’s their or languages’ fault, but it just is.
that breaks down the moment you think about the people stuck on windows
I seem to remember macOS is phasing ruby out of being installed by default too. Which would put it in a similar place to windows, default-ruby-wise.
I think they still include ruby in /usr/bin
, it’s just piss old, like from 2022.
you can get a “system ruby” for windows in the form of a binary installer from the language website. I’m not sure how the version manager would work, but bootstrapping should never be an issue because the base ruby interpreter does not depend on ruby
i guess the real news here is that the right people seem to be fixing a big problem a bunch of other rubyists haven’t taken the time to address
What big problem?
Not saying the current tooling is perfect, as I have my own laundry list of thing I’d love to see improved, but it’s all pretty niche, and I’m generally content with what we have.
If rv
end up better for my use-case I’ll happily switch to it.
But I don’t think Ruby has any reason to be ashamed of bundler, nor rbenv - ruby-build / chruby - ruby-install.
I honestly don’t see a big problem here.
What big problem?
I think the current tooling to great at project-level dependency management, but personally I’ve struggled to find the right pattern for managing gems that I use across all my projects and are more utility or dev tool than a project dependency. While not really a problem, I do think better shareability for little utilities programs would be cool for Ruby. Maybe others have found satisfying patterns for mixing system gems and project gems, but I have not.
for managing gems that I use across all my projects and are more utility or dev tool than a project dependency.
Right, I’ve also wished for that sometimes. Typically an issue in larger teams when some devs wish for a specific debug gem (e.g. pry, lsp stuff etc).
I don’t see anything in their roadmap addressing that though.
It’s been a while since I’ve used Ruby, so I’m not gonna insist there’s a big problem, but note that this is coming from the bundler/rbenv folks, so at least they think it’s a big enough problem to build this.
I will also say that so much of my time mentoring and getting folks introduced to Ruby has been burned on the finicky incantations of getting it installed on a machine.
rbenv
and ruby-build
have helped a ton in that regard, and I love them for that. And containerization also created a great path for introduction & trial. Having rv
as another option & approach, with speed & quickly-available binaries, helps introduce new folks to the language and makes it much faster to get to the joy of the language