File: rakefile.rdoc

# rakefile.rdoc

Path: | doc/rakefile.rdoc
Last Update: | Fri Sep 01 18:00:54 MDT 2006

# Rakefile Format

First of all, there is no special format for a Rakefile. A Rakefile contains executable Ruby code. Anything legal in a ruby script is allowed in a Rakefile.

Now that we understand there is no special syntax in a Rakefile, there are some conventions that are used in a Rakefile that are a little unusual in a typical Ruby program. Since a Rakefile is tailored to specifying tasks and actions, the idioms used in a Rakefile are designed to support that.

So, what goes into a Rakefile?

## Tasks

Tasks are the main unit of work in a Rakefile. Tasks have a name (usually given as a symbol or a string), a list of prerequisites (more symbols or strings) and a list of actions (given as a block).

### Simple Tasks

A task is declared by using the `task` method. `task` takes a single parameter that is the name of the task.

      task :name

### Tasks with Prerequisites

Any prerequisites are given as a list (inclosed in square brackets) following the name and an arrow (=>).

      task :name => [:prereq1, :prereq2]

NOTE: Although this syntax looks a little funky, it is legal Ruby. We are constructing a hash where the key is :name and the value for that key is the list of prerequisites. It is equivalent to the following …

      hash = Hash.new
      hash[:name] = [:prereq1, :prereq2]
      task(hash)

### Tasks with Actions

Actions are defined by passing a block to the `task` method. Any Ruby code can be placed in the block. The block may reference the task object via the block paramter..

      task :name => [:prereq1, :prereq2] do |t|
        # actions (may reference t)
      end

### Multiple Definitions

A task may be specified more than once. Each specification adds its prerequisites and actions to the existing definition. This allows one part of a rakefile to specify the actions and a different rakefile (perhaps separately generated) to specify the dependencies.

For example, the following is equivalent to the single task specification given above.

      task :name
      task :name => [:prereq1]
      task :name => [:prereq2]
      task :name do |t|
        # actions
      end

## File Tasks

Some tasks are designed to create a file from one or more other files. Tasks that generate these files may be skipped if the file already exists. File tasks are used to specify file creation tasks.

File tasks are declared using the `file` method (instead of the `task` method). In addition, file tasks are usually named with a string rather than a symbol.

The following file task creates a executable program (named `prog`) given two object files name `a.o` and `b.o`. The tasks for creating `a.o` and `b.o` are not shown.

      file "prog" => ["a.o", "b.o"] do |t|
        sh "cc -o #{t.name} #{t.prerequisites.join(' ')}"
      end

## Directory Tasks

It is common to need to create directories upon demand. The `directory` convenience method is a short-hand for creating a FileTask that creates the directory. For example, the following declaration …

      directory "testdata/examples/doc"

is equivalent to …

      file "testdata"              do |t| mkdir t.name end
      file "testdata/examples"     do |t| mkdir t.name end
      file "testdata/examples/doc" do |t| mkdir t.name end

The `directory` method does not accept prerequisites or actions, but both prerequisites and actions can be added later. For example …

      directory "testdata"
      file "testdata" => ["otherdata"]
      file "testdata" do
        cp Dir["standard_data/*.data"], "testdata"
      end

## Tasks with Parallel Prerequisites

Rake allows parallel execution of prerequisites using the following syntax:

      multitask :copy_files => [:copy_src, :copy_doc, :copy_bin] do
        puts "All Copies Complete"
      end

In this example, `copy_files` is a normal rake task. Its actions are executed whereever all of its prerequisites are done. The big difference is that the prerequisites (`copy_src`, `copy_bin` and `copy_doc`) are executed in parallel. Each of the prerequisites are run in their own Ruby thread, possibly allowing faster overall runtime.

### Secondary Prerequisites

If any of the primary prerequites of a multitask have common secondary prerequisites, all of the primary/parallel prerequisites will wait until the common prerequisites have been run.

For example, if the `copy_xxx` tasks have the following prerequisites:

      task :copy_src => [:prep_for_copy]
      task :copy_bin => [:prep_for_copy]
      task :copy_doc => [:prep_for_copy]

Then the `prep_for_copy` task is run before starting all the copies in parallel. Once `prep_for_copy` is complete, `copy_src`, `copy_bin`, and `copy_doc` are all run in parallel. Note that `prep_for_copy` is run only once, even though it is referenced in multiple threads.

### Thread Safety

The Rake internal data structures are thread-safe with respect to the multitask parallel execution, so there is no need for the user to do extra synchronization for Rake’s benefit. However, if there are user data structures shared between the parallel prerequisites, the user must do whatever is necessary to prevent race conditions.

## Rules

When a file is named as a prerequisite, but does not have a file task defined for it, Rake will attempt to synthesize a task by looking at a list of rules supplied in the Rakefile.

Suppose we were trying to invoke task "mycode.o", but no task is defined for it. But the rakefile has a rule that look like this …

      rule '.o' => ['.c'] do |t|
        sh "cc #{t.source} -c -o #{t.name}"
      end

This rule will synthesize any task that ends in ".o". It has a prerequisite a source file with an extension of ".c" must exist. If Rake is able to find a file named "mycode.c", it will automatically create a task that builds "mycode.o" from "mycode.c".

If the file "mycode.c" does not exist, rake will attempt to recursively synthesize a rule for it.

When a task is synthesized from a rule, the `source` attribute of the task is set to the matching source file. This allows us to write rules with actions that reference the source file.

### Advanced Rules

Any regular expression may be used as the rule pattern. Additionally, a proc may be used to calculate the name of the source file. This allows for complex patterns and sources.

The following rule is equivalent to the example above.

      rule( /\.o$/ => [
        proc {|task_name| task_name.sub(/\.[^.]+$/, '.c') }
      ]) do |t|
        sh "cc #{t.source} -c -o #{t.name}"
      end

NOTE: Because of a quirk in Ruby syntax, parenthesis are required on rule when the first argument is a regular expression.

The following rule might be used for Java files …

      rule '.java' => [
        proc { |tn| tn.sub(/\.class$/, '.java').sub(/^classes\//, 'src/') }
      ] do |t|
        java_compile(t.source, t.name)
      end

NOTE: `java_compile` is a hypothetical method that invokes the java compiler.

## Importing Dependencies

Any ruby file (including other rakefiles) can be included with a standard Ruby `require` command. The rules and declarations in the required file are just added to the definitions already accumulated.

Because the files are loaded before the rake targets are evaluated, the loaded files must be "ready to go" when the rake command is invoked. This make generated dependency files difficult to use. By the time rake gets around to updating the dependencies file, it is too late to load it.

The `import` command addresses this by specifying a file to be loaded after the main rakefile is loaded, but before any targets on the command line are specified. In addition, if the file name matches an explicit task, that task is invoked before loading the file. This allows dependency files to be generated and used in a single rake command invocation.

### Example:

      require 'rake/loaders/makefile'

      file ".depends.mf" => [SRC_LIST] do |t|
        sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}"
      end

      import ".depends.mf"

If ".depends" does not exist, or is out of date w.r.t. the source files, a new ".depends" file is generated using `makedepend` before loading.

## Comments

Standard Ruby comments (beginning with "#") can be used anywhere it is legal in Ruby source code, including comments for tasks and rules. However, if you wish a task to be described using the "-T" switch, then you need to use the `desc` command to describe the task.

### Example:

      desc "Create a distribution package"
      task :package => [ ... ] do ... end

The "-T" switch (or "—tasks" if you like to spell things out) will display a list of tasks that have a defined comment. If you use `desc` to describe your major tasks, you have a semi-automatic way of generating a summary of your Rake file.

      traken$ rake -T
      (in /home/.../rake)
      rake clean            # Remove any temporary products.
      rake clobber          # Remove any generated file.
      rake clobber_rdoc     # Remove rdoc products
      rake contrib_test     # Run tests for contrib_test
      rake default          # Default Task
      rake install          # Install the application
      rake lines            # Count lines in the main rake file
      rake rdoc             # Build the rdoc HTML Files
      rake rerdoc           # Force a rebuild of the RDOC files
      rake test             # Run tests
      rake testall          # Run all test targets

Only tasks with descriptions will be displayed with the "-T" switch. Use "-P" (or "—prereqs") to get a list of all tasks and their prerequisites.

## Namespaces

As projects grow (and along with it, the number of tasks), it is common for task names to begin to clash. For example, if you might have a main program and a set of sample programs built by a single Rakefile. By placing the tasks related to the main program in one namespace, and the tasks for building the sample programs in a different namespace, the task names will not will not interfer with each other.

For example:

      namespace "main"
        task :build do
          # Build the main program
        end
      end

      namespace "samples" do
        task :build do
          # Build the sample programs
        end
      end

      task :build => ["main:build", "samples:build"]

Referencing a task in a separate namespace can be achieved by prefixing the task name with the namespace and a colon (e.g. "main:build" refers to the :build task in the `main` namespace). Nested namespaces are supported, so

Note that the name given in the `task` command is always the unadorned task name without any namespace prefixes. The `task` command always defines a task in the current namespace.

### FileTasks

File task names are not scoped by the namespace command. Since the name of a file task is the name of an actual file in the file system, it makes little sense to include file task names in name space. Directory tasks (created by the `directory` command) are a type of file task and are also not affected by namespaces.

### Name Resolution

When looking up a task name, rake will start with the current namespace and attempt to find the name there. If it fails to find a name in the current namespace, it will search the parent namespaces until a match is found (or an error occurs if there is no match).

The "rake" namespace is a special implicit namespace that refers to the toplevel names.

If a task name begins with a "^" character, the name resolution will start in the parent namespace. Multiple "^" characters are allowed.

Here is an example file with multiple :run tasks and how various names resolve in different locations.

      task :run

      namespace "one" do
        task :run

        namespace "two" do
          task :run

          # :run            => "one:two:run"
          # "two:run"       => "one:two:run"
          # "one:two:run"   => "one:two:run"
          # "one:run"       => "one:run"
          # "^run"          => "one:run"
          # "^^run"         => "rake:run" (the top level task)
          # "rake:run"      => "rake:run" (the top level task)
        end

        # :run       => "one:run"
        # "two:run"  => "one:two:run"
        # "^run"     => "rake:run"
      end

      # :run           => "rake:run"
      # "one:run"      => "one:run"
      # "one:two:run"  => "one:two:run"

## Odds and Ends

### do/end verses { }

Blocks may be specified with either a `do`/`end` pair, or with curly braces in Ruby. We strongly recommend using `do`/`end` to specify the actions for tasks and rules. Because the rakefile idiom tends to leave off parenthesis on the task/file/rule methods, unusual ambiguities can arise when using curly braces.

For example, suppose that the method `object_files` returns a list of object files in a project. Now we use `object_files` as the prerequistes in a rule specified with actions in curly braces.

      # DON'T DO THIS!
      file "prog" => object_files {
        # Actions are expected here (but it doesn't work)!
      }

Because curly braces have a higher precedence than `do`/`end`, the block is associated with the `object_files` method rather than the `file` method.

This is the proper way to specify the task …

      # THIS IS FINE
      file "prog" => object_files do
        # Actions go here
      end

[Validate]