Ruby Bundler - Multiple Ruby versions in the same gemfile - ruby

I am using rbenv and bundler on macos.
I need to maintain multiple versions of Ruby for my application. Specifically 2.2.4 and 2.5.5. Different users will have different versions.
Currently I am only working with 2.2.4 so I do the following. My gemfile looks like this
source "https://rubygems.org"
ruby '2.2.4'
gem "net-ssh", "4.1.0"
gem "net-scp", "2.0.0"
.....
To install I run bundle install and then to deploy with my application I run
bundle install --deployment --path src/mct-tools/ext/gems
This generates a folder src/mct-tools/ext/gems/ruby/2.2.0 in my application that I then distribute.
Now I would like to add ruby 2.5.5 with updated gems so I was wondering whether it would be acceptable to have a gemfile looking like this one
source "https://rubygems.org"
ruby '2.2.4'
gem "net-ssh", "4.1.0"
gem "net-scp", "2.0.0"
.....
ruby '2.5.5'
gem "net-ssh", "6.1.0"
gem "net-scp", "3.0.0"
.....
so that when I deploy with bundle install --deployment --path src/mct-tools/ext/gems the two versions are added to the gems folder.
Is this the correct approach? An alternative would be to specify two different gemfiles for each version and then switch between them.

Gemfiles Declare Dependencies
A Gemfile declares a dependency on a Ruby version, with or without semantic versioning constraints. It is not meant to control multiple build targets for your application. It simply enforces that the Ruby version available to your app/gem is whatever you've defined. For example:
# Will fail if run with a different RUBY_VERSION.
ruby '2.2.4'
# Allows RUBY_VERSION >= 2.2.4, but <= 2.3.
ruby '~> 2.2.4'
# Allows either Ruby 2.2.4+ or 2.5.5+, with
# a minimum (but no maximum) patch version.
ruby '~> 2.2.4', '~> 2.5.5'
However, it won't install a given Ruby, nor do anything other than raise an error and a non-zero exit status when running bundler install. You need to take a different approach to test multiple targets.
Changing Build Targets with Continuous Integration (CI) Tools
If you're using an external CI like TravisCI, you can create a build matrix that targets multiple Ruby versions to test against. Whether you remove the Ruby version constraint altogether, or specify a supported range, is up to you. Leveraging your CI tool to build against the versions of Ruby you plan to support is really the best approach, though, whether or not you constrain your Ruby runtime in a Gemfile.
For example, you might use a matrix in your travis.yml like so:
language: ruby
rvm:
- 2.2.4
- 2.5.5
Switching Gemfiles
If you insist on doing it the way you're doing it, with a singular Ruby version allowed in your Gemfile, then you might consider having two separate gemfiles with different names in your source tree, such as Gemfile-2.2.4 and Gemfile-2.5.5. You can then specify which Gemfile to use with Bundler's --gemfile flag , or by symlinking a custom Gemfile to the canonical Gemfile for your project.
Here are some examples to consider:
# Resolve against a specific Gemfile with
# hard-coded Ruby version.
$ ls Gemfile*
Gemfile-2.2.4 Gemfile-2.5.5
$ bundle install --gemfile="Gemfile-2.2.4"
# Resolve against whatever custom file is
# symlinked to your ./Gemfile.
$ ln -sf Gemfile{-2.5.5,}
$ ls -F Gemfile*
Gemfile# Gemfile-2.2.4 Gemfile-2.5.5
$ bundle install
Both approaches work, but the former is more flexible at the cost of needing to specify your chosen Gemfile each time, while the latter can help when you have a development/testing workflow that doesn't support Bundler's --gemfile flag.
Changing Rubies with Ruby Managers
If you have multiple Ruby versions in development, best practice is to use a version manager such as rvm, rbenv, or chruby. You can use your version manager to change rubies back and forth manually as needed.
You might also check whether your version manager supports auto-switching on .ruby-version or other configuration files. You'd still have to update that file each time you want to build or test against a different Ruby, but you wouldn't have to keep changing your Gemfile contents, re-pointing the Gemfile symlink, or updating a flag on each call to Bundler.
Whether or not any given approach is better than others will depend on your workflow. No technical solution will fit all circumstances, so your mileage may legitimately vary.

Related

How can I create multiple Gemfile to run with different Ruby version with Uru?

How can I create multiple Gemfile to run with different Ruby version? I am using Uru to manage ruby versions and was wondering if we have something like create gemsets in RVM.
I want to be able to run a Gemfile for Ruby v1.8.7 and another for v2.5.1.
I would discourage from creating multiple gemsets, it will be a maintenance mess (that probably accounts for gemsets for different rubys as well), also there is a clean way to manage multiple ruby versions within a Gemset (given that you are 100% sure that you only serve to those versions):
if Gem::Version.new('1.8.7') == Gem::Version.new(RUBY_VERSION)
# your gemspec for 1.8.7
else
# your gemspec for 2.5.1
Using Gemfile::Version allows you to do version comparisons, for instance:
Gem::Version.new('1.8.7') < Gem::Version.new('2.5.1')
# => true
See the docs for more information

Force Bundler to Use my Gem Source

I am running my own gem server with geminabox. This gem server hosts private forks of several public gems, as well as my own gems. I have a Gemfile with the sources listed like so:
source https://rubygems.org
source http://my/gem/server
When Bundler installs a bundle, I would like it to always use the version available on my gem server if it exists and satisfies the version requirements. I can't get this to work. Instead, bundler seems to pull from rubygems unless it can't find a gem with the same name there. This may be because the versions on rubygems are higher. However, bundler's documentation makes no mention of version numbers. This is how it describes source priority:
SOURCE PRIORITY
When attempting to locate a gem to satisfy a gem requirement, bundler uses the following priority order:
The source explicitly attached to the gem (using :path or :git)
For implicit gems (dependencies of explicit gems), any git or path repository otherwise declared. This results in bundler prioritizing the ActiveSupport gem from the Rails git repository over ones from rubygems.org
The sources specified via source, searching each source in your Gemfile from last added to first added.
Also, I know it is possible to explicitly list a source for each gem in the Gemfile. I would prefer to avoid this, since I have many of my own gems, and it is annoying to make explicit entries in the Gemfile for dependencies of dependencies (which you have to do, because bundler will not read the Gemfile of dependencies).
How can I achieve my goal?

Better way to work on a Rails gem

Hi guys sometimes I work a gem and sometimes to develop this gem I need a Rails application.
Actually when I do this I make my gem in the vendor/plugins/my_gem/ directory and I do all work in this directory.
May be there is other way to do this, more cleaner (plugin like this won't work in rails 4.0).
I normally work have a separate, version-controlled projects for every custom gem that I work on. I normally create gems using Bundler, although there are other options. I specify rake as a development dependency in the my_gem.gemspec. After I'm done writing code and specs in the gem, I normally assign it a new version, then run:
# cd/to/my_gem
bundle exec rake build # build a gem
ruby -S gem install ./pkg/my_gem-0.0.1.gem # install it locally
# cd/to/app_using_gem/
gem unpack my_gem -v 0.0.1 --target ./vendor/gems # vendorize gem
I think this way is cleaner than modifying code in vendor/plugins/my_gem/

Does the Bundler 1.2.0 ruby version check obviate the need for a basic .rvmrc file?

Bundler 1.2.0.pre includes a new "ruby" DSL option. According to Heroku's documentation, they use this new Gemfile syntax to control which ruby version is used when you push your app.
Being pre-release, documentation for the new Bundler option is fairly thin on the ground at the moment, and the Bundler 1.2 roadmap simply lists it as "ruby version check".
My question is: currently I use a one-line .rvmrc file in most of my projects, in which I only specify the ruby version for that project (eg. rvm ruby-1.9.3). I don't use RVM gemsets or anything else (I prefer to vendor all of the required gems within the project, and let Bundler manage the dependencies).
Given my trivial RVM config, will the new "ruby" option in Bundler's DSL mean I no longer need to specify a .rvmrc file at all? Or are they two different things?
(I do like the fact that RVM automatically switches the ruby version when I cd into my project...not sure if Bundler would do that, or if it just warns when the current version doesn't match?)
the new ruby is a function and it will allow anything that finally evaluates to a string.
Unfortunately o read it you would need to use a bundler command which assumes you already have a ruby.
Instead RVM gives you two ways to defining ruby in Gemfile:
1) ruby "1.9.3" - simple strings
2) #ruby=1.9.3-p125 - a comment when you want to use ruby code for ruby or when you want to specify patchlevel or gemset!:
#ruby=1.9.3
ruby ENV['RUBY_VERSION'] || '1.9.3'
Will allow bundler to work with any ruby loaded by RVM but by default will use 1.9.3 from #ruby=

How do I add conditional rubygem requirements to a gem specification?

Is it possible to add a gem dependency only if the person is using a certain version of ruby?
Background: I'm working on a fork of a project that uses Test::Unit::Autorunner and the like. They are part of the standard library in ruby 1.8, but aren't part of the standard library in 1.9.1, and is instead in the "test-unit" gem. I want to add a dependency that says that if someone's using ruby 1.9.1 or later, install the "test-unit" gem, but if they're using 1.8 or earlier, they don't need to install anything.
If you look at the gemspec documentation for add_dependency, there isn't an option for a ruby version. Perhaps you could use the post_install_message attribute to tell the user to install the gem if they're using ruby 1.9.
I did this exact thing for a project. The trick is to add the script as an extension, which will then get executed at install time on the user's machine.
Here are code snippets and links to our github:
First, when in the gemspec (we're actually using a Rakefile to generate it, but the result ends up the same) source
# This file *needs* to be named this, there are other files
# for other extension processors
s.extensions << 'ext/mkrf_conf.rb'
And then the relevant lines in that mkrf_conf.rb source
require 'rubygems/dependency_installer.rb'
inst = Gem::DependencyInstaller.new
inst.install "test-unit" if RUBY_VERSION > "1.9"
Gem doesn't support conditional dependencies (except on gem builder's environment -as noted above), and bundler is not a viable option to solve this either - see https://github.com/carlhuda/bundler/issues/1281
hay ... i'm kind of a ruby newbie ... if this is the best way to do it.
any way ... i wish i can do that using only Ruby .... though u can use your operating system shell to do that, by using this in your program installer, just execute (works for Linux based operating systems):
$ruby --version
(u can execute that from a ruby installer file, just like: ruby --version)
and put a possibility according to output, if it's 1.9.1 add an extra line to execute:
$ sudo gem install gem_name
else, just leave it be.
Checkout this tutorial in the Ruby Programming wikibook.
Tt shows how to install different versions of dependencies depending on what version of ruby the installee is using.
(short answer--it ain't as easy as it should be)
You can't. You need to build two gems, one with
spec.required_ruby_version = '~> 1.8.6'
and one with
spec.required_ruby_version = '~> 1.9.1'
spec.add_dependency 'test-unit', '~> 2.0.5'
Gemspecs are just ruby files anyway, so you can execute any ruby code inside them, so:
spec.add_dependency = 'test-unit', '>= 2.0' if RUBY_VERSION =~ '1.9'
EDIT: Specs run only on the builders machine.

Resources