Using Gemfile gems inside .gemspec file

Introduction

If you have a (or plan to create new) gem, the chance is that you also have Gemfile inside your project along with .gemspec file. Here we have two different ways to describe dependencies of your ruby code. They don’t coexist well with each other and you have to do additional steps in order to let them work together.

According to bundler gem help, all you have to do is to create simple Gemfile with only one instruction (gemspec):

# Gemfile

gemspec

This instruction will look into corresponding .gemspec file (you should have it as part of your gem project). It will create default and development groups and fill them out according to usage of add_runtime_dependency and add_development_dependency calls.

So, if you have the following my_gem.gemspec file:

# my_gem.gemspec

spec.add_runtime_dependency "haml", [">= 0"]
spec.add_runtime_dependency "sass", [">= 0"]

spec.add_development_dependency "gemcutter", [">= 0"]

it will dynamically build something like this:

# Gemfile

group :default do
  gem "haml"
  gem "sass"
end

group :development do
  gem "gemcutter"
end

This approach has on drawback though - you have to think in terms of .gemspec file, which is counter-intuitive.

It would be nice if you can do it other way around. We keep all the dependencies in Gemfile as we would do it for any other ruby project. We also keep .gemspec file as ERB template, so we can generate resulting .gemspec when we need it or as part of gem release process.

Installation

Add this line to to your Gemfile:

gem "gemspec_deps_gen"

And then execute it:

bundle

Usage

Create Gemfile file:

# Gemfile

group :default do
  gem "haml"
  gem "sass"
end

group :development do
  gem "gemcutter"
end

Create my_gem.gemspec.erb file:

Gem::Specification.new do |spec|
  spec.name = "sample"
  spec.summary = %q{Summary.}
  spec.description = %q{Description.}
  spec.email = "alexander.shvets@gmail.com"
  spec.authors = ["Alexander Shvets"]
  spec.homepage = "http://github.com/shvets/sample"

  spec.files = `git ls-files`.split($\)
  spec.test_files =
    spec.files.grep(%r{^(test|spec|features)/})

  spec.version = "1.0.0"
  #<%= project_dependencies %>
end

You can see ERB fragment (project_dependencies) included inside the body of the specification.

Now, run this command:

$ gemspec_deps_gen my_gem.gemspec.erb my_gem.gemspec

It will generate my_gem.gemspec and replace ERB fragment with dependencies from Gemfile:

Gem::Specification.new do |spec|
  ...

  spec.add_runtime_dependency "haml", [">= 0"]
  spec.add_runtime_dependency "sass", [">= 0"]
  spec.add_development_dependency "gemcutter", [">= 0"]

end

If second parameter is missing, result will be outputted to console.

As alternative, you can call it as rake command:

require "gemspec_deps_gen"

project_name = 'my_gem'

task :gen do
  generator = GemspecDepsGen.new

  generator.generate_dependencies "spec", "#{project_name}.gemspec.erb", "#{project_name}.gemspec"
end

And run it:

rake gen

It will also generate my_gem.gemspec and replace ERB fragment with dependencies from Gemfile.