Rook Users' Guide

makoto kuwata <kwa(at)kuwata-lab.com>
last update: $Date: 2006-09-24 16:39:26 +0900 (Sun, 24 Sep 2006) $

Preface

Rook is a software build tool such as Make, Ant, SCons or Cook. It is implemented in Ruby and runs any platform Ruby support. Basic command (copy, move, rename, mkdir, ...) is also implemented in Ruby and allows you to execute platform-depended command.

Rook liken build process to cooking. Input file is called 'ingredient', output is 'product', task is 'recipe', build file is 'cookbook'. Rook generates products from ingredients according to recipes. You describe products, ingredients, and recipes in cookbook.

Features:

Caution! Rook is currently under experimental. It means that the design and specification of Rook may change frequently.

Table of Contents

Quick guide to YAML

YAML is a text-base format to describe stuctured data. The purpose of YAML is similar to XML but YAML is more friendly for human to read and write than XML.

You should learn about YAML because cookbook ('Rookbook.yaml') is described in YAML format. This chapter will help you to learn YAML in quickly. See the following page for details.

Sequence

Sequence is a list of element.

Sequence in YAML (block-style)
- aaa
- bbb
- ccc
Sequence in YAML (flow-style)
[ aaa, bbb, ccc ]

These are equivarent to the following Ruby code.

Sequence in Ruby
['aaa', 'bbb', 'ccc']

Mapping

Mapping is a map or hash of pairs of key and value.

Mapping in YAML (block-style)
one: 1
two: 2
three: 3
Mapping in YAML (flow-style)
{ one: 1, two: 2, three: 3 }

These are equivarent to the following Ruby code.

Mapping in Ruby
{'one'=>1, 'two'=>2, 'three'=>3}

Combination of Sequence and Mapping

Sequence and mapping are nestable in each other.

The following is an example of a sequence of mappings.

Sequence of mappings (block-style and flow-style)
- name:   Foo
  email:  foo@mail.com
  age:    30
  married: yes
- name:   Bar
  email:  bar@mail.org
  gender: F
- { name: Baz, age: 19, married: no }

The above is equivarent to the following Ruby code.

Sequence of mappings in Ruby
[
  {'name'=>'Foo', 'email'=>'foo@mail.com', 'age'=>30},
  {'name'=>'Bar', 'email'=>'bar@mail.org', 'gender'=>'F'},
  {'name'=>'Baz', 'age'=>19, 'married'=>false},
]

The following is an example of a mapping of sequences.

Mapping of seqeunces (block-style and flow-style)
members:
  - Foo
  - Bar
  - Baz
tasks:
  - write code
  - test code
  - write documents
sub-projects: [ sub1, sub2 ]

The above is equivarent to the following Ruby code.

Sequence of mappings in Ruby
{
  'members'=>['Foo', 'Bar', 'Baz'],
  'tasks'=>['write code', 'test code', 'write documents'],
  'sub-projects'=>['sub1, 'sub2'],
}

Scalar and Data Type

YAML determines data type of elemet automatically.

Data types which YAML supports
- foobar           # string
- 123              # integer
- 3.14159          # float
- true             # boolean (true)
- yes              # boolean (true)
- on               # boolean (true)
- false            # boolean (false)
- no               # boolean (false)
- off              # boolean (false)
- 2006-01-01       # date
- 2006-01-01 00:00:00  # timestamp
- 'true'           # string
- "false"          # string

YAML data is represented by the combination of the followings.

This feature is similar to JSON. In fact, you can regard YAML's flow-style as JSON.

Block text

'|' means start of block-text. Block-text is convenient if string has several lines.

Block text example
text1: |
  foo
  bar
  baz
text2: |
  foo
    bar
      baz

This is equivarent to the following Ruby code.

Block text in Ruby
[
  'text1'=> <<END,
foo
bar
baz
END
  'text2'=> <<END,
foo
  bar
    baz
END
]

Anchor and Alias

Anchor is feature to mark data. Alias is feature to refer anchored data.

Anchor and alias example
- name:  foo
  desc:  &anchor1 description
  text:  &anchor2 |
    blocktext
    blocktext
    blocktext

- name:  bar
  desc:  *anchor1
  text:  *anchor2

This is equivarent to the following Ruby code.

Anchor and alias in Ruby
anchor1 = "description"
anchor2 = <<END
blocktext
blocktext
blocktext
END

[
  { 'name'=>'foo',
    'desc'=>anchor1,
    'text'=>anchor2,
  },
  { 'name'=>'bar',
    'desc'=>anchor1,
    'text'=>anchor2,
  }
]

Comment

YAML comment starts with the '#' character. Between '#' and end of line is YAML comment.

# comment
    # comment
members:        # comment
  - name: Foo   # comment
    age:  19    # comment

YAML has only line comment.

Special Characters

YAML uses several characters to describe features.

char feature
[ start of inline-style sequence
] end of inline-style sequence
{ start of inline-style mapping
} end of inline-style mapping
- sequence element in block-style
? mapping element in block-style
: separator of key and value in block-style and flow-style
* alias
& anchor
! data type
# comment
| text in block-style
> text in block-style

Notice that these characters may cause YAML parse error if you don't quote them with single quotation or double quotation.

## OK
- foo *bar* baz

## NG  (because '*foo' means alias in YAML)
- *foo* bar baz

## OK
- '*foo* bar baz'

See YAML Cookbook or YAML specification for details.

Cookbook

Recipes

Cookbook should contains some recipes.

In the 'method*:' part, some helper functions provided by Rook are available. For exaple, function 'sys()' invokes OS-depend command. See References for details about helper functions.

The following is an example of recipe definitions in cookbook.

Rookbook.yaml: Compile hello.c so that to generate 'hello' command.
recipes:

  - product:    hello
    desc:       generates hello command
    ingreds:    [ hello.o ]
    method*: |
      sys "gcc -g -o hello hello.o"

  - product:    hello.o
    desc:       compile 'hello.c' and 'hello.h'
    ingreds:    [ hello.c, hello.h ]
    method*: |
      sys "gcc -g -c hello.c"

The following is an example of invoking rook command. Command-line option '-l' shows recipes which have 'desc:' part. It means that recipes which have 'desc:' part are public recipes. Command-line option '-L' shows all recipes.

command-line example
$ rook -l
properites:

recipes:
  hello              : generates hello command
  hello.o            : compile 'hello.c' and 'hello.h'
$ rook hello
### ** hello.o
rook$ gcc -g -c hello.c
### * hello
rook$ gcc -g -o hello hello.o

One of the aim of Rook is fusion of 'declarative' and 'procedural'. Attributes of recipe are described in declarative format (=YAML) and steps how to generate product are described in procedural format (=Ruby).

The benefits of this style are:

Ruby is the language which is easy to define DSL(Domain Specific Language). But not all the language have the ability to define DSL as easily as Ruby. Embedding script code in YAML solve the problem.

@product and @ingreds

Product and ingredient names are referable as instance variable in recipe's 'method*:' part.

Rookbook.yaml: Use @product and @ingreds
recipes:

  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      sys "gcc -g -o #{@product} #{@ingred}"
      # or sys "gcc -g -o #{@product} #{@ingreds[0]}"

  - product:   hello.o
    desc:      compile 'hello.c' and 'hello.h'
    ingreds:   [ hello.c, hello.h ]
    method*: |
      sys "gcc -g -c #{@ingred}"
      # or sys "gcc -g -c #{@ingreds[0]}"
command-line example
$ rook hello
### ** hello.o
rook$ gcc -g -c hello.c
### * hello
rook$ gcc -g -o hello hello.o

Specific recipe and generic recipe

Specific recipe is a recipe which is combined to a certain file. Product name of specific recipe is a concrete file name.

Generic recipe is a recipe which is combined to a pattern of file name. Product name of generic recipe is a pattern with metacharacter or regular expression.

Rook converts file name pattern into regular expression. For example, file name pattern '*.o' is coverted to regular expression '/^(.*)\.o$/', and '*.??.txt' to '/^(.*)\.(.)(.)\.txt$/'. Matched strings with metacharacter ('*' or '?') are accessable by '$(1)', '$(2)', ... in recipe definition and instance variable '@m' in 'method*:' part.

Rookbook.yaml: Compile hello.c so that to generate 'hello' command.
recipes:

  ## specific recipe
  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      sys "gcc -g -o #{@product} #{@ingred}"

  ## generic recipe
  - product:   *.o        # or /^(.*)\.o$/
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c, $(1).h ]
    method*: |
      sys "gcc -g -c $(1).c"
      # or "gcc -g -c #{@m[1]}.c"
      # or "gcc -g -c #{@ingred}"
command-line example
$ rook -l
properites:

recipes:
  hello              : generates hello command
  *.o                : compile '*.c' and '*.h'
$ rook hello
### ** hello.o
rook$ gcc -g -c hello.c
### * hello
rook$ gcc -g -o hello hello.o

It is able to specify regular expression instead of filename pattern. For example, /^(.*)\.o$/ is available as product instead of *.o. Grouping in regular expression is referable by $(1), $(2), ... in the same way.

Specific recipe is prior to generic recipe. For example, recipe 'hello.o' is used and recipe '*.o' is not used to generate 'hello.o' when target product is 'hello.o' in the following example.

Specific recipe is prior to generic recipe.
recipes:

  - product:   hello.o
    ingreds:   [ hello.c ]
    method*: |
      sys "gcc -g -O2 -o #{@product} #{@ingred}"

  - product:   *.o
    ingreds:   [ *.c, *.h ]
    method*: |
      sys "gcc -g     -o #{@product} #{@ingred}"

Toppings

There may be a case that ingredient file exists or not. For example, product 'foo.o' depends on 'foo.c' and 'foo.h', while product 'bar.o' depends only on 'bar.c'.

In the case, you can use 'toppings:' part which resolve the problem. For example when 'toppings: [hello.h]' is specified in recipe definition, Rook detect dependency as following.

'toppings:' part is useful especially when used with generic recipes.

Rookbook.yaml: Example of 'toppings:'
recipes:

  ## specific recipe
  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      sys "gcc -g -o #{@product} #{@ingred}"

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      sys "gcc -g -c #{@ingred}"     # or "gcc -g -c $(1).c"
command-line example
$ rook hello
### ** hello.o
rook$ gcc -g -c hello.c
### * hello
rook$ gcc -g -o hello hello.o

Symbolic Recipes

Symbolic recipe is a recipe which is not combined with any files. Product name of symbolic recipe starts with colon (':').

For example, symbolic recipe :clean is a recipe to delete '*.o' files and is not combined to file ':clean'. Also symbolic recipe :all is a recipe to call recipe of 'hello' and is not combined to file ':all'.

Rookbook.yaml: Symbolic recipes
recipes:
  ## specific recipe
  - product:   hello
    desc:      generates hello command
    ingreds:   [ hello.o ]
    method*: |
      sys "gcc -g -O2 -o #{@product} #{@ingred}"

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      sys "gcc -g -O2 -c #{@ingred}"     # or "gcc -g -c $(1).c"

  ## symbolic recipes
  - product:   :clean
    method*: |
      rm_f '*.o'         # remove '*.o' files

  - product:   :all
    ingreds:   [ hello ]
command-line example
$ rook -l
properites:

recipes:
  hello              : generates hello command
  *.o                : compile '*.c' and '*.h'
  :clean             : remove all byproducts and itermediates
  :all               : cook all
$ rook :all
### *** hello.o
rook$ gcc -g -O2 -c hello.c
### ** hello
rook$ gcc -g -O2 -o hello hello.o
### * :all
$ ls
Rookbook.yaml    hello    hello.c    hello.h    hello.o
$ rook :clean
### * :clean
rook$ rm -f *.o
$ ls
kookbook.yaml    hello    hello.c    hello.h

Rook have several well-known symbolic product. Symbolic recipes which product is contained in the following list are got public automatically.

:all
create all products
:clean
remove by-products
:clear
remove all products and by-products
:deploy
deploy products
:config
configure
:setup
setup
:install
install products
:test
do test

Properties

Property is a pair of name and value. You can set properties in cookbook.

You can get property value with '$(name)' notation in property definition and recipe definition. And properties are accessable as instance variable in 'method*' part of recipe definition.

Property name should match to regular expression '/^[_a-zA-Z]\w*$/'. Notice that period ('.') or hyphen ('-') are not available as property name character.

Rookbook: properties
properties:
  - cc:         gcc
  - c_flags:    -g -O2
  - basename:   hello

recipes:

  ## specific recipe
  - product:   $(basename)
    desc:      generates $(basename) command
    ingreds:   [ $(basename).o ]
    method*: |
      sys "#{@cc} #{@c_flags} -o #{@product} #{@ingred}"

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      sys "#{@cc} #{@c_flags} -c #{@ingred}"  # or "#{@cc} #{@c_flags} -c $(1).c"

  ## symbolic recipes
  - product:   :clean
    method*: |
      rm_f '*.o';        # remove '*.o' files

  - product:   :all
    ingreds:   [ $basename ]
command-line example
$ rook hello
### ** hello.o
rook$ gcc -g -O2 -c hello.c
### * hello
rook$ gcc -g -O2 -o hello hello.o

Properties are shown when command-line option '-l' is specified. And it is able to overwrite properties in command-line option '--propname=propvalue'. Properties specified in command-line are prior to that in Rookbook.

command-line example
$ rook -l
properites:
  cc                 = "gcc"
  c_flags            = "-g -O2"
  basename           = "hello"

recipes:
  hello              : generates hello command
  *.o                : compile '*.c' and '*.h'
  :clean             : remove all byproducts and itermediates
  :all               : cook all
$ rook -l --basename=foobar
properites:
  basename           = "foobar"
  cc                 = "gcc"
  c_flags            = "-g -O2"

recipes:
  foobar             : generates foobar command
  *.o                : compile '*.c' and '*.h'
  :clean             : remove all byproducts and itermediates
  :all               : cook all

Property file is another way to specify properties. If you have create property file 'Rookbook.props' or 'Properties.yaml', rook command automatically read it and set properties.

Property file example
basename:  hello
c_flags:   -g -O2

Property value can be specified in both YAML format and Ruby code. If you append '*' in tail of property name, Rook regard it's value as Ruby code. Property value is to be refered by '$(propname)' in YAML and by '$propname' in Ruby code.

Property values specified as Ruby code.
properties:
  - srcdir:      src/main/java
  - junit_jar:   lib/junit.jar
  - path_elems:  [ '.', $(srcdir), $(junit_jar) ]
  - classpath*:  @path_elems.join(File::PATH_SEPARATOR)

recipes:
  - product:     :echo
    method*: |
        p @classpath      #=> ".:src/main/java:lib/junit.jar"

Parameters

Parameters are similar to properties but have the following differences.

You can consider properties as 'global variables' or 'public variable', and parameters as 'local variables' or 'private variable'.

Similar to properties, parameters value can be refered by '$(paramname)' in YAML and by 'paramname' (local variable) in Ruby code.

There is a special parameter 'rook_product'. If the parameter is set, rook regard it as the default target product when target product is not specified in command-line.

Rookbook.yaml: parameters
properties:
  - cc:         gcc
  - c_flags:    -g -O2

parameters:
  - basename:      hello
  - rook_product:  $(basename)    # default product when no product
                                        # specified in command-line

recipes:

  ## specific recipe
  - product:   $(basename)
    desc:      generates $(basename) command
    ingreds:   [ $(basename).o ]
    method*: |
      sys "#{@cc} #{@c_flags} -o #{basename} #{basename}.c"
      # or sys "$cc #{@c_flags} -o #{@product} #{@ingred}"

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      sys "#{@cc} #{@c_flags} -c #{@ingred}"

  ## symbolic recipes
  - product:   :clean
    method*: |
      rm_f '*.o'         # remove '*.o' files

  - product:   :all
    ingreds:   [ $(basename) ]
command-line example
$ rook -L
properites:
  cc                 = "gcc"
  c_flags            = "-g -O2"

parameters:
  basename           = "hello"
  rook_product       = "hello"

recipes:
  hello              : generates hello command
  *.o                : compile '*.c' and '*.h'
  :clean             : remove all byproducts and itermediates
  :all               : cook all
$ rook
### ** hello.o
rook$ gcc -g -O2 -c hello.c
### * hello
rook$ gcc -g -O2 -o hello hello.c

Material

There is an exception in any case. Assume that you have a file 'optparse.o' which is supplied by other developer and no source. Rook will try to find 'optparse.c' and failed in the result.

Using 'materials:' part, you can tell Rook that 'optparse.o' is not a product .

Rookbook.yaml: materials
properties:
  - cc:         gcc
  - c_flags:    -g -O2

parameters:
  - basename:      hello
  - rook_product:  $(basename)

materials:
  - optparse.o

recipes:
  ## specific recipe
  - product:   $(basename)
    desc:      generates $(basename) command
    ingreds:   [ $(basename).o, optparse.o ]
    method*: |
      sys "#{@cc} #{@c_flags} -o #{@product} #{@ingred} optparse.o"

  ## generic recipe
  - product:   *.o
    desc:      compile '*.c' and '*.h'
    ingreds:   [ $(1).c ]
    toppings:  [ $(1).h ]
    method*: |
      sys "#{@cc} #{@c_flags} -c #{@ingred}"   # or "#{@cc} #{@c_flags} -c $(1).c"

  ## symbolic recipes
  - product:   :clean
    method*: |
      rm_f '*.o'

  - product:   :all
    ingreds:   [ $(basename) ]
command-line example
$ rook
### ** hello.o
rook$ gcc -g -O2 -c hello.c
### * hello
rook$ gcc -g -O2 -o hello hello.o optparse.o

Ruby DSL style Cookbook

Rook allows you to describe cookbook not only in YAML format but also Ruby DSL style.

Rookbook.rb: cookbook in ruby dsl.
## properties
property :cc,      'gcc'
property :c_flags, '-g -O2'


## parameters
basename = 'hello'
parameter :rook_product, basename


## materials
material 'optparse.o'


## recipes

desc "generated #{basename} command"
recipe basename,        "#{basename}.c", "#{basename}.o"   do |r|
        sys "#{@cc} #{@c_flags} -o #{@product} #{@ingred} optparse.o"
    end

desc "compile '*.c' and '*.h'"
recipe '*.o',           "$(1).c", :toppings=>["$(1).h"]    do |r|
        sys "#{@cc} #{@c_flags} -c #{@ingred}"
        # notice that "$(1).c" is not available in block!
    end

recipe :clean           do |r|
        rm_f '*.o'
    end

recipe :all,            basename

Default name cookbook in Rook is Rookbook.yaml or Rookbook.rb. If suffix of cookbook is '.yaml' or '.yml' then rook loads cookbook in YAML mode, else if suffix is '.rb' then rook loads cookbook as Ruby script.

command-line example
$ rook -l
### ** hello.o
rook$ gcc -g -O2 -c hello.c
### * hello
rook$ gcc -g -O2 -o hello hello.c optparse.o

Why rook supports both YAML style and Ruby DSL style? Because it is for choosability. YAML style is declarative and Ruby DSL style is procedural. Each approach have each advantages. You can choose which style to describe cookbook.

Other features

Invoke Recipes Forcedly

Command-line option '-F' invokes recipes forcedly, it means that timestamp of files are ignored.

Rookbook.yaml:
recipes:

  - product:   hello
    ingreds:   [ hello.o ]
    method*: |
        sys "gcc -o #{@product} #{@ingred}"

  - product:   hello.o
    ingreds:   [ hello.c, hello.h ]
    method*: |
        sys "gcc -c #{@ingred}"
command-line example
## create 'hello'
$ rook hello
### ** hello.o
rook$ gcc -c hello.c
### * hello
rook$ gcc -o hello hello.o

## 'hello' is newer than 'hello.c' and 'hello.h'
$ rook hello

## create 'hello' forcedly
$ rook -F hello
### ** hello.o
rook$ gcc -c hello.c
### * hello
rook$ gcc -o hello hello.o

Validation check for Cookbook

Command-line option '-c' do validation checks for cookbook. It checks schema check of cookbook and Ruby syntax check.

Rookbook.yaml: cookbook for validation example
property:                    # 'properties:' is correct
  - cc:       gcc
  - c_flags:  -g -O2

recipes:
  - product:  hello
    method*: |
      sys "#{@cc} #{@c_flags} hello.c"
      mv 'a.out', 'hello'
validation example
$ rook -c
validation error:
Rookbook11.yaml:1: [/property] key 'property:' is undefined.
Rookbook.yaml: cookbook for validation example
properties:
  - cc:       gcc
  - c_flags:  -g -O2

recipes:
  - product:  hello
    method*: |
      sys "#{@cc} #{@c_flags} hello.c"
      rm_f 'a.out', 'hello',           # extra comma
validation example
$ rook -c
Rookbook12.yaml:9: parse error, unexpected $

Nested Array

You can specify not only filenames but also array of filenames as ingredient on 'ingreds:', 'toppings:', 'byprods:' and 'coprods:'. In addition, nested arrays are also available. These arrays are flattened by rook.

Rookbook.yaml: specify nested array as a ingredient
parameters:
  - project:     hello
  - src_files*:  Dir.glob('lib/*.rb')           # array
  - text_files:  [README, ChangeLog, COPYING]   # array (sequence)
  - all_files:   [$(src_files), $(text_files)]  # nested array

recipes:
  - product:  :inspect
    ingreds:  [ package.xml, $(all_files) ]   # nested array will be flatten
    method*: |
      # all_files is a nested sequence
      puts "*** all_files (nested)"
      p all_files

      # $ingreds is flattened
      puts "*** @ingreds (flattened)"
      p @ingreds
command-line example
$ rook :inspect
### * :inspect
*** all_files (nested)
[["lib/bar.rb", "lib/baz.rb", "lib/foo.rb"], ["README", "ChangeLog", "COPYING"]]
*** @ingreds (flattened)
["package.xml", "lib/bar.rb", "lib/baz.rb", "lib/foo.rb", "README", "ChangeLog", "COPYING"]

Preparation

It is able to load external Ruby scripts and to define functions with 'preparation*:' section. You can write any Ruby code in 'preparation*:' section (tail asterisk '*' means that 'this is Ruby code').

'preparation*:' section is evaluated by rook before evaluating 'properties:', 'parameters:', and 'recipes:' sections. Functions you defined in 'preparation*:' section are available in 'properties:', 'parameters:', or 'recipe:' section.

Rookbook.yaml: preparation example
preparation*: |

  require 'my-util'          # load external Ruby script
  
  def my_join_path(list)     # define original function
    s = ''
    list.each do |item|
      if item && !item.empty?
        s << File::PATH_SEPARATOR unless s.empty?
        s << item
      end
    end
    return s
  end

parameters:
  - path_elems:  [ '.', src/main/java, null, '', lib/junit.jar ]
  - classpath*:  my_join_path(path_elems)

Recipe Listing

Command-line option '-l' shows list of properties and recipes which has description and '-L' shows list of properties, parameters, and all of recipes.

Rookbook.yaml: cookbook example
properties:
  - cc:       gcc
  - c_flags:  -g -O2

parameters:
  - basename:  hello
  - kook_product:  $(basename)

recipes:

  # this will be shown with '-l' option because it has 'desc:'
  - product:  hello
    desc:     create hello command

  # this will not be shown with '-l' option  because no 'desc:'
  - product:  *.o
    #desc:    no comment
command-line example
$ rook -l
properites:
  cc                 = "gcc"
  c_flags            = "-g -O2"

recipes:
  hello              : create hello command
$ rook -L
properites:
  cc                 = "gcc"
  c_flags            = "-g -O2"

parameters:
  basename           = "hello"
  kook_product       = "hello"

recipes:
  hello              : create hello command
  *.o                : 

Rook set description of symbolic recipe automatically if product name is ':all', ':clean', ':clear', or ':test'. This means that these symbolic recipes are listed automatically even when you omit recipe description.

Rookbook.yaml: cookbook example
recipes:
  # the following are shown regardless no-description.
  - product:  :all
    #desc:    no description
  - product:  :clean
    #desc:    no description
  - product:  :clear
    #desc:    no description
  - product:  :test
    #desc:    no description
command-line example
$ rook -l
properites:

recipes:
  :all               : cook all
  :clean             : remove all byproducts and itermediates
  :clear             : remove all byproducts, intermediates, and products
  :test              : do test

Recipe-local Parameters

Recipe can have it's own local parameters. Recipe local parameters cannot be refered by other recipes.

This is convenient to override property or parameter specified in cookbook, and to share method definition with other recipe by YAML anchor and alias.

Rookbook.yaml: cookbook example
parameters:
  - c_flags:     -g -Wall

recipes:
  - product:     *.o
    ingreds:     [ $(1).c ]
    toppings:    [ $(1).h ]
    method*: &gcc |
        sys "gcc #{c_flags} -c #{@ingred}"

  - product:     foo.o
    ingreds:     [ foo.c ]
    params:
      - c_flags:   $(c_flags) -O2
    method*:     *gcc
command-line example
$ rook -n hello.o
### * hello.o
rook$ gcc -g -Wall -c hello.c
$ rook -n foo.o
### * foo.o
rook$ gcc -g -Wall -O2 -c foo.c

By-Products and Co-Products

'byprods:' and 'coprods:' sections represent by-products and co-products of the recipe. Currently, these section have no feature than description.

Instance variables @byprods and @coprods in 'method*:' section represents by-products and co-products of the recipe. Instance variable @byprod is equivarent to @byprods[0] and @coprod is equivarent to @coprods[0].

Rookbook.yaml: example of by-products.
recipes:
  - product:    *.dvi
    ingreds:    [ $(1).tex ]
    byprods:    [ $(1).aux, $(1).log ]
    method*: |
        sys "latex #{@ingred}"
        rm_f @byprods

References

Filesystem Functions

The following functions are available in 'method*:' part of recipes.

sys cmmand-string
Execute command-string. If command status is not zero then exception is raised.
sys "gcc hello.c"
sys! command-string
Execute command-string. Command statuis is ignored even if it is zero.
echo string
Echo string. Newline is printed.
echo "OK."
echo_n string
Echo string. Newline is not printed.
echo_n "Enter your name: "
chdir dir, &block
Change directory and do block.
chdir "lib" do
  cp_pr "**/*.rb", dir
end
cd dir, &block
Equivarent to chdir.
chmod mode, path[, path2, ...]
Change permission of path.
chmod 0644, "lib/**/*.rb"
chmod 0755, "bin/*"
chmod_R mode, path[, path2, ...]
Change permission of path recursively.
chmod_R 0644, "lib"
chown user, group, path[, path2, ...]
Change owner of path. If user or group is nil then no changed.
chown nobody, nil, "lib/**/*.rb"
chown_R owner, group, path[, path2, ...]
Change owner of path recursively. If user or group is nil then no changed.
chown nobody, nil, "lib"
mkdir path
Make directory.
mkdir "lib"
mkdir_p path
Make directory. If parent directory is not exist then it is created automatically.
mkdir_p "foo/bar/baz"
rm path[, path2, ...]
Remove files.
rm '*.html', '*.txt'
rm_r path[, path2, ...]
Remove files or directories recursively.
rm_r '*'
rm_f path[, path2, ...]
Remove files forcedly. No errors reported even if path doesn't exist.
rm_f '*.html', '*.txt'
rm_rf path[, path2, ...]
Remove files or directories forcedly. No errors reported even if path doesn't exist.
rm_rf '*'
touch path[, path2, ...]
Touch files or directories. If path doesn't exist then empty file is created.
touch '*.c'
cp file1, file2
Copy file1 to file2.
cp 'foo.txt', 'bar.txt'
cp file, file2, ..., dir
Copy file to dir.
cp '*.txt', '*.html', 'dir'
cp_r path1, path2
Copy path1 to path2 recursively.
cp_r 'dir1', 'dir2'
cp_r path, path2, ..., dir
Copy path to dir recursively. Directory dir must exist.
cp_r 'lib', 'doc', 'test', 'dir'
cp_p file1, file2
Copy file1 to file2. Timestams is preserved.
cp_p 'foo.txt', 'bar.txt'
cp_p file, file2, ..., dir
Copy file to dir. Timestams is preserved. Directory dir must exist.
cp_p '*.txt', '*.html', 'dir'
cp_pr path1, path2
Copy path1 to path2 recursively. Timestams is preserved.
cp_pr 'lib', 'lib.bkup'
cp_pr path, path2, ..., dir
Copy path to dir recursively. Directory dir must exist. Timestams is preserved.
cp_pr 'lib/**/*.rb', 'test/**/*.rb', 'tmpdir'
cp_a path, path2, ..., dir
Copy path to dir recursively. Timestams is preserved.
mv file1, file2
Rename file1 to file2.
mv 'foo.txt', 'bar.txt'
mv path, path2, ..., dir
Move path to dir.
mv 'lib/*.rb', 'test/*.rb', 'tmpdir'
ln file1, file2
Create hard-link.
ln 'file1.txt', 'file2.txt'
ln_s path, path2
Create symblic-link.
ln_s 'dir1', 'dir2'
ln_s '*.txt', 'dir'
ln_sf path, path2
Create symblic-link forcedly.
ln_sf 'dir1', 'dir2'
ln_sf '*.txt', 'dir'
store path, path2, ..., dir
Copy path (files or directories) to dir with keeping path-structure.
store "lib/**/*.rb", "doc/**/*.html", "dir"
store_p path, path2, ..., dir
Copy path (files or directories) to dir with keeping path-structure. Timestamp is preserved.
store_p "lib/**/*.rb", "doc/**/*.html", "dir"
store_a path, path2, ..., dir
Copy path (files or directories) to dir with keeping path-structure. Timestamp is preserved.
store_a "lib/**/*.rb", "doc/**/*.html", "dir"
edit path, path2, ... { |content [,filename]| ... }
Edit file content. If path is directory then it is ignored.
edit "lib/**/*.rb", "doc/**/*.html" do |content|
  content.gsub!(/\$Release\$/, '1.0.0')
  content.gsub!(/\$Copyright\$/, 'copyright(c) 2006 kuwata-lab.com')
end
zip archive, path1, path2, ...
Create zip archive file. Requires rubyzip library.
zip 'file.zip', 'file1.txt', 'file2.txt'
zip_r archive, path1, path2, ...
Create zip archive file recursively. Requires rubyzip library.
zip_r 'file.zip', 'lib', 'doc', 'test'
unzip archive
Unzip archive file. Requires rubyzip library.
unzip 'file.zip'
tar_cf archive, path1, path2, ...
Create tar archive. Requies minitar library.
tar_cf 'file.tar', 'lib', 'doc', 'test'
tar_xf archive
Extract tar archive. Requies minitar library.
tar_xf 'file.tar'
tar_czf archive, path1, path2, ...
Create gzipped tar archive. Requies minitar library.
tar_czf 'file.tar.gz', 'lib', 'doc', 'test'
tar_xzf archive
Extract gzipped tar archive. Requies minitar library.
tar_xzf 'file.tar.gz'
tar_cjf archive, path1, path2, ...
Create gzipped tar archive. Requies minitar library and bz2 library.
tar_cjf 'file.tar.bz2', 'lib', 'doc', 'test'
tar_xjf archive
Extract gzipped tar archive. Requies minitar library and bz2 library.
tar_xjf 'file.tar.bz2'

The above functions can take arrays as file or directory name. For example, the following code is available.

## specify filenames in strings
cp 'file1.txt', 'file2.txt', 'file3.txt', 'dir'

## specify filenames with filename pattern
cp '*.txt', 'dir'

## specify filenames in array.
files = %w[file1.txt file2.txt file3.txt]
cp files, 'dir'

The following file pattern is available.

*
Matches sequence of any character.
?
Matches a character.
{a,b,c}
Matches a or b or c.
**/
Matches directory recursively.

Helper Functions

The following fuctions are available at 'properties:', 'parameters:', 'preparation*:', and 'method*:' in cookbook.

Enumerable#apply(method_name, *args)
Appy method with args.
[1, 2, 3].apply(:'+', 5)  #=> [6, 7, 8]
Enumerable#add_prefix(prefix)
Add prefix for each item.
['a', 'b', 'c'].add_prefix('dir/') #=> ["dir/a", "dir/b", "dir/c"]
Enumerable#add_suffix(suffix)
Add suffix for each item.
['a', 'b', 'c'].add_suffix('.txt') #=> ["a.txt", "b.txt", "c.txt"]
Enumerable#sandwich(prefix, suffix)
Add prefix and suffix for each item.
['a','b','c'].sandwich('p/', '.s') #=> ["p/a.s", "p/b.s", "p/c.s"]
Enumerable#each_sub(pattern, replace, &block)
Apply 'sub()' method to each item.
['a.txt', 'b.txt'].each_sub(/\.txt$/, '.html') #=> ["a.html", "b.html"]
Enumerable#each_gsub(pattern, replace, &block)
Apply 'gsub()' method to each item.
['a.txt', 'b.txt'].each_gsub(/\.txt$/, '.html') #=> ["a.html", "b.html"]
Enumerable#delete_suffix(suffix=/\.\w+\z/)
Delete suffix for each item.
['a.txt', 'b.txt'].delete_suffix() #=> ["a", "b"]
Enumerable#basenames()
Get basename of each item.
['dir1/a.txt', 'b.txt'].basenames() #=> ['a.txt', 'b.txt']
Enumerable#dirnames()
Get dirname of each item.
['dir1/a.txt', 'b.txt'].dirnames() #=> ['dir1', '.']

Command-line options

Usage: kook [-hvTnqQFlLc] [-b bookname] product

Options:

-h
Print help and quit.
-v
Print version and quit.
-l
List properties and described recipes.
-L
List all propeties, parameters, and recipes.
-q
Quiet mode.
-Q
More quiet mode.
-F
Force recipes to execute (ignore timestamps).
-n
No command execution. Notice that ruby code is executed.
-T
Don't untabify cookbook. In default Rook command expands tab characters in cookbook into spaces, because YAML parser doesn't allow cookbook (= YAML document) to include tab characters. Command-line option '-T' doesn't expand tab characters.
-c
Validation check of cookbook.
-b bookname
Cookbook filename.
-P propfile
Property filename (default is 'Rookbook.props' and 'properties.yaml'). '-P -' means not to read property file.
--propname=propvalue
Property name and value.