Tuesday, April 29, 2008

Day 89: The Gemification

Buckle in, this is going to be a long one: I want Kuiper to be installable as a gem.

The biggest kick in the teeth along this road is that the Rubygem online book itself doesn't really tell you how to do it, only that you should "see the DeveloperGuide for the real meat". The problem in this case is that the Developer Guide does not, so far as I can tell, exist. Thus I give you this:

Roger's (comparatively) Pain-Free Gem Creation Tutorial (with a bit of Rakefile stuff thrown in)

As you might expect, it's weighted heavily toward my game's deployment needs. Still, it's somewhat useful.

The first step, because we want this to be an easily re-done process, is to create a Rakefile. This is pretty simple, because it's Ruby code itself. The first part of my Rakefile, I decided, would run the unit tests I love so very much. This does the trick:

require 'rake/runtest'

task :test do
Rake.run_tests 'tests/tests.rb'
end

"tests.rb" is a file that just requires all my other tests; rake knows to do the right thing.

Next, I made a rule to build my documentation:

require 'rake/rdoctask'

Rake::RDocTask.new do |rd|
rd.main = "lib/kuiper.rb"
rd.title = "Kuiper RDocs"
rd.rdoc_files.include("lib/**/*.rb")
end

Tasks like the test one are easy; I'd already known about run_tests. But what the frak is a Rake::RDocTask, and how did I know what to do with it? Here the Rake RDocs (coincidentally, the link above to 'Rakefile' takes you straight to the docs) were invaluable. Every special task is documented there with examples of how to use it.

Next, the big tomato. Creating the gem:

require 'rake'
require 'rake/gempackagetask'
require 'rubygems'

Rake::GemPackageTask.new(gem_spec) do |pkg|
pkg.need_tar_bz2 = true
pkg.need_zip = true
pkg.need_tar = true
end

You didn't really think it'd be that easy, did you?

Each gem relies on a 'spec' to tell it minor things like what to put in the gem and what that gem requires. That's where 'gem_spec' comes from in the example above. A reference to everything that goes in it can be found here and I relied on it extensively.

Here's what my spec looks like, broken down:

gem_spec = Gem::Specification.new do |s|

This creates the actual Specification object and starts a block to initialize the whole thing. We're going to do some heavy changes to that 's' variable!

s.name = "kuiper" # The name of our gem
s.author = "Roger Ostrander" # Me
s.email = "denor@users.sourceforge.net"
s.homepage = "http://llynmir.net/projects/kuiper/"
s.summary = "Top down 2D space action/rpg "+
"hybrid"
s.description = "Kuiper is a single player "+
"top-down 2D space RPG game, "+
"in the spirit of Escape "+
"Velocity and Tradewars."
# Do we have rdoc for this package?
s.has_rdoc = true

This is just some basic information about the game. Most of it's self-explanatory, I just want to say that 'summary' is what will show up as a result of the 'gem list' or 'gem search' commands. 'description' is basically a longer summary.

# Our version number is [major,minor,bug]
s.version = $KUIPER_VERSION.join(".")
# This specifically refers to the Rubygame
# gem. A .rpm or .deb will not, as of right
# now, do the trick.
s.add_dependency('rubygame', '>=3.0.0')

Version info - first comes ours (which I keep in a central place in one of my source files, to make it easy to change) and then those we rely on. Like the comment says, this specifically refers to other gems; when I installed my gem it complained because I hadn't installed the rubygame gem (even though Rubygame lives elsewhere on my system)

# The directory of the script to start the game
s.bindir = "bin"
# Scripts to install
s.executables << 'kuiper'

I'm making a game, not a library, so this is a little awkward. These lines basically tell the gem system that I'm installing a binary (in reality, the 'kuiper' part there is another ruby script that will start things up), and so to put that file in the gem bin directory. I then discovered that Ubuntu doesn't include this directory in the $PATH by default, for those wondering it's "/var/lib/gems/1.8/bin/"

s.require_paths = ["lib"]

This tells Rubygems where (else) it should look for files it's requiring. The standard is to put source files in "lib", so I've done that and indicated this fact.

s.rdoc_options << '--title' << 'Kuiper RDocs' <<
'--main' << 'lib/kuiper.rb'
s.test_file = 'tests/tests.rb'

This is me repeating myself; though we've already got rake tasks to do these, people can test gems on their own, and also get documentation for them (when I installed the gem I created, in fact, it automatically installed the documentation with it).

Finally,

s.files = FileList.new do |fl|
fl.include(
"{lib,ext,samples,doc,images,data}/**/*")
fl.exclude(/svn/)
end
end

These are all the files to be put into the gem! And with that, you can go to the command line and go 'rake gem'.

Tomorrow: Part 2

EDITED: Because blogger preview sucks at showing you what will get cut off by its borders.

No comments: