Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby Feature: Which ruby version manager would you prefer? #757

Open
samruddhikhandale opened this issue Nov 16, 2023 · 23 comments
Open

Ruby Feature: Which ruby version manager would you prefer? #757

samruddhikhandale opened this issue Nov 16, 2023 · 23 comments
Labels

Comments

@samruddhikhandale
Copy link
Member

Hello! 👋

We are looking forward to get some feedback for refactoring the Ruby Feature.

Which Ruby version manager would you like the Feature to install by default? You can use the respective emojis for voting your preference!

  1. None - 😕
  2. rbenv - 🎉
  3. rvm - 🚀
  4. Any other (feel free to drop a comment)

For the ruby Feature, we have heard issues with conflicting ruby managers (rbenv and rvm) which can be problematic for complex projects. We have a community member who is willing to contribute a PR with the necessary changes ✨ See #603 (comment)

(Draft) Proposal

  • Create a new Feature option which will define the ruby version manager to be used for installing ruby
    • Let’s say we call it rubyVersionManager
    • It will be string with enum: ["none", "rbenv", "rvm"])
    • Default value: "TBD: Poll result"
  • Based on the value provided by rubyVersionManager, the Feature will use that version manager to install ruby
  • In case of none, we will first attempt to install it with apt.
    • If it fails, then install from source.

Looking forward to everybody's feedback, thank you!

@nevans
Copy link

nevans commented Nov 17, 2023

In the case of installing with apt, do we just accept whatever version comes from the OS? Or do we require that it matches the version that was specified for the container? Although I do like to use Debian/Ubuntu packages when I can, they will be constrained as to which version can be installed. For consistency, I think it's probably better to always "install from source" in the normal "none" case and simply ignore the OS distribution's packages (and maybe add a fourth "apt" option?).

@ThomasOwens
Copy link

ThomasOwens commented Nov 18, 2023

@nevans

I was trying to not scope-creep this too much, but I think I see what you're getting at.

From what I can tell, the default none case does rely on whatever comes from the container. That isn't the best solution. It looks like Debian comes with Ruby and the common-utils feature runs an upgrade of all OS packages. If I'm understanding the scripts correctly, none always gets you the latest Ruby release (in the Debian repos, anyway). Not necessarily what people want. Plus, if Debian stopped distributing Ruby for some reason, we'd be in a tough spot.

I believe the solution is this:

  1. Create a new Feature option rubyVersionManager to define the Ruby version manager. It will be a string with enum: ["none", "rbenv", "rvm"]. The default value will be the poll results here.
  2. Create a new Feature option rubyVersion to define the version of Ruby to install. It will be a string with some proposals for supported Ruby versions. This should be freeform, though, and parsed out to download any version of Ruby.
  3. Based on the values of rubyVersionManager and rubyVersion, install the specified Ruby version. Install from source for none or using the version manager otherwise. When installing from source, set the path variables properly.

Would need to deal with the Ruby version from source. The URL for the source tarball for Ruby X.Y.Z would be https://cache.ruby-lang.org/pub/ruby/X.Y/ruby-X.Y.Z.tar.gz or https://github.com/ruby/ruby/archive/refs/tags/vX_Y_Z.tar.gz. That's not so bad. The preview versions, on the other hand, are of the format https://cache.ruby-lang.org/pub/ruby/X.Y/ruby-X.Y.Z-previewN.tar.gz or https://github.com/ruby/ruby/archive/refs/tags/v3_3_0_previewN.tar.gz. You need to not only know the major, minor, and patch version, but also the fact that it is a preview and which preview build it is. I'd have to think this through some more. I'd also need to understand how version managers specify preview builds. Although perhaps we don't need to worry about preview versions at all - anyone using one of them in a dev container can create custom onCreate or postCreate scripts to handle this specific case.

This does bring up an interesting question, though. Perhaps I was wrong in that no version manager should be installed. If you want true isolation between whatever Ruby comes with the OS and your app's Ruby version, the best way to do that is a version manager. I should revisit good practices for containerization of Ruby applications - perhaps it is insufficient to be containerized and use a system Ruby.

@samruddhikhandale
Copy link
Member Author

In the case of installing with apt, do we require that it matches the version that was specified for the container?

Yes, this was the plan ^

As installing from apt is faster than installing from source. We thought it made sense to try adding ruby with apt.

For consistency, I think it's probably better to always "install from source" in the normal "none" case and simply ignore the OS distribution's packages (and maybe add a fourth "apt" option?).
Although I do like to use Debian/Ubuntu packages when I can, they will be constrained as to which version can be installed.

We could also update the version option to accept os-provided, and it installs whichever version is available in apt feeds.

@samruddhikhandale
Copy link
Member Author

Create a new Feature option rubyVersion to define the version of Ruby to install. It will be a string with some proposals for supported Ruby versions. This should be freeform, though, and parsed out to download any version of Ruby.

@nevans we already have version option, why not use that?

@samruddhikhandale
Copy link
Member Author

samruddhikhandale commented Nov 20, 2023

Can we install ruby from the prebuilt binaries available in https://github.com/ruby/ruby-builder/releases/tag/toolcache ? This would make the Ruby Feature way faster to install. Also, we could still keep the install from source fall back if some version is not available in prebuilt binaries.

Update:

@ThomasOwens
Copy link

@samruddhikhandale

Thinking this through, I'm not sure using the OS-provided version will work. I need to test this specifically, but that version will get updated when the common-utils feature updates all the OS packages. If that is indeed the case, a devcontainer user has no control over the OS-provided Ruby version. Plus, if other OS-packages have a dependency or otherwise expect a more recent version of Ruby than what the application needs, other OS-packages could end up in a non-functional state.

I know I was one of the people who questioned even installing a Ruby version manager. And in very lightweight production Ruby container, that may make sense if the only thing the container is doing is running your application. However, in a development environment, you have other tools. And I'm not sure I'd want to be forcing the version of Ruby to something else, especially a much lower version than what is in the OS repositories.

I'm starting to lean toward requiring a Ruby version manager, even if it only points to the OS-installed Ruby version. It would still cause an issue if the OS ever stopped providing Ruby, but that could be a breaking change for a lot of people out there.

I can look at using ruby-builder's toolcache as a possibility. But the standard container is a Debian container. Not sure if the Ubuntu builds will work. Maybe worth a look, though.

Overall, it doesn't change the approach I'm taking. Just a few minor details to work out.

@nevans
Copy link

nevans commented Nov 21, 2023

As installing from apt is faster than installing from source. We thought it made sense to try adding ruby with apt.

It does make sense! ...except that it probably won't match the requested version. 😉

We could also update the version option to accept os-provided, and it installs whichever version is available in apt feeds.

That option makes sense to me. Are there other devcontainer features that go this route? If so, then we can follow their lead. Is os-provided a version name that is used by other devcontainer features? I think I've seen system used more commonly by various version managers, but consistency with other devcontainer features should take priority, IMO.

Thinking this through, I'm not sure using the OS-provided version will work

Honestly, I think that skipping the os-provided version is the simplest approach, at least for the first pass. Maybe iterate to allow that as an option in the future, after the most important case is handled: ensuring the user-specified version is installed.

I'm starting to lean toward requiring a Ruby version manager

Similarly, while it might be nice to provide a simple "no version manager" option, that doesn't need to be in the first updated release. We can iterate towards that. But it's not an option today, so it's not like the feature would be losing any functionality if it's left out of a first PR.

When installing from source, set the path variables properly.

Note that installing from source should be fine without setting any ENV variables, so long as ruby is installed to /usr/local/bin (which is the default for installing from source). In my opinion, it's preferred: rubygems uses smart dynamic defaults for GEM_HOME and GEM_PATH but only when they aren't set. Setting them to empty strings will prevent these defaults from being used, and docker makes it difficult to unset environment variables. Similarly, I think that rbenv should only need its own binaries and shims to be on the PATH. Setting any other env vars will override everything that rbenv does in its shims.

rvm might need GEM_HOME and GEM_PATH (I'm not sure). But you may want to keep them as-is even if it doesn't need them, simply for backward compatibility with the current feature.

The biggest problem with setting these vars is that the dynamic defaults will automatically adjust the directories based on binary compatibility with your current ruby version. When they env vars are set statically, you can wind up with compiled extensions that are incompatible with your currently installed ruby version... which kind of defeats the purpose of a version manager. 😉 ENV variables are great for overrides, but lousy for dynamic defaults.

I can look at using ruby-builder's toolcache as a possibility. But the standard container is a Debian container. Not sure if the Ubuntu builds will work. Maybe worth a look, though.

Even if you were able to get it working with both Ubuntu and Debian, I'd still want to open an issue in that repo to make sure they're okay taking on a new dependency. It may be that they don't want to promise ongoing compatibility and stability for anything other than https://github.com/ruby/setup-ruby.


If you do create a default "install" script, I recommend that 1) we install to /usr/local and 2) we should lean heavily on one of the other existing tools. Some options:

  • copy and adapt the script from the official Docker Hub ruby Dockerfile
  • Install into /usr/local using one of the version managers' build/install tools without the rest of the version manager. If no version manager is being installed, the build tool could be deleted/uninstalled after it's used.

And if the standard defaults don't work for some reason, rather than set the standard ENV vars, we should consider doing what the linux distros do, and add our own 'operating_system.rb' file to rubygems. See, for example:

@samruddhikhandale
Copy link
Member Author

Thank you @ThomasOwens @nevans for your amazing thoughts, appreciate it! ✨

Is os-provided a version name that is used by other devcontainer features?

Yes, Python and Git Feature currently supports ie. See https://github.com/search?q=repo%3Adevcontainers%2Ffeatures%20os-provided&type=code

It does make sense! ...except that it probably won't match the requested version. 😉
Honestly, I think that skipping the os-provided version is the simplest approach, at least for the first pass.

Apologies for not writing a detailed proposal, I was proposing to use apt only if the requested version matches with the one available in the feeds. However, if not, install from source.

If we could support os-provided as a value for version, then this is way better approach. The reason to have an option to install from apt is simply because that's faster. Installing from source takes quite a lot of time compared to installing from apt, and we could soon hear complaints about the Feature taking too long 😅 So this is kind of like a precaution and a faster alternative.

I know I was one of the people who questioned even installing a Ruby version manager

Similarly, while it might be nice to provide a simple "no version manager" option, that doesn't need to be in the first updated release. We can iterate towards that. But it's not an option today, so it's not like the feature would be losing any functionality if it's left out of a first PR.

I agree having some ruby version manager is definitely better than having none. Then we have that question of which version manager should be installed by default? Should it be rvm, rbenv or something else?

According to the current polls, it's one vote for rbenv and three votes for none 😅

We definitely want to refactor the Ruby Feature as per how the community prefers, but it doesn't seem like we have enough votes right now.

If folks prefer to not have any ruby version manager installed, and if it's possible to install the requested version of ruby from source or ruby prebuilt binaries, then having none as default makes sense. This will most likely be used by learner development scenarios who doesn't have a preference on version managers and simply want some working version of ruby for their hobby projects.

In case of more complex scenarios, folks would have their own preference of ruby version managers which they could simply install with rubyVersionManager option.

Talking about the Ruby image, depending upon how this discussion evolves, we could decide a plan for it.

But you may want to keep them as-is even if it doesn't need them, simply for backward compatibility with the current feature.

This refactoring PR would most likely be a major version bump of the Feature. Major version bumps don't necessarily guarantee that it supports back compat, hence, if this might cause any other (future) issues, then we could avoid adding it.

Even if you were able to get it working with both Ubuntu and Debian, I'd still want to open an issue in that repo to make sure they're okay taking on a new dependency. It may be that they don't want to promise ongoing compatibility and stability for anything other than https://github.com/ruby/setup-ruby.

That sounds like a great idea, @nevans feel free to loop me in if necessary

@ThomasOwens
Copy link

@nevans and @samruddhikhandale

I've opened a draft/work-in-progress PR to start to show my work iteratively.

At this point, there should be parity with the Node Feature. I used that as a model since Node is frequently used to satisfy a dependency in Rails so I have some familiarity with setting up a Node development environment. I also noticed that they were using NVM, which is the Node equivalent to RVM. Although I prefer rbenv (and nodenv), this is a good first step. The initial issue of having multiple version managers has been resolved, which was my number one concern. At this point, I think it's iteration until it's good enough to merge.

Would need to check and images that use this Feature just to make sure requesting the image will be fine. I know it's used in the Ruby and the Ruby on Rails and Postgres devcontainers, but I'm not sure how to test building those while pointing at my modified feature.

@samruddhikhandale
Copy link
Member Author

// cc @eljog and @joshspicer for visibility.

@ThomasOwens
Copy link

@samruddhikhandale @eljog @joshspicer

I realize it's getting close to holiday time in the US and you're all US-based from your profiles, so not expecting a fast response, but I wanted to check in on how to get this to a ready-to-merge state.

I have two open questions:

  • What is the minimum feature set to merge a PR? Right now, the current PR resolves the (most important) issue of installing multiple version managers and has parity with the Node Feature. This PR, in current state, would allow me to confidently use the devcontainer. There is discussion of other features - rbenv, choosing system ruby version. I'm not sure what the minimum state to be broadly useful or acceptable is.
  • Is the test coverage good? I see that the actual Ruby images use a Ruby image as their base. That seems both unexpected and wrong to me, but perhaps the tests should reflect the current reality rather than the ideal state.

Once the minimum feature set is implemented with appropriate test coverage, I can flip the PR out of its draft state.

@nevans
Copy link

nevans commented Dec 18, 2023

@ThomasOwens NOTE: I edited this comment after first posting it.

I'll copy these comments over to the PR. To summarize my thoughts here:

As an incremental improvement, it's fine. But if we are going to bump the version number for backward incompatibility, I personally vote for delaying until we can make rvm fully optional. This is only a minor improvement over the current version: rbenv is unusable now, and (after the PR) rbenv will still be unusable without more backward incompatible changes.

I personally consider the minimum feature set to be:

  • Don't set GEM_HOME or GEM_PATH at all. They will screw up alternate version managers or other customization added by the user (either before or after this feature). (Adding rvm or rbenv specific directories to PATH should be just fine.)
  • Support os-provided, at least for apt-based distributions.
  • Use the version of ruby that's already on the PATH, if the version number matches the requested version. This allows the ruby feature to cooperate with the official docker hub ruby image or the user's own custom base image.
  • When the version of the available ruby does not match the requested version, install the requested version somehow that won't break any existing or future installs of rvm or rbenv. How it's installed is not part of my "minimum feature set", so long as my env vars constraint is met. 🙂

My recommendation on how to satisfy my "minimum feature set":

  • Do not install either rvm or rbenv (unless requested by the user by a config option).
  • Do install ruby-build to /usr/local, unless it's already installed on the PATH or (perhaps) disabled by a config option. As far as I can tell, this is the only version management tool that is actively supported by the core teams of CRuby, JRuby, and TruffleRuby. It works just fine without rbenv. And, even if it is unused, it isn't large and installing it shouldn't hurt anything.
  • When necessary, install from source using ruby-build to either /opt/ruby or /usr/local. Ensure the default PATH can find all of ruby's bins.

I'm not sure if it should be part of the minimum feature set, but I think we should probably do one of the following, to aid with backward-incompatible upgrade pains:

  • Ensure that the user can use rvm, even with GEM_HOME and GEM_PATH unset.
    • Check the feature against at least a two base images: the default devcontainer base image, and the default docker hub ruby image. I suspect rvm might work without those env vars on the default devcontainer images, but it might break if the user bases the feature on the docker hub ruby image (which also sets these env vars!!!).
  • Document that they may need to set the environment variables themselves to make everything play nicely with rvm.

None of the following are in my personal minimum feature set... but they would all be nice to have:

  • If you make the install location user-configurable, document that variable as only applying to the ruby installed by ruby-build (i.e: not the os-provided version, nor to any version installed via rvm or rbenv).
  • If the OS provided version matches the requested version but isn't installed, install it (at least for apt distros). The version detection code in other official features can probably be copied to accomplish this.
  • A config option to install a version manager: none or rvm or rbenv (or chruby).
  • Install via rvm when rvm is installed (by the feature, or already on PATH).
  • Install via rbenv install when rbenv is installed (by the feature, or already on PATH).
  • If we need (or want) any further customization to how our default installation of rubygems behaves, use their recommended 'operating_system.rb' and not environment variables. Environment variables are great for overrides, but (IMO) they are unsuitable for defaults.

What do you think? (My apologies: I'll probably be too busy with other end-of-year stuff both at work and in my personal time, and I probably can't really offer any more than my criticism right now.)

@ThomasOwens
Copy link

@nevans

Thank you! Very much appreciated.

I agree that there are some minimum items open for a 1.3.0 incremental improvement:

  • Clean up the GEM_HOME, GEM_PATH, and PATH environment variables.
  • Expand test coverage to include both the default MCR images and the official Ruby image currently used. Both should be supported for the Feature. Need to make sure that regardless of the base image, the environment works.

I do think the overall question about how far to go is more of a strategic question for the core devcontainer team, though. Does it make sense to have a 1.3.0 that follows the patterns of the Node Feature (that is, the current PR plus the environment variable cleanup)? Or would it be better for a 2.0.0 that does a lot more of what you suggest?

I think your minimum suggestions for what a 2.0.0 look like are probably spot-on:

  • Always install ruby-build. It's small and, since it's an installer and not a version manager, shouldn't conflict with any other version manager.
  • An option to install rvm. This is the only working version manager today, so being able to request it should minimize the amount of people breaking with an update.
  • Necessary versions are installed with your version manager. If you don't have a version manager, ruby-build takes care of installing them. If you install rvm, install Rubies with rvm. When rbenv is supported, install Ruby versions with rbenv and ruby-build.
  • Install Rubies built by ruby-build to /usr/local. This is based on my understanding of what /opt and /usr/local are used. Perhaps consider allowing a location to be specified, but that's not minimum requirement for 2.0.0.

I don't agree with supporting os-provided, though. I think that if you're requesting the Ruby Feature in your devcontainer, there should be a guarantee that a Ruby version exists. I do think it's safe to check if an OS-provided Ruby exists and matches the requested version, but that requires requesting a specific version.

@iwdt
Copy link

iwdt commented Jan 27, 2024

It seems to me that Ruby feature should only install Ruby (via ruby-install or ruby-build and when finished, remove it). rbenv, rvm, chruby, asdf, etc. should be separate features that are installed separately if the user needs it

ruby-install looks like the easiest way to do this, since it takes into account installing all necessary dependencies (which may change from version to version) if they are missing, while ruby-build just downloads the archives and starts building.

@samruddhikhandale
Copy link
Member Author

@ThomasOwens @nevans Apologies for the delay in response, was OOF.

would it be better for a 2.0.0 that does a lot more of what you suggest?

Going over the discussion, I feel bumping the major version makes sense to me. This gives us more flexibility for making changes, and we don't have to worry about backwards compatibility.

Always install ruby-build. It's small and, since it's an installer and not a version manager, shouldn't conflict with any other version manager.

An option to install rvm. This is the only working version manager today, so being able to request it should minimize the amount of people breaking with an update.

Install Rubies built by ruby-build to /usr/local. This is based on my understanding of what /opt and /usr/local are used.

They ^ make sense to me and sounds good. 👍

Necessary versions are installed with your version manager. If you don't have a version manager, ruby-build takes care of installing them. If you install rvm, install Rubies with rvm. When rbenv is supported, install Ruby versions with rbenv and ruby-build.

Does this mean we are leaning towards not installing any manager?

By going over the reactions on this issue, looks like the community wants rbenv to be installed by default. As we were trying to shape the changes based on community feedback, it makes sense to me.

It seems to me that Ruby feature should only install Ruby (via ruby-install or ruby-build and when finished, remove it). rbenv, rvm, chruby, asdf, etc. should be separate features that are installed separately if the user needs it
ruby-install looks like the easiest way to do this, since it takes into account installing all necessary dependencies (which may change from version to version) if they are missing, while ruby-build just downloads the archives and starts building.

Thanks @iwdt for pointing out. However, I am not too familiar with https://github.com/postmodern/ruby-install, and have questions on how well it's supported and maintained. Also, I am not confident if the the community will prefer to install ruby from third party binaries. I believe this will need an audit and security review before proceeding with this. // cc @Chuxel @bamurtaugh @craiglpeters wondering if you are familiar with https://github.com/postmodern/ruby-install ?

@ThomasOwens
Copy link

Does this mean we are leaning towards not installing any manager?

I don't think so, @samruddhikhandale. It seems like the community is in favor of rbenv, so that should be the default. Anything else would be an option. Minimally, none should also be an option. I'd like to include rvm as well.

I'd also tend to agree with not including ruby-install, at least now. It could easily be a 2.1 thing, if someone is interested in that. The idea of installing third-party binaries doesn't seem that appealing to me, even as an option.

I think the only open question from my end is the deal with the base image. It feels a bit strange to use a Ruby image and then install more Ruby stuff on top of it. I had put this in an earlier comment:

I see that the actual Ruby images use a Ruby image as their base. That seems both unexpected and wrong to me, but perhaps the tests should reflect the current reality rather than the ideal state.

@samruddhikhandale
Copy link
Member Author

I don't think so, @samruddhikhandale. It seems like the community is in favor of rbenv, so that should be the default. Anything else would be an option. Minimally, none should also be an option. I'd like to include rvm as well.

Great, aligns with my thought!

I think the only open question from my end is the deal with the base image. It feels a bit strange to use a Ruby image and then install more Ruby stuff on top of it. I had put this in an earlier comment:

This has been historical, the ideology here is to use official images as base (so ruby in this case) and top it off with more utils to improve the dev container experience (using common-utils Feature, adding git, and other ruby pkgs/utils).

You will see the same behavior with majority of the devcontainer/images (See dotnet, python, anaconda)

@bamurtaugh
Copy link
Member

However, I am not too familiar with https://github.com/postmodern/ruby-install, and have questions on how well it's supported and maintained. Also, I am not confident if the the community will prefer to install ruby from third party binaries. I believe this will need an audit and security review before proceeding with this. // cc @Chuxel @bamurtaugh @craiglpeters wondering if you are familiar with https://github.com/postmodern/ruby-install ?

Thanks for tagging me. On this point - I agree with your takeaway @samruddhikhandale 👍 (I'm also not familiar with https://github.com/postmodern/ruby-install, so feels like it'd be safest not to include).

@nevans
Copy link

nevans commented Feb 3, 2024

I am familiar with https://github.com/postmodern/ruby-install and it's great. I have great respect for the maintainer (and also for the rvm maintainers). And using either would simplify the install scripts. Nevertheless, I think both should be opt-in only.

So I'm in agreement with @samruddhikhandale, @ThomasOwens, and @bamurtaugh: The default should be ruby-build. It is the only tool officially supported by the ruby core team (as well as JRuby, TruffleRuby, etc).

@samruddhikhandale
Copy link
Member Author

@ThomasOwens Wondering if you have started working on this, and let us know if you need any help. Thanks!

@ThomasOwens
Copy link

@samruddhikhandale Yep. Had to get some tools updated, but I've started refactoring the work in the draft PR and will start to work toward the decisions above. A bit busy this week/weekend, but looking to get more into this after.

@samruddhikhandale
Copy link
Member Author

@samruddhikhandale Yep. Had to get some tools updated, but I've started refactoring the work in the draft PR and will start to work toward the decisions above. A bit busy this week/weekend, but looking to get more into this after.

That's a pretty exciting news, thank you so much! 🎉

@koleskizzzzz
Copy link

Hello! 👋

We are looking forward to get some feedback for refactoring the Ruby Feature.

Which Ruby version manager would you like the Feature to install by default? You can use the respective emojis for voting your preference!

  1. None - 😕
  2. rbenv - 🎉
  3. rvm - 🚀
  4. Any other (feel free to drop a comment)

For the ruby Feature, we have heard issues with conflicting ruby managers (rbenv and rvm) which can be problematic for complex projects. We have a community member who is willing to contribute a PR with the necessary changes ✨ See #603 (comment)

(Draft) Proposal

  • Create a new Feature option which will define the ruby version manager to be used for installing ruby

    • Let’s say we call it rubyVersionManager
    • It will be string with enum: ["none", "rbenv", "rvm"])
    • Default value: "TBD: Poll result"
  • Based on the value provided by rubyVersionManager, the Feature will use that version manager to install ruby

  • In case of none, we will first attempt to install it with apt.

    • If it fails, then install from source.

Looking forward to everybody's feedback, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants