← Home

Release your Gems with ease

After the recent, somewhat heated discussion about whether or not to check in gemspecs to a repository, manually crafting versus autogenerating them, using a dynamic piece of code to collect relevant files versus maintaining a static files list in the gemspec file ... I've felt motivated to polish my own gem plugin that I've been using to publish my gems and finally add some of the functionality that I've been missing myself.

In short, I'm a happy resident in the camp of manually maintained gemspecs - as long as I have a dynamic snippet of Ruby code collecting my files for me. Just check in a well done gemspec and you're good to go. (If you're interested in more explanation then see below.)

So, here's my rubygems gem-release plugin.

It's pretty simple, could as easily be done in Rake (I don't like Rake though) or Thor and maybe only suits my own needs well. In fact, if you don't publish any gems that often or aren't familiar with the process I'd probably recommend doing all of this manually. Also, obviously there are other, mostly Rake based, tools like Jeweler, rakegem or even Hoe. And if your happy with Textmate snippets, then Mislav has one for you that generates a gemspec.

The gem-release plugin adds four commands to the gem command:

$ gem bootstrap       # bootstraps a new gem repository
$ gem gemspec         # generates a gemspec file with sane defaults
$ gem tag             # creates a git tag and pushes it to the origin repository
$ gem release         # builds the gem and pushes it to rubygems.org

For more a detailled explanation of these commands and their options see the README.

The bootstrap command can optionally invoke the gemspec command. And the release command can optionally invoke the tag command. So this is what my workflow looks like:

$ mkdir foo-bar; cd foo-bar
$ gem bootstrap --scaffold --github
# code away
# check the generated gemspec file and add/edit stuff
$ git add/commit/push
$ gem release --tag

# come back later ...
# code away, manually bump version in lib/foo_bar/version.rb
$ git add/commit/push
$ gem release --tag

The tool assumes that you're following the good practice of having a lib/[name]/version.rb that defines [Name]::VERSION (and it will add that file for you if you use the --scaffold option on gem bootstrap).

It also assumes that you want a git-based strategy for dynamically collecting files in your gemspec (i.e. `git ls-files {app,lib}`.split("\n")). If you pass the option --strategy glob then you get a glob instead (i.e. Dir['{lib/**/*,[A-Z]*}']). Obviously you're free to change that in any way you want (and I'll happily accept pull requests for different strategies).

Also, the tool currently assumes that you have a bunch of config variables defined in your global Git setup. I.e. it will populate:

authors  from `git config --get user.name`
email    from `git config --get user.email`
homepage from `git config --get github.user` (http://github.com/[github.user]/[gem_name])

And obviously it uses `git config --get github.user` and `git config --get github.token` to create a repository on Github when you pass the --github option to gem bootstrap.

Enjoy :)


PS:

In case your interested in this discussion at all here's my point of view.

I don't want to manually maintain a list of files. I'm a slacker, so that's just to cumbersome and errorprone to me. But I don't really want to use an external tool for maintenance (like Jeweler, which I've been using a lot before) as I tend to forget updating the gemspec before I push.

Also, given the assumption that it is ok to have dynamic code in the gemspec I don't think it makes a lot of sense to keep gem meta information in a Rake file instead of the gemspec (like authors, email, homepage ...). And I'm definitely not concerned with any left over swap files, .svn directories or other rubbish making their way into my gems because I just don't have them in the relevant directories.

So this is one of the rare cases where I think code generation is a good thing. I'll just have a tool to generate my gemspec once and then I can manually maintain it in case I have to add a dependency or something.

I can't see why I shouldn't be checking the file in to my repository this way either. It's not autogenerated in the sense that I'd use some external tool to rebuild it from a git pre-commit hook or something. Instead it's scaffolded just as any Rails app is.

One thing I find nice about this approach is that my stuff works without any dependencies. People who check out the repository will be able to just do gem build; gem install and they're done. The fact that I'm using a dynamic piece of code to collect the files means that I don't need to worry about the file being in sync with recent changes to the codebase.

blog comments powered by Disqus