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:
- Checkout the master branch
- Update the master branch from origin
- Create a new branch called 'gem' to work in
- Add a configuration for jeweler to the Rakefile
- Add a VERSION file based on the version used in
init.rb - Move the existing
init.rbtorails/init.rbso Rails will load it from the gem - Generate a valid gemspec for the plugin
- 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.
#!/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 =<<-EORAKE
begin
require 'jeweler'
Jeweler::Tasks.new do |s|
s.name = "#{@plugin_name}"
s.summary = "#{description}"
s.email = "edavis@littlestreamsoftware.com"
s.homepage = "https://projects.littlestreamsoftware.com/projects/TODO"
s.description = "#{description}"
s.authors = ["Eric Davis"]
s.rubyforge_project = "#{@plugin_name}" # TODO
s.files = FileList[
"[A-Z]*",
"init.rb",
"rails/init.rb",
"{bin,generators,lib,test,app,assets,config,lang}/**/*",
'lib/jeweler/templates/.gitignore'
]
end
Jeweler::GemcutterTasks.new
Jeweler::RubyforgeTasks.new do |rubyforge|
rubyforge.doc_task = "rdoc"
end
rescue LoadError
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
end
EORAKE
end
FileUtils.cd(@plugin_dir, :verbose => 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
