P O S T M O D E R N

RubyGems Tasks

gemspec, rubygems, tasks

Warning: Controversial Content

If you are morally or ethically opposed to building/releasing Ruby Gems with rake, this blog post may anger you. If this is the case, you are advised to STOP READING and CLICK THE BACK BUTTON.

Ever since we could release Ruby Gems, we have had Gem helpers that could generate new projects and provided Rake tasks to automate the building/publishing of Gems. The most popular of these Gem helpers were Hoe and Jeweler. Both Hoe and Jeweler required a certain project layout and imposed a certain workflow onto the developer.

Sometime before the introduction of Bundler, Yehuda Katz proposed a radically simpler way of building Gems, using the gemspec file. Developers could use the built-in gem build command to build a .gem file from a .gemspec, and use the gem push command to publish the Gem to rubygems.org. This marked the start of an exodus of sorts, away from using Gem helpers such as Hoe and Jeweler.

All you need is a Gemspec?

With the advent of building gems from a .gemspec, a vocal minority formed within the Ruby community. They proclaimed that all one needs is a gemspec, and that all other tools (Hoe, Jeweler, Bundler and even Rake) are now obsolete!

Let us take a look at how they might release a Gem:

$ git status
$ git push
$ gem build my_project.gemspec
$ gem push my_project-1.2.3.gem
$ git tag 1.2.3
$ git push --tags

It takes roughly six commands to release a Gem to rubygems.org. A developer must remember to run each of these six commands, in order, every time they release a new version of their Gem. The possibility for human error increases.

In fact, I have found Gems that contained newer source-code than their Git repository; because the developer forgot to commit or push the changes before pushing the Gem. I have also found Git repositories with no tags, making it difficult to review what exactly changed between versions.

Now, let us see how developers release gems using Rake tasks:

$ rake release

That’s it! That one command will run each of the above six commands. If any of the commands fail, the release process will halt. If there are uncommitted changes, the release process will halt. The possibility for human error has been greatly minimized.

Alternatives

Having used Hoe, Jeweler and then Bundler, I missed some of the workflow provided by these Gem helpers. So I began searching for lightweight alternatives.

I looked at Gem::PackageTask, MG, gem release and even bundler/gem_helper. Unfortunately they all were missing various features. Gem::PackageTask only built the .gem file, but did not check git status, tag the release or push the .gem. MG also did not check git status before releasing, did not tag releases, and only supports Git. gem release came close, but did not check git status before releasing, deleted the .gem file, only supports Git and tried to do too much (auto-magically bumping the version of your project for you). bundler/gem_helper came the closest, although while it defined Rake tasks it did not leverage Rake’s powerful task => dependency system.

So I decided to cherry-pick all of the nice features from Hoe, Jeweler, MG, gem release and bundler/gem_helper, and leave out the opinionated features.

rubygems-tasks

rubygems-tasks provides agnostic and unobtrusive Rake tasks for building, installing and releasing Ruby Gems.

$ gem install rubygems-tasks

Adding rubygems-tasks to an existing project is easy as:

require 'rubygems/tasks'
Gem::Tasks.new

$ rake -T
rake build    # Builds all packages
rake console  # Spawns an Interactive Ruby Console
rake install  # Installs all built gem packages
rake release  # Performs a release

Features

  • Provides tasks to build, install and push gems to rubygems.org:
    • Loads all project metadata from the .gemspec file.
    • Supports loading multiple .gemspec files.
    • Supports pushing gems to alternate Gemcutter servers.
  • Supports optionally building .tar.gz and .zip archives.
  • Supports Git, Mercurial and SubVersion SCMs.
    • Supports creating PGP signed Git/Mercurial tags.
  • Supports generating checksums of built packages:

    Gem::Tasks.new(:sign => {:checksum => true})
    
  • Supports generating PGP signatures for built packages:

    Gem::Tasks.new(:sign => {:pgp => true})
    
  • Provides a console task, for jumping right into your code.
  • Defines task aliases for users coming from Jeweler or Hoe.
  • ANSI coloured messages!

Anti-Features

  • Does not parse project metadata from the README or the ChangeLog.
  • Does not generate or modify your code.
  • Does not automatically commit changes.
  • Does not inject dependencies into gems.
  • Zero dependencies.

Examples

Specifying an alternate Ruby Console to run:

Gem::Tasks.new do |tasks|
  tasks.console.command = 'pry'
end

Enable pushing gems to an in-house Gemcutter server:

Gem::Tasks.new do |tasks|
  tasks.push.host = 'gems.company.com'
end

Disable the push task:

Gem::Tasks.new(:push => false)

Enable building .tar.gz and .zip archives:

Gem::Tasks.new(:build => {:tar => true, :zip => true})

Enable Checksums and PGP signatures for built packages:

Gem::Tasks.new(:sign => {:checksum => true, :pgp => true})

Selectively defining tasks:

Gem::Build::Tar.new
Gem::SCM::Status.new
Gem::SCM::Tag.new(:format => 'REL-%s')
Gem::Sign::Checksum.new
Gem::Console.new