Get ruby gem version without installation - ruby

If I have a local gem (gemfile.gem) how can I get the name and version information from code or the command line without installing the gem.
Reason: I'm installing a user gem to validate it and want to uninstall it to clean up. User gem is not something I control so I can't depend on naming conventions.
CLI Solution:
gem spec gemfile.gem name
gem spec gemfile.gem version
Ruby Solution:
name = Psych.safe_load(`gem spec gemfile.gem name`).to_s
version = Psych.safe_load(`gem spec gemfile.gem version`, permitted_classes: [Gem::Version]).to_s
# Now you can uninstall the gem with
Gem::Uninstaller.new(name, {:version => version, :force => true}).uninstall

You can see locked version in Gemfile.lock
cat Gemfile.lock | grep gem-name
Other option, but need bundle install first
bundle exec gem dependency | grep gem-name
Update:
If you need to check local gem version, for example some-gem.gem, you can use such command to parse all information from binary
gem specification some-gem.gem
or just
gem spec some-gem.gem
You can also look it with Ruby format
gem spec some-gem.gem --ruby
Of course you can use grep to filter lines with version word
But it's better to pass it as argument like this
gem spec some-gem.gem version

Your question is ambiguous. If you mean "How can I read in the gem name and version from the gemspec?" then you can use the output of Gem::Specification#load. For example, assuming you have a gem with a standard layout and foo_bar.gemspec in the root of your gem's project directory, you can use Git to find the top-level of your project and read in the gemspec:
$ cd "$(git rev-parse --show-toplevel)
$ ruby -e 'puts Gem::Specification.load "#{File.basename Dir.pwd}.gemspec"'
#<Gem::Specification name=foo_bar version=0.1.0>
You can then parse the output with sed, awk, or cut.

A Gemfile does not (necessarily) specify an exact version of a dependency. It might specify nothing (i.e. "any version is fine"), or ~> 1.0 (i.e. >= 1.0 and < 2.0), or whatever. Also, dependency constraints might further restrict the valid range of versions.
I'm assuming that this isn't what you meant by your question. Instead, you'd like to know what exact versions of dependencies will be installed by running bundle install, given a Gemfile.lock.
One way to achieve this reliably (i.e. rather than using grep and eyeballing which line(s) are most relevant) is by parsing the Gemfile.lock:
require 'bundler'
lockfile = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))
puts lockfile.specs.find { |spec| spec.name == 'the-gem-you-want-to-check' }.version

Related

Ruby Bundler - Multiple Ruby versions in the same gemfile

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.

Why does bundler use more than one gem location?

This is happening in Puppet's bundle.
The Gemfile specifies
gem "puppet", :path => File.dirname(__FILE__), :require => false
But one of the gems I installed in $GEM_HOME appears in $: after all.
$ bundle exec ruby -e 'puts $:'
...
/home/puppy/puppet-git-clone/lib
...
/usr/lib/ruby/vendor_ruby
...
/home/puppy/gems/gems/puppet-3.7.5/lib
...
This is not a problem in and of itself, but apparently Ruby will load Puppet 3.7.5 instead of the 3.7.3 I checked out of the git repo.
$ bundle exec irb
irb(main):001:0> require 'puppet'
=> true
irb(main):002:0> Facter.value(:puppetversion)
=> "3.7.5"
Why is Puppet not loaded from the git tree and how can I debug this further?
Update
Puppets .gemspec might be involved. It's clever about specifying the version. I now worry that Rubygems does in fact load the installed 3.7.5 gem so that Puppet.version would truthfully report a wrong value, throwing off bundler. Could that be what's happening?
Update 2
As suggested in the comments, I tried settings the path and version statically in the Gemfile.
gem "puppet", "3.4.2", :path => "/home/puppy/puppet-git-clone", :require => false
As for the result, well - at least bundler is consistent in its views ;-)
Could not find gem 'puppet (= 3.4.2) ruby' in source at /home/ffrank/git/puppet.
Source contains 'puppet' at: 3.7.3
Run `bundle install` to install missing gems.
The quick fix is to add -Ilib to your ruby command:
$ bundle exec ruby -e "require 'puppet'; puts Facter.value(:puppetversion)"
3.7.5
$ bundle exec ruby -Ilib -e "require 'puppet'; puts Facter.value(:puppetversion)"
3.7.3
If we compare the load paths, you can see that adding -Ilib results in 3.7.5 not being present in the second load path:
$ diff <(bundle exec ruby -e 'puts $:') <(bundle exec ruby -Ilib -e 'puts $:') | grep 'puppet-'
< /Library/Ruby/Gems/2.0.0/gems/puppet-3.7.5/lib
It seems like this should be the default behavior, so there may be a bug in bundler.
Provided you've deleted your Gemfile.lock and removed all other versions of the gem before trying bundle exec ... although not explicitly defined by the same problem, this is a known problem with Bundler check this out:
https://github.com/bundler/bundler/issues/3585
And it ought to be fixed as of this merged Pull Request:
https://github.com/bundler/bundler/pull/3592
This will cause your "preferred source" to be favored, and used.
( Links used as answer because this refers to existing activities, not a solution I could put here in the answer. Not link-only answer. )
try this :
source "file://home/puppy/puppet-git-clone"
gem "puppet", "3.4.2", :path => "/home/puppy/puppet-git-clone"
why do you need require false?
After what you said in my previous answer, try this:
Add gemspec to your Gemfile near the top.
If you do this, the subsequent gem ... calls will override the gemspec which is ultimately setting the version.
Add it right here:
source ENV['GEM_SOURCE'] || "https://rubygems.org"
gemspec # and let me know if this fixes it
Like I recently said, I found the same problem you had, in my own projects. And I've solved it by a workaround. The idea itself is what I'm giving here, I'm not saying it's the most efficient or bug-free way. A variation of this approach worked for me.
It involves hijacking Gemfile and perhaps gemspec, depending on what's the offender in reality -- that part is still unknown. My most recent answer, the second approach I gave could solve this already. If not, you may need a workaround. There are many standing impasses for Bundler.
As a work around, insert a curator.
I suggest, at the end of Gemfile you insert a processor such as this one, to curate Bundler::Dsl yourself. We can focus completely on the gem you're looking to resolve, but it could be done for any and all gems.
For example... this is mostly a concept, it may run, but it may have a bug. You will need to harden it. This will remove everything but the version you expect:
PUPPET_VERSION = 'version desired'
until(current = self.dependencies.find { |d| d.name == 'puppet' }) == 1
current.each { |gem|
if !gem.version == PUPPET_VERSION
self.dependencies.delete(current)
end
}
end
I'm not sure which version you actually want. There are three versions mentioned, 3.7.3, 3.7.5 ... just plug in the one you do want. Any other versions will be purged from the dependencies Bundler is working with.
I had the same problem in past, I think you can specify your gem without the 'require: false' parameter and it will look for the Gemfile specific version.
gem "puppet", "3.4.2", :path => "/home/puppy/puppet-git-clone"
If you specify the 'require' option with 'gem' and run the irb command with bundle exec irb it always load the latest version for the gems which has declared with require: false option.

Ruby - Digest::Digest is deprecated; Use Digest

I've been getting this warning whenever I run my tests or start rails server.
When I run grep from .rvm folder I see the following:
grep -R 'Digest::Digest' .
./rubies/ruby-2.1.0/lib/ruby/2.1.0/openssl/digest.rb: warn('Digest::Digest is deprecated; Use Digest')
- additional references to openssl and ruby 2.1.0
So it looks like it's a Ruby 2.1.0 bug. Are there any fixes? There are no patches available yet as far as I can tell.
Whilst my app uses Fog and a bunch of other gems that have issues relating to this message, I'm using patched versions that don't have the bug. So I reckon Ruby is at fault here.
Borrowing the reply from this thread
OpenSSL::Digest::Digest has been discouraged to use from very ancient era such as Ruby 1.8 and finally was deprecated recently.
If you search for the error message, you will see that a lot of gems, including fog, were still using the deprecated syntax.
I assume it will take a while before all the gems will be updated. If you came across the deprecation in one of the libs you use, I encourage you to report it to the maintainer.
Here's a few examples
https://github.com/fog/fog/pull/2473
https://github.com/alexreisner/geocoder/pull/580
https://github.com/ruby/ruby/pull/446
It's likely your Rails app depends on a gem that is using that old syntax.
If you're using bundler, a good way to find out what is causing the problem is to grep through all the gems defined in your Gemfile:
# grep (ack or ag) the problem code
bundle show --paths | xargs grep -r Digest::Digest
# here was my output
~/.gem/ruby/2.1.0/gems/fog-1.15.0/lib/fog/cloudstack.rb: ##digest = OpenSSL::Digest::Digest.new('sha1')
~/.gem/ruby/2.1.0/gems/fog-1.15.0/lib/fog/core/hmac.rb: #digest = OpenSSL::Digest::Digest.new('sha1')
~/.gem/ruby/2.1.0/gems/fog-1.15.0/lib/fog/core/hmac.rb: #digest = OpenSSL::Digest::Digest.new('sha256')
# update appropriate gems (in my case fog)
gem install fog
bundle update fog
Also make sure you aren't locked on a gem version in your Gemfile.
# change
gem 'fog', '~> 1.15.0'
# to
gem 'fog', '~> 1.0'
# or omit the version if you are a cowboy/girl
Use OpenSSL::Digest instead of deprecated OpenSSL::Digest::Digest

how do you activate or set the default rake?

I have seen many
You have already activated rake 0.9.x, but your Gemfile requires rake 0.x.x
errors.
Of course, they can be solved (temporarily or always) by some methods like the following.
bundle exec rake
The method above works but you always have to type bundle exec.
It can also be solved by
bundle update
But bundle update also updates your other gems.
Some say it can be solved by
gem uninstall unwanted_rake_version
Yes, the unwanted rake can be installed but it is still marked as activated thus, still giving the error.
One solution would be to explicitly specify the rake version in your Gemfile but, that is not the question. It is on how to set the default rake version, or activate that specific version in rvm or other types of ruby installations?
The newer versions of rake can be activated by supplying an optional first argument, that is the gem version.
$ rake 0.9.2
Alternatively, if you have an older version of rake you can update the rake script manually to include this parameter (or specify any specific version you want).
The rake script usually lives in /usr/bin/rake (or ~/.rvm/gems/ruby-#{ruby-name}/rake if using rvm). And dictates the version of them gem to load before parsing paramaters.
It looks like this on my system.
$ cat ~/.rvm/gems/ruby-1.9.2-p180/bin/rake
#!/home/tomcat/.rvm/rubies/ruby-1.9.2-p180/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'rake' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem 'rake', version
load Gem.bin_path('rake', 'rake', version)
The important bit is gem 'rake', version changing version will force rake to a specific version system/rvm wide.
For more info, Katz' article explains nicely how binaries run under rubygems
When I get that error, its usually a result of working between projects that depend on different versions of rake. An easy fix is
gem uninstall rake
And then in your project directory (assuming you're working with Bundler) simply
bundle
I always uninstall rake first, command like this:
gem uninstall rake -v=version
then install another version
gem install rake -v=version

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