Converting a Redmine plugin to a Gem

A lot of Rails plugins are starting to be released as RubyGems. I’ve found using RubyGems to manage plugins has been easy and have several advantages over a standard Rails plugin:

  • built in versioning
  • easy installation – no more installing plugins to the wrong directory
  • pick a specific version to use
  • not having to add a bunch of code to the application tree

Since I’ve created several dozen plugins for Redmine, I decided to start releasing them as RubyGems. I hoping this will make it easier for the user to install and upgrade them. After spending a night converting four plugins, I found I was running the same commands again and again. That meant, I could script it for the remaining 29 plugins.

The process

After a few tweaks, I ended up with a simple procedural script that would:

  1. Checkout the master branch
  2. Update the master branch from origin
  3. Create a new branch called ‘gem’ to work in
  4. Add a configuration for jeweler to the Rakefile
  5. Add a VERSION file based on the version used in init.rb
  6. Move the existing init.rb to rails/init.rb so Rails will load it from the gem
  7. Generate a valid gemspec for the plugin
  8. Install the new gem

Finally, I would review the ‘gem’ branch and test out the newly minted gem in Redmine. If something went wrong, the entire ‘gem’ branch could be destroyed with git branch -D gem. This let me finish converting the rest of the plugins in just a couple of hours.

The script

This script is still Redmine specific (how it parses the version and description) but it could easily be adapted to work with any Rails plugin. I’ve also shared this as a Gist, so feel free to fork and modify it there. Let me know if you used it and how it worked for you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env ruby
# Usage:
#  ruby plugin_to_gem.rb my_plugin_directory
require 'fileutils'
 
@plugin_dir = ARGV[0]
@plugin_name = ARGV[0]
 
def rakefile_content
  description = 'TODO'
  redmine_init_content = File.read('init.rb')
  if redmine_init_content.match(/description (.*$)/)
    description = $1.gsub("'",'').gsub('"','')
  end
 
  content =< true) do |dir|
  system('rake clean')
  system('git status')
  system('git checkout master')
  system('git merge origin/master')
  system('git checkout -b gem')
 
  # Rakefile
  File.open('Rakefile','a') do |file|
    file.puts(rakefile_content)
  end
  system('git commit -am "Updated rakefile for jeweler"')
 
  # VERSION
  File.open('VERSION','w') do |version_file|
    redmine_init_content = File.read('init.rb')
    if redmine_init_content.match(/version (.*$)/)
      version = $1.gsub("'",'').gsub('"','')
      version_file.puts version
    end
  end
  system('git add VERSION')
  system('git commit -am "Added Version file"')
 
  # Rails GemPlugin init.rb
  FileUtils.mkdir_p('rails')
  system('git mv init.rb rails/init.rb')
  File.open('init.rb','w') do |init_file|
    init_file.puts('require File.dirname(__FILE__) + "/rails/init"')
  end
  system('git add init.rb')
  system('git commit -am "Added init file for Rails GemPlugin"')
 
 
 
  # Gemspec
  system('rake gemspec')
  system("git add #{@plugin_name}.gemspec")
  system('git commit -am "Added generated gemspec"')
 
  # Install to test
  system('rake install')
 
  # Back to master to allow merging
  system('git checkout master')
end

Eric Davis