postmodern / chruby

Changes the current Ruby
MIT License
2.86k stars 190 forks source link

Use RUBY_ENGINE_VERSION for gem env. #329

Closed brixen closed 8 years ago

brixen commented 9 years ago

The fact that chruby assumes RUBY_VERSION is the correct way to segregate gems on all Ruby implementations causes a great deal of grief for users. Two of the primary problems involve fixed shebangs in gem binary wrappers and conflicting native libraries in C-ext gems.

  1. Fixed gem binary wrapper shebangs:

Sometimes (but not always), rubygems will install a gem binary wrapper with a fixed sheband. For example, the 'bundle' binary wrapper will have a shebang like:

/Users/johnny/.rubies/ruby-2.2.0/bin/ruby

The 'bundle' file itself is in the gem bin directory:

/Users/johnny/.gem/ruby/2.2.0/bin/bundle

Everything works fine until the user installs another Ruby version. Then the user may switch to that version, run bundle and the wrong Ruby version will get invoked due to the fixed shebang.

For MRI, this is less of an issue, because every release of MRI changes the value of RUBY_VERSION. However, this is not the case for Rubinius nor any other Ruby implementation. Forcing the gem env to use RUBY_VERSION mixes the gem binary wrappers for multiple versions and leads to extremely confusing errors for users. For example, when specifying the ':engine_version' in a Gemfile, the version of Ruby running bundle differs from the version selected with chruby:

$ chruby 2.5 $ ruby -v rubinius 2.5.2 (2.1.0 7a5b05b1 2015-01-30 3.5.1 JI) [x86_64-darwin14.3.0] $ bundle Your rbx version is 2.4.1, but your Gemfile specified rbx 2.5.2

This results because the shebang in the gem wrapper is an absolute path to the 2.4.1 version of Rubinius. Since this directory for the gem binary wrappers is shared across all Rubinius versions, this problem is inevitable.

  1. Conflicting native libraries in C-ext gems

Very similar to the shebang issue above, this issue involves installing a C-ext gem under a particular version of a Ruby implementation and then loading that gem under another version. For example, Psych uses a C-ext. Installing Psych under one version of Rubinius will build the C-ext against the C-API for that version. When the user installs another version of Rubinius, and then runs bundle when Psych is listed as a gem, Bundler will find the installed version and not install a new version. But if the C-API has changed in the new version of Rubinius, the C-ext built against the older version may not load in the newer version. The reverse is also possible: the gem installed under a newer version may not work with an older version.

Rubinius added the RUBY_ENGINE constant many years ago to address the challenge of determining which implementation was running the Ruby code. A similar need exists for distinguishing the Ruby engine version, as illustrated above. Rubinius has added the RUBY_ENGINE_VERSION constant and it will be available in 2.5.3+ releases. Just as with RUBY_ENGINE, which was added in MRI 1.9 but never available in MRI <= 1.8.7, we need conditional code to account for RUBY_ENGINE_VERSION not being set. This provides backward compatibility and results in zero behavior change for any existing engines and versions.

havenwood commented 9 years ago

Just an aside relating to the shebang situation, but I look forward to RubyGems #1049 landing.

Standardizing around RUBY_ENGINE_VERSION seems like a really good idea. In JRuby RUBY_ENGINE_VERSION = JRUBY_VERSION and in CRuby RUBY_ENGINE_VERSION = RUBY_VERSION, etc.

brixen commented 9 years ago

The implementation-specific code cannot be removed because that does not solve the problem for existing releases. The implementation specific code could be moved to implementation specific functions.

postmodern commented 9 years ago

I also would like to get some shunit2 tests on this behavior. We could download a rbx binary in test/setup, just like we do with MRI.

headius commented 9 years ago

I agree that RUBY_ENGINE_VERSION is a useful addition (a perfect counterpart to RUBY_ENGINE) and the patch (sans impl-specific case statement) seems fine to me.

enebo commented 9 years ago

We added this to JRuby: https://github.com/jruby/jruby/issues/2751 . JRuby 1.7.20+ and JRuby 9.0.0.0.pre2+ will have this constant defined.

enebo commented 9 years ago

@postmodern @brixen Do you think we could solve the historical problem and the no-impl-specific code problem by making a new ruby-versions gem which defined RUBY_ENGINE and RUBY_ENGINE_VERSION if they are not defined? Each implementation would probably do a single commit and the gem would never change. All library authors could just depend on the gem. Another gem dep I know, but it seems like a compromise?

postmodern commented 8 years ago

Just saw that mruby-1.2.0 also added RUBY_ENGINE_VERSION, so thinking it's time to come back to this. I will load RUBY_ENGINE_VERSION if defined but use ${RUBY_ENGINE_VERSION:-$RUBY_VERSION} in the GEM_HOME, so that it gracefully falls back to the original behavior for older previously installed rubies with pre-existing gem dirs.

postmodern commented 8 years ago

Pushed my own version of this behavior to the ruby_engine_version branch. We'd just need to download a precompiled version of jruby or rubinius and use it to test if RUBY_ENGINE_VERSION is detected and used within the GEM_HOME env variable.

havenwood commented 8 years ago

CRuby now supports RUBY_ENGINE_VERSION as well with the release of ruby-2.3.0.

postmodern commented 8 years ago

We would need a ruby where RUBY_VERSION != RUBY_ENGINE_VERSION to properly test the behavior. We could write stub bin/ruby scripts that mimic the expected behavior. While we wouldn't be testing against the "real deal", stub scripts would allow us to test every edge-case.