Gem development with Bundler: include or exclude Gemfile? - ruby

I'm developing a gem locally. It's a command-line utility that only has test dependencies, and my Gemfile looks like this:
source :rubygems
gemspec
group :test do
gem "cucumber"
gem "aruba"
gem "rspec"
end
My gemspec looks like this:
Gem::Specification.new do |s|
# authorship stuff...
s.files = `git ls-files`.split("\n")
end
That's the default gemspec created by Bundler. I know we're supposed to keep Gemfile and Gemfile.lock in source control, but I'm wondering about including them in the packaged gem through the Gem::Specification#files attribute. Are there arguments for/against including Gemfile and Gemfile.lock in the distributed gem? It seems weird or at least unnecessary to me.

Yehuda Katz just blogged on this topic! : Clarifying the Roles of the .gemspec and Gemfile

Related

Optional runtime dependency for Ruby gem with executables

I'm writing a gem aipp which exposes a few executables, nothing fancy here:
#!/usr/bin/env ruby
require 'aipp'
AIPP::NOTAM::Executable.new(File.basename($0)).run
Parts of the gem optionally use database adapters (pg or ruby-mysql gem). But since these can be a pain in the butt when the gem is used on the cloud, I'd like to really make them optional and not require them as runtime dependency in the .gemspec.
So I require them conditionally at runtime:
require 'pg' if ENV['AIPP_POSTGRESQL_URL']
require 'mysql' if ENV['AIPP_MYSQL_URL']
Unfortunately, that doesn't work as expected. When either of the environment variables is set and the executable is used, the require fails – probably because there's no dependency declared.
I've tried an inline Gemfile on the executable like the following. Works in development (repo checkout), but not when the gem is intalled via Rubygems:
#!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'aipp'
gem 'pg', '~> 1' if ENV['AIPP_POSTGRESQL_URL']
gem 'ruby-mysql', '~> 3' if ENV['AIPP_MYSQL_URL']
end
AIPP::NOTAM::Executable.new(File.basename($0)).run
What's the correct approach to require gems which are not listed as runtime dependency but installed additionally (via gem install or Gemfile)?
Maybe somebody knows of an existing gem which has already solved this problem somehow.
Thanks for your help!
I would use bundler groups for that.
gemfile do
source 'https://rubygems.org'
gem 'aipp'
group :database do
gem 'pg', '~> 1'
gem 'ruby-mysql'
end
end
There is more info about how to use them in the link.

How can I install a gem (via bundler using gemspec) before parsing the gemspec?

I have a gem that exists for the purpose of helping with versioning. It's useful to have this gem available when defining the version in the gemspec file.
The problem, however, is that running bundle install first causes the gemspec to be parsed, which results in an error because the required gem isn't installed yet.
I can get around it by running gem install <other_gem> before bundle install, but I'd much prefer bundler manage it, especially when taking into account that I'm using a custom gem server.
I've tried adding the gem to the Gemfile directly before the gemspec line, but no luck.
Gemfile:
source 'https://my.gemserver.com/gems'
gemspec
mygem.gemspec:
require 'external/dependency'
Gem::Specification.new do |spec|
spec.name = 'mygem'
spec.version = External::Dependency.version_helper
....
spec.add_development_dependency 'external-dependency'
end
EDIT:
Another workaround is to rescue the LoadError and specify a default version if the dependency isn't loaded. Also, not ideal
begin
require 'external/dependency'
rescue LoadError; end
Gem::Specification.new do |spec|
spec.name = 'mygem'
spec.version = defined?(External::Dependency) ? External::Dependency.version_helper : ''
....
spec.add_development_dependency 'external-dependency'
end
I think you're stuck with gem install. But I would solve this by adding that step to the Dockerfile I use for the project.
Maybe it's possible to do something like this using rbenv or rvm? Haven't used either of those since migrating to Docker, but rvm gemset is kind of a bootstrap...
I got around it by making the gemspec install the gem during a bundle update or install.
EXTERNAL_DEPENDENCY = Gem::Dependency.new('external-dependency', '~> 0.1')
if File.basename($0) == 'bundle' && ARGV.include?('update') || ARGV.include?('install')
require 'rubygems/dependency_installer'
Gem::DependencyInstaller.new.install(EXTERNAL_DEPENDENCY)
end
and then...
spec.add_development_dependency EXTERNAL_DEPENDENCY.name, EXTERNAL_DEPENDENCY.requirements_list

How can I avoid bundlers warning about multiple sources when I have all gems in my .gemspec?

In my own gem, I have a Gemfile that looks basically like this:
source 'https://my.gemserver.com'
source 'https://rubygems.org'
gemspec
My .gemspec has all dependencies listed as add_dependency and add_development_dependency.
As of Bundler 1.8, I get the warning:
Warning: this Gemfile contains multiple primary sources. Using `source` more than
once without a block is a security risk, and may result in installing unexpected gems.
To resolve this warning, use a block to indicate which gems should come from the
secondary source. To upgrade this warning to an error,
run `bundle config disable_multisource true`.
Is there a way to resolve this warning (without muting via bundle config)? I cannot find anything about a source option in the Rubygems specification.
No, you'll either need to mute the warning or add the source block to your Gemfile with the specific gems you want to come from your private server. There isn't a need to duplicate the ones that come from rubygems.org (or you could do it the other way around, if you depend on more private gems than public ones, and your private gems do not themselves depend on public ones).
The problem is that the gemspec format has no support for specifying the source for each gem, so without duplicating them into the Gemfile, there is no way to specify which gems come from each source.
Kind of sad, but one has to move it out to Gemfile :-(
Gemfile:
source 'https://my.gemserver.com' do
your_gem1
your_gem2
#...
end
source 'https://rubygems.org'
gemspec
but then, if some of your gems should be included in :development or :test group, following could be used
Gemfile:
your_gem1, :source => 'https://my.gemserver.com'
#...
group :development do
your_gem2, :source => 'https://my.gemserver.com'
#...
end
source 'https://rubygems.org'
gemspec
To elaborate on the discussion on the bundler issue, as previous answers have stated, you must include the gem in you Gemfile. However, you only need to specify the version of the gem in your .gemspec. If you change versions more often than private dependencies this isn't a terrible solution.
Reference the gem without version in Gemfile:
# Gemfile
source 'https://rubygems.org'
source 'https://xxx#gem.fury.io/me/' do
gem 'my-private-dependency'
end
gemspec
Reference the gem with version specification in the .gemspec:
# my-gem.gemspec
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Gem::Specification.new do |spec|
spec.add_dependency 'my-private-dependency', '~> 0.1.5'
end

Dependency issues with Rubygems

I created a gem that has some dependency. I published that gem on Rubygems.org. My gem gets installed on other system, but the gem package is not located. I figured out that this is likely due to my gem dependency not getting installed. Do I need to do any specific thing to get all my dependency installed?
Looking at existing gems I see these components: Gemfile, Gemfile.lock, and whatever.gemspec. Note the gem name is lowercase to match the gemspec file name (I think this was a problem for me before)
Gemfile
source 'https://rubygems.org'
# Specify your gem's dependencies in whatever.gemspec
gemspec
whatever.gemspec
Gem::Specification.new do |spec|
spec.name = "whatever"
spec.add_dependency "hashie"
end

Bundler's :require option in a gemfile?

How would one go about using Bundler's :require=> in a gemspec? For instance, I would like to only use sinatra/base as a runtime dependency. This works in a gemfile:
gem 'sinatra', :require => 'sinatra/base'
but does not work in the gemspec (even when bundler is required in the gemspec):
s.add_runtime_dependency 'sintatra', :require => 'sinatra/base'
The error is "Illformed requirement [{:require=>"sinatra/base"}]"
Anybody else find themselves in a similar situation?
In the gemspec file you can specify the gem's dependencies only. This file doesn't do require for you. You should require dependencies in the ruby code.
On the other hand you can create a Gemfile to help you in the development environment. But the Gemfile is not loaded when you are using the gem on other projects.

Resources