Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

build a ruby gem and conditionally specify dependencies

I am working on a gem that has needs to set dependencies conditionally when the gem is installed. I've done some digging around

and it looks like i'm not alone in this need.

Rubygems: How do I add platform-specific dependency?

this is a long thread

http://www.ruby-forum.com/topic/957999

The only way I can see to add dependencies to a gem is to use add_dependency method within a Gem::Specifiction block in a .gemspec file

Gem::Specification.new do |s|

  # ... standard setup stuff

  # conditionally set dependencies
  s.add_dependency "rb-inotify", "~> 0.8.8" if RUBY_PLATFORM =~ /linux/i
  s.add_dependency "rb-fsevent", "~> 0.4.3.1" if RUBY_PLATFORM =~ /darwin/i
  s.add_dependency "rb-fchange", "~> 0.0.5" if RUBY_PLATFORM =~ /mswin|mingw/i

end

Based on all of the docs and threads I found on the net, I would have expected that if you install the gem on

  • Linux, then, rb-inotify would be a dependency and auto-installed
  • Mac - rb-fsevent would be installed
  • Windows - rb-fchange would be installed

However, it seems that is not the case. The "if" statements within the block are evaluated at the time the gem is built and packaged. Therefore, if you build and package the gem on Linux, then, rb-inotify is added as a dependency, Mac, then, rb-fsevent, Windows - rb-fchange.

Still needing a solution, I dug around in the rubygems code and it seems the following is a broad stoke of what happens.

  • build all of your code for your gem: foo.gem
  • create a foo.gemspec file
  • build, package, and release the gem to a gem server such as rubygems.org
  • let everyone know
  • developers install it locally via: gem install foo
  • the foo.gem file is downloaded, unpacked, and installed. all dependencies are installed as well.
  • everything should be set and we can beging using the gem.

It seems that when the gem is built and released the foo.gemspec file is loaded and the Gem::Specification block is evaluated and converted to YAML, compressed as metadata.gz, and included in foo.gem. The ruby code is compressed into data.tar.gz and included as well. When the gem is installed on the local developer machine, the YAML is extracted from metadata.gz and converted back into a Gem::Specification block, however, it is not converted back to the original block.

instead, you will see something like the following:

Gem::Specification.new do |s|

  if s.respond_to? :specification_version then
    s.specification_version = 3

    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
      s.add_runtime_dependency(%q<rb-inotify>, ["~> 0.8.8"])
    else
      s.add_dependency(%q<rb-inotify>, ["~> 0.8.8"])
    end
  else
    s.add_dependency(%q<rb-inotify>, ["~> 0.8.8"])
  end

end

Ok. So, I have a bird's eye view of the process, however, that does not change my desire to build a single gem and conditionally specify dependencies for a range of OS targets.

If anyone has a solution other than building multiple .gemspec files for each target OS... I'm all ears!!

like image 607
worthspending Avatar asked Jan 20 '12 10:01

worthspending


2 Answers

I have also stumbled upon this problem in the past. The only workaround I could find was to create a Rake task for installing the dependencies. Of course, at that stage, you might just want to let the user figure out on his own which gem he is missing based on the error message he is receiving. In my case, there were several platform-dependent dependencies to be installed, so that wasn't an option.

Rakefile:

task :install do |t|
  require './lib/library/installer'
  Library::Installer.install
end

Installer:

module Library::Installer

  require 'rubygems/dependency_installer'

  def self.install
    installer = Gem::DependencyInstaller.new
    dependency = case RUBY_PLATFORM
      when /darwin/i then ["rb-fsevent", "~> 0.4.3.1"]
      when /linux/i then ["rb-inotify", "~> 0.8.8"]
      when /mswin|mingw/i then ["rb-fchange", "~> 0.0.5"]
    end
    installer.install(*dependency)        
end

Then, the user can use rake install to get install appropriate dependencies.

Conditional dependency install (not just based on platform, but based on user input, for example) is cruelly missing to RubyGems. Let's hope it'll get implemented in the future!

like image 134
user2398029 Avatar answered Nov 04 '22 11:11

user2398029


i have never done this myself, but there are some gems that are available in platform specific versions: http://rubygems.org/gems/libv8/versions

from what i understand it's just a naming thing, which can be configured by setting the platform option of your gemspec. have a look at the doc: http://guides.rubygems.org/specification-reference/#platform=

like image 40
phoet Avatar answered Nov 04 '22 10:11

phoet