Ruby project howto

This section is a brief howto for automating some common tasks like running rdoc, unit tests and packaging.

We are assuming a project with the following directories and files:

    Rantfile
    README
    bin/
        wgrep
    lib/
        wgrep.rb
    test/
        tc_wgrep.rb

You can find this little project in test/project_rb1 directory of the Rant package. You‘ll probably have more files in your project, but we will try to write our Rantfile as generic as possible:

    import %w(rubytest rubydoc rubypackage clean)

    lib_files = sys["lib/**/*.rb"]
    dist_files = lib_files + sys["README", "rantfile.rb", "{test,bin}/*"]

    desc "Run unit tests."
    gen RubyTest do |t|
        t.test_dir = "test"
        t.pattern = "tc_*.rb"
    end

    desc "Generate html documentation."
    gen RubyDoc do |t|
        t.opts = %w(--title wgrep --main README README)
    end

    desc "Create packages."
    gen RubyPackage, "wgrep" do |t|
        t.version = "1.0.0"
        t.summary = "Simple grep program."
        t.files = dist_files
        t.bindir = "bin"
        t.executable = "wgrep"
        t.package_task
    end

    desc "Remove autogenerated and backup files."
    gen Clean
    var[:clean].include "doc", "pkg", "*~"

To verify how our tasks are named:

    % rant -T
    rant test     # Run unit tests.
    rant doc      # Generate html documentation.
    rant package  # Create packages.
    rant clean    # Remove autogenerated and backup files.

Let’s examine the code by running the tasks:

    % rant test
    cd test
    ruby -I /home/stefan/Ruby/lib/rant/test/project_rb1/lib -S testrb tc_wgrep.rb
    Loaded suite tc_wgrep.rb
    Started
    .
    Finished in 0.004588 seconds.

    1 tests, 2 assertions, 0 failures, 0 errors
    cd -

The RubyTest generator automatically added the lib directory to the LOAD_PATH for testing. Because we have set the test_dir to "test", it changes to the test directory, evaluates the pattern to collect the testcases and then runs the testrb command.

To format the documentation:

    % rant doc

                                 README:
                               wgrep.rb: mcc....
    Generating HTML...

    Files:   2
    Classes: 2
    Modules: 1
    Methods: 4
    Elapsed: 0.335s

All of this output originates from RDoc. The RubyDoc generator gives the lib directory and additionally the opts we have set to RDoc.

The most interesting task is the package task:

    % rant package
    mkdir pkg
    mkdir pkg/wgrep-1.0.0
    mkdir -p pkg/wgrep-1.0.0/lib
    mkdir -p pkg/wgrep-1.0.0/test
    mkdir -p pkg/wgrep-1.0.0/bin
    ln lib/wgrep.rb pkg/wgrep-1.0.0/lib/wgrep.rb
    ln rantfile.rb pkg/wgrep-1.0.0/rantfile.rb
    ln README pkg/wgrep-1.0.0/README
    ln test/text pkg/wgrep-1.0.0/test/text
    ln test/tc_wgrep.rb pkg/wgrep-1.0.0/test/tc_wgrep.rb
    ln bin/wgrep pkg/wgrep-1.0.0/bin/wgrep
    touch pkg/wgrep-1.0.0
    cd pkg
    tar zcf wgrep-1.0.0.tar.gz wgrep-1.0.0
    cd -
    cd pkg
    zip -yqr wgrep-1.0.0.zip wgrep-1.0.0
    cd -
      Successfully built RubyGem
      Name: wgrep
      Version: 1.0.0
      File: wgrep-1.0.0.gem
    mv wgrep-1.0.0.gem pkg

This is what it does:

  1. Link all package files to the new folder pkg/wgrep-1.0.0.
  2. Create a tar.gz file in the pkg directory.
  3. Create a .zip file in the pkg directory.
  4. Create a RubyGem in pkg.

The RubyGem will only be created if RubyGems is available on your system.

The gem specification also contains the RDoc options we have given to the RubyDoc generator and the has_rdoc attribute is set to true. No need to duplicate this information in our Rantfile.

And if we want to remove all autogenerated files (and backup files) we run the clean task:

    % rant clean
    rm -rf doc
    rm -rf pkg
    rm -f README~

More about the RubyPackage generator

The argument given to the RubyPackage generator is used as package name (here "wgrep"):

    gen RubyPackage, "wgrep" do |t|

The line which actually creates the package task(s) is this one:

    t.package_task

Optionally you may give it a task name as argument:

    t.package_task :release

Now you can run rant with the argument "release" for packaging:

    % rant release

If you want to change the package directory (which defaults to pkg) you can set the pkg_dir attribute:

    t.pkg_dir = "packages"
    t.package_task

Perhaps you want to specify explicetely what packages you want to build. To accomplish this, each of the following methods listed takes an optional task name as argument:

    t.tar_task          # create tar.gz archive, task name defaults to "tar"
    t.zip_task          # create zip archive, task name defaults to "zip"
    t.gem_task          # create gem, task name defaults to "gem"

An example would be:

    desc "Create tar.gz package."
    t.tar_task :archive
    desc "Create RubyGem."
    t.gem_task
    # Not required: Creates a shortcut for the previously defined
    # package tasks ("archive" and "gem")
    desc "Create packages."
    t.package_task

In our example we had only a few attributes for our package. Here are following two lists with the available attributes.

  • Attributes that take a single argument:
          name
          date
          description
          email
          has_rdoc
          homepage
          platform
          required_ruby_version
          rubyforge_project
          summary
          version
    
  • Attributes that take one or more values (if you give a single value it will automatically be converted to a list):
          author
          bindir
          executable
          extension
          files
          rdoc_options
          requires
          test_files
          test_suite
    

The attributes can also be set without the assignment operator:

    t.executable "mybin"

Additionally to all this attributes you can explicitely set an attribute for the gem specification by preceding it with gem_:

    t.gem_autorequire = "myproject"

More about the RubyDoc generator

If an argument is given to the RubyDoc generator, it will be used as task name. The output directory for the html files defaults to doc. To change this set the dir attribute:

    desc "Generate html documentation."
    gen RubyDoc, :html do |t|
        t.dir = "doc/html"

As rant invokes RDoc programmatically, no command is printed when the task is run. If you want to see one, set the verbose attribute to true:

        t.verbose = true

Installation

Since we get a RubyGem from the RubyPackage generator, our package is ready for distribution and installation. But probably you also want to provide a "normal" zip/tar archive. In this case you can use a conventional install.rb or setup.rb script for installation.

If we install the "wgrep" script on Windows, it won’t run out of the box, because Windows doesn’t know that it has to run it with the Ruby interpreter. This problem can be solved with Rant. Add the following lines to your Rantfile:

    import "win32/rubycmdwrapper"

    desc "Install wgrep."
    task :install do
        # Run setup.rb with the Ruby interpreter.
        sys.ruby "setup.rb"
    end

    if Env.on_windows?
        # Create .cmd files for all scripts in the bin/ directory and
        # make the install task dependent on them.
        enhance :install => (gen Win32::RubyCmdWrapper, sys["bin/*"])
    end

See also

Rant Overview:README
Writing an Rantfile:doc/rantfile.rdoc
Advanced Rantfiles:doc/advanced.rdoc