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.
Related
I am working on a new Ruby Gem. I am familiar with using Bundler to manage gems:
source "https://rubygems.org"
gemspec
gem 'rspec-rails'
I am familiar with specifying dependencies in a gemspec file:
Gem::Specification.new do |s|
# ...
s.add_dependency "rails", "~> 4.1.5"
end
The Gemfile that was generated mentions that I should move my dependency declarations from my Gemfile to my gemspec when I'm ready to release.
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
Why would I want to do this? Why should my gemspec care what gems I'm using in development? What purpose does development_dependency serve that Bundler doesn't already do for me?
To best answer your questions, we should first untangle the concepts of Bundler and Rubygems. I think a great explanation can be found here.
Why Would I Want to [move dependencies from Gemfile to .gemspec]?
Gemfile allows you to specify not only dependencies, but where the dependencies come from. This is useful when you are also working on the dependencies themselves and need to point to a Git repo (or something).
Once work on these dependencies is complete, Rubygem conventions dictate that you move the dependency declaration on these released gems into your .gemspec file. Adding a line gemspec tells Bundler to read from this conventional Rubygems location. If you are working on a gem, and you are not actively developing the gem's dependencies, then all dependencies should be declared in your .gemspec
Why should my gemspec care what gems I'm using in development?
From the docs for add_development_dependency:
Development dependencies aren't installed by default and aren't activated when a gem is required.
A popular example for this Rspec. You should generally declare Rspec as a development dependency for yourself, but not force everyone else to download it when they grab your gem.
Note that the comment you cited does not say "development dependencies", it says "dependencies that are in development". Dependencies that are in development commonly include cutting-edge versions of gems that you are installing directly from a Git repo. RubyGems cannot install gems from a Git repo; however, Bundler can. If you are installing cutting-edge versions of gems installed from a source that RubyGems cannot handle (such as a VCS repo), you should list them in your Gemfile instead of the .gemspec file.
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"]
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.
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.
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.