Ruby gem - Requires depending on Runtime Options - ruby

I'm developing a ruby gem, and my requires are a mess.
The gem is a web Scraper, which depending on the given options, uses different methods to access the web, and thus needs to require different gems. Some users may never need some gems, or any of them.
My question is, what do I put in my .gemspec:
s.add_runtime_dependency #do I require all the gems here?
Where do I actually require the necessary gems in my code, and how do I do testing?
I don't know the conventions on this. Thanks.
*code: https://github.com/ZirconCode/Scrapah

RubyGems provides two main “types” of dependencies: runtime and development.
Runtime dependencies are what your gem needs to work (such as rails needing activesupport).
Development dependencies are useful for when someone wants to make modifications to your gem. When you specify development dependencies, another developer can run gem install --dev your_gem and RubyGems will grab both sets of dependencies (runtime and development). Typical development dependencies include test frameworks and build systems.
So in Gem Specification file you can add those gems that your gems need to work
Gem::Specification.new do |s|
s.name = "gem name"
s.version = "2.0.0"
s.add_runtime_dependency "daemons",
["= 1.1.0"]
s.add_development_dependency "bourne",
[">= 0"]

Related

Should gem's development dependencies restate required runtime dependencies?

I can not find any documentation on this. My intuition was that add_development_dependency from a gemspec file should list only additional dependencies that are needed for development and testing and can rely on dependencies specified with add_runtime_dependency to be installed.
I was surprised to discover that gem test command installs only development dependencies, and fails if any runtime dependency is needed during tests.
Is it only gem test quirk or should development dependencies always restate required runtime dependencies, like below:
Gem::Specification.new do |s|
# ...
s.add_runtime_dependency 'rack'
s.add_runtime_dependency 'net-http-persistent'
s.add_development_dependency 'rack-test'
s.add_development_dependency 'webmock'
s.add_development_dependency 'rack'
s.add_development_dependency 'net-http-persistent'
end
?
I think gem test assumes that you have installed the gem you want to test before you actually try to test it.
gem install whatever
gem test whatever
All the tests would indicate that this is the case - they call install_stub_gem first, which installs a fake gem to run the gem test commands against.
I've never seen a gem duplicate all its runtime dependencies as development dependencies, and the language of the docs suggests that it wouldn't make sense to do so:
development dependencies
Gems that are used for development purposes only. (emphasis mine)
Gems that are also runtime dependencies would therefore not fit into this category.

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!!
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!
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=
I have looked into this as well and have come to the conclusion that is not possible by design. Having a single 'mega gem' for all platforms causes the problem of not knowing if a platform is supported until the gem is downloaded and installed. A Gem would have to be smart enough to determine what is correct way to install depending on the platform. If a platform is not supported at all, the gem may fail horribly, opening a big can of worms. There use to be a callback after a gem was installed that was removed for the same reason, no magic to get a gem to install correctly. Some people have hacked around this using mkmf, but I suggest following the worn path of a gem per platform as the better solution.
Based on this, in a project that builds a gem for ruby and jruby, I have to manually create each gem and upload them to RubyGem. Using Jeweler this is as simple as specifing the Gemfile, but I have to rebuild the gem spec each time I package a gem. Fairly trivial when supporting only 2 platforms, but the build process is straight forward enough that it could be automated to provide support multiple platform gems.

Why should I add development dependencies to my gemspec

It seems like a strange feature that rubygems wants to know what my development dependencies are, so far I never saw it used to actually load these when running the gem tests.
If someone decides to hack your gem for whatever reason, e.g. they fork it on Github to add a feature to it (which they might want to contribute), it helps if they know what development dependencies your gem needs (e.g. testing frameworks, mocking tools etc.).
If you're using bundler with the gemspec command, it will hook into your gemspec dependencies and install the development dependencies along with the runtime ones when you run
bundle install
This saves you having to install these gems manually.
The gem command can also list all the dependencies of a gem including the development ones:
gem dependency my_gem
Gem my_gem-0.1.3
activerecord (~> 3.0.0)
json (~> 1.4.3, development)
rake (>= 0, development)
rspec (~> 2.5.0, development)
ruby-openid (~> 2.1.0)
Once again this is probably more for other people rather than for yourself.
My gems have normally unit-test. This test requires sometimes gems, which are not needed to use the gem. Or perhaps you need additional gems to generate parts of the gem.
Once I misused development dependencies to define 'optional dependecies' (dependecies were necessary for some specific features of my gem, but not needed for the 'normal' usage).
Example:
My application offered the posibility to export to a file as text or pdf. The pdf-generation uses prawn. So prawn is a dependecy - but it is not necessary to use the application, only a specific feature needs it.
So I didn't add prawn to gem dependencies (it is not necessary), but to the development dependencies (it is usefull for the gem).
The documented answer to this question at RubyGems is as follows:
Development dependencies are useful for when someone wants to make
modifications to your gem. When you specify development dependencies,
another developer can run gem install --dev your_gem and RubyGems will
grab both sets of dependencies (runtime and development). Typical
development dependencies include test frameworks and build systems.
For example, I may specify spec.add_development_dependency 'minitest'
Adding the --dev flag to the gem install command would then pull in the minitest gem automatically. This feature saves another developer from manually having to identify and install the gems necessary for development.

Where do I have to do my Bundler setup?

If I'm developing a gem using Bundler and RSpec for testing. Where do I do my Bundler.setup? Let's assume my gem is called fancy-gem and my directory setup is similar to the following:
Gemfile
Gemfile.lock
lib/
fancy-gem.rb
Rakefile
README
spec/
...
Should I execute Bundler.setup in my 'fancy-gem.rb' or does this cause problems with other gems which might use bundler? I'm thinking, when I'm not doing this, then there is no way to guarantee that the right version of the third party libraries I'm requiring is loaded.
I already asked, if I need to add Bundler itself to the Gemfile. The answer was no, but now I'm not so sure, because if I do execute Bundler.setup somewhere then Bundler actually is a dependency of my Gem and should be installed along with my Gem when it is downloaded from rubygems.org
In my opinion you should neither depend on bundler, nor use it in your gem. The way I'd do it is simply require your gem's dependencies in lib/fancy-gem.rb (almost every gem only has a handful of runtime dependencies, so this should not be too much of a hassle) and I'd call Bundler.setup only in the development files (like spec_helper.rb or Rakefile). This way you don't screw with applications that use your gem and still get all the convenience of automatic dependency management when developing your gem.

Writing a Jeweler Rakefile that adds dependencies depending on RUBY_ENGINE (ruby or jruby)

I have a Rakefile that includes this:
Jeweler::Tasks.new do |gem|
# ...
gem.add_dependency('json')
end
The gemspec that this generates builds a gem that can't be installed on jruby because the 'json' gem is native.
For jruby, this would have to be:
Jeweler::Tasks.new do |gem|
# ...
gem.add_dependency('json-jruby')
end
How do I conditionally add the dependency for 'json-jruby' when RUBY_ENGINE == 'java'?
It seems like my only option is to manually edit the gemspec file that jeweler generates to add the RUBY_ENGINE check. But I'd rather avoid this, since it kind of defeats the purpose of using jeweler in the first place.
Any ideas?
After some more digging around, I've come to the conclusion that the problem here isn't so much the limitation in Jeweler as it is a problem with how the json-jruby gem is named.
In a perfect world, the json-jruby gem should be named just json. That way when the gem file is built under JRuby (i.e. jgem build json-jruby.gem), the resulting gem file would be named json-1.4.3-universal-java-1.6.gem (RubyGems names the file based on the 'platform' property in the gemspec). When installed with the appropriate platform in the gem name, everything works transparently. There's no need to explicitly require json-jruby. RubyGems uses the appropriate version automatically.
I've contacted the maintainer of the json gem to see if he'd be willing to get json-1.4.3-universal-java-1.6.gem added to his gemcutter repo, but in the meantime you can download the drop-in jruby json gem here: http://roughest.net/json-1.4.3-universal-java-1.6.gem

Resources