Packaging

Usually you want to create some archive file(s) to distribute your software. Rant supports creation of zip and gzipped tar archives. The good news is that you don’t have to install any extra software because Rant integrates parts of Mauricio Julio Fernandez Pradier and Austin Ziegler’s archive-tar-minitar and Thomas Sondergaard’s rubyzip.

Rant provides two techniques, which will be described in the following sections.

The Package::* generators

The Package namespace provides two generators, namely Package::Tgz and Package::Zip. Let’s look at some examples to create gzipped tar archives:

Put this into your Rantfile:

    import "package/tgz"
    gen Package::Tgz, "foo", :files => sys["{bin,lib,test}/**/*"]

This creates a file task called foo.tgz. If you run it with

    % rant foo.tgz

rant will create a directory called foo, link all selected files (in this case all files under bin/, lib/, test/ and all their subdirectories, recursively) to foo/ and then create the archive foo.tgz from the foo directory. This means that the archive contains exactly one toplevel directory (foo) which in turn contains the selected files. Most Open Source software is packaged up this way. Also usual is to add a version number to the package name:

    import "package/tgz"
    gen Package::Tgz, "foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

Now the created file task (and thus the archive file) is called foo-1.0.1.tgz, and the toplevel directory in the archive is called foo-1.0.1.

TAKE CARE:If the directory foo or whatever the name of the package is, exists, it will be removed if you invoke the package task!

To avoid such clashes, you can tell Rant to place the package in a subdirectory, e.g. pkg:

    import "package/tgz"
    gen Package::Tgz, "pkg/foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

Now the task is called pkg/foo-1.0.1.tgz. Rant automatically creates the pkg/ directory when required. Behind the scenes, Rant defines a Directory task for the pkg/ directory.

Cleaning up with AutoClean

The AutoClean generator knows which files/directories have been created by our packaging tasks. Just put this lines into the Rantfile:

    import "package/tgz", "autoclean"

    gen Package::Tgz, "pkg/foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

    gen AutoClean

If you invoke rant with autoclean as argument now:

    % rant autoclean

it will recursively remove the pkg and the foo-1.0.1 directories. This could be a problem if you have source files (or other precious files) in the pkg/ directory. In this case change the syntax to:

    import "package/tgz", "autoclean"

    gen Package::Tgz, "pkg", "foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

    gen AutoClean

The file name for the package is still pkg/foo-1.0.1.tgz like in the previous example, but now rant assumes that the pkg directory belongs to your source base and doesn’t create a Directory task for it. If you invoke the autoclean task now, it won’t remove the pkg directory, just the pkg/foo-1.0.1.tgz archive and the pkg/foo-1.0.1 directory.

If you intend to use AutoClean, you should definitely read doc/advanced.rdoc.

Writing a MANIFEST

Rant can automatically write a MANIFEST file where it lists all files which come into the package. Just give the :manifest flag:

    import "package/tgz"
    gen Package::Tgz, "pkg/foo", :manifest,
        :version => "1.0.1",
        :files => sys["{bin,lib,test}/**/*"]

Now Rant will write/update a file called MANIFEST whenever the package task (pkg/foo-1.0.1.tgz) is invoked. MANIFEST will then contain a list of all files (each path on a separate line, as usual). Of course the MANIFEST file will also be in the archive.

You can use another name instead of MANIFEST. In the following example we use contents:

    import "package/tgz"
    gen Package::Tgz, "pkg/foo",
        :manifest => "contents",
        :version => "1.0.1",
        :files => sys["{bin,lib,test}/**/*"]

Reading a MANIFEST

If you manually maintain a MANIFEST file with all files which belong to your software package, you can tell Rant to read it:

    import "package/tgz"
    gen Package::Tgz, "pkg/foo", :manifest, :version => "1.0.1"

Because we didn’t specify the list of files to package with the :files option, but gave the :manifest flag, Rant will read the list of files to package from a file called MANIFEST.

Giving a non-standard file extension

If you want another file extension than the default .tgz or .zip you can specify it with the :extension option:

    import "package/tgz"
    gen Package::Tgz, "pkg/foo", :manifest,
        :version => "1.0.1",
        :extension => ".tar.gz"

Creating zip packages

To create a zip package, use exactly the same syntax and options as for tgz packages. Just import "package/zip" and use the Pakage::Zip generator:

    import "package/zip"
    gen Package::Zip, "foo", :files => sys["{bin,lib,test}/**/*"]

This has the same effect as our first example, with the only difference that a foo.zip archive will be created. As already mentioned, you can give the same options and flags as to the Package::Tgz generator and of course you can use both in the same Rantfile:

    import "package/zip", "package/tgz"
    gen Package::Zip, "foo", :files => sys["{bin,lib,test}/**/*"]
    gen Package::Tgz, "foo", :files => sys["{bin,lib,test}/**/*"]

The Archive::* generators

Like the Package:: namespace, the Archive:: namespace also provides two generators, namely Archive::Tgz and Archive::Zip. They require the same syntax and recognize the same options. So what’s the difference? The Archive::* generators don’t move the selected files into a toplevel directory. Let’s look at an example:

    import "archive/tgz", "package/tgz"
    gen Package::Tgz, "foo", :files => sys["{bin,lib,test}/**/*"]
    gen Archive::Tgz, "bar", :files => sys["{bin,lib,test}/**/*"]

This creates a file task foo.tgz and a file task bar.tgz. The difference is, that foo.tgz contains a toplevel directory foo which in turn contains the bin, lib and test directories, but bar.tgz directly contains the bin, lib and test directories:

Created with Package::Tgz:

    foo.tgz
        foo/
            bin/
                .
                .
            lib/
                .
                .
            test/
                .
                .

Created with Archive::Tgz:

    bar.tgz
        bin/
            .
            .
        lib/
            .
            .
        test/

Of course the same difference is between the Package::Zip and Archive::Zip generators.

Creating a shortcut, dependencies

Imagine you have a package task which creates a tgz package and a task which should upload the package to a server. This means that the upload task depends on the package task. You could do this like in the following Rantfile:

    import "package/tgz"

    gen Package::Tgz, "pkg", "foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

    task :upload => "pkg/foo-1.0.1.tgz" do |t|
        sys "<some command to upload the archive file>"
    end

That’s not very convinient and it’s tedious to always update the dependency when you change the version number. But the package generator returns an object with information about the created package tasks:

    import "package/tgz"

    pkg =
    gen Package::Tgz, "pkg", "foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

    task :upload => pkg.path do |t|
        sys "<some command to upload the archive file>"
    end

Here we assign this object to the pkg variable. The pkg.path method returns the path to our tgz archive.

Another handy use case is to create a shortcut:

    import "package/tgz"

    pkg =
    gen Package::Tgz, "pkg", "foo", :version => "1.0.1", :files => sys["{bin,lib,test}/**/*"]

    desc "Create package for distribution."
    task :package => pkg.path

Now you can invoke the package task from the commandline:

    % rant -T
    rant package    # Create package for distribution.
    % rant package
    <messages about tgz creation>

Some random notes

  • Symbolic links are always dereferenced, i.e. the file/directory the link points to will be in the archive, not the symbolic link. Rant will probably provide an option to store the symbolic links in the future.
  • Directories are not included recursively:
          gen Package::Tgz, "foo", :files => sys["lib"]
    

    Assuming lib is a directory, foo.tgz will contain only the empty directory foo/lib, no matter how many files/subdirectories lib contains. If you want to package lib with all subdirectories and files it contains, use the following pattern:

          gen Package::Tgz, "foo", :files => sys["lib/**/*"]
    
  • Consider the directory where all files are linked to by the Package::* generators as byproducts and don’t rely on their creation.

See also

Rantfile basics:doc/rantfile.rdoc
Advanced Rantfiles:doc/advanced.rdoc
Support for C/C++:doc/c.rdoc
Rant Overview:README