Showing posts with label rubygem. Show all posts
Showing posts with label rubygem. Show all posts

Wednesday, April 30, 2008

Day 90: Gemification 2, the revenge

Sinuses... stuffed! Laptop... dying! Must... complete... article... on... gems!

(Part 1)

So I've got a gem. 'gem install kuiper-0.0.1.gem' went well. I even added /var/lib/gems/1.8/bin/ to my $PATH so I could run the script. I started up a new terminal and did this:

roger@roger-laptop:~$ kuiper
roger@roger-laptop:~$

Gwuh? Now this was an unexpected result! Obviously it's finding the file, but running it had no effect. I tracked this down to a Ruby idiom we've all seen before:

if $0 == __FILE__
start_kuiper
end

But this doesn't work with gems! Rubygems doesn't actually install your script in /var/lib/gems/1.8/bin/, they install their own script there, which itself calls yours. Luckily, Rubygems names this script after the one it's calling, so you can modify the usual idiom as so:

if File.basename($0) ==
File.basename(__FILE__)

start_kuiper
end

Now when I ran the game, I got what I was expecting, which is to say a giant list of tracebacks because it couldn't find the resources it needed. (Put in a bug to fix that gracefully: #121) Locating your data files turns out to be relatively simple:

begin
require 'rubygems'
Gem.activate('kuiper',false)
rl.dirs << Gem.loaded_specs['kuiper'].
full_gem_path
rescue LoadError
# Do something else
end

The begin/rescue part in there serves two reasons: Obviously, it'll catch the case where Rubygems isn't loaded. Less obviously, Gem.activate will also throw a LoadError if it can't find its gem. The Gem.activate bit looks for a gem by the name you give it, and 'activates' it - so far as I can tell, this is the same as requiring it via a 'gem' command (which I'll show later). This step is necessary to populate Gem.loaded_specs with our gem's spec. Finally, full_gem_path is - like it sounds - a full path to where the gem installed. In my case, that's:

ls /var/lib/gems/1.8/gems/kuiper-0.0.1/
bin data images lib tests

These directories are all the stuff I told the spec to include (via spec.files) - which includes my data files.

Finally, a bit on interacting with other gems. I made my gem depend on the rubygame gem being installed - now how do I go about letting my program require it?

begin
require 'rubygems'
gem 'rubygame', '>=3.0.0'
rescue LoadError
# Nope, either no gems or no rubygame
end

require 'rubygame'

Like before, the begin/end block there will make sure I have both gems and rubygame installed. Here instead of using the Gem API directly, I use a convenience function to see if Rubygame is there. The require line is outside of the begin/end so that, if we don't have gems and/or don't have the rubygame gem, we can still run so long as the user has rubygame installed somewhere on their system.

And that, as they say, is that. I'm off to drown my troubles in pseudoephedrine!

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.