123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- = Rakefile Format (as of version 0.8.7)
- 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 (enclosed in square brackets)
- following the name and an arrow (=>).
- task :name => [:prereq1, :prereq2]
- <b>NOTE:</b> 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 parameter.
- 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 <tt>a.o</tt> and <tt>b.o</tt>. The tasks
- for creating <tt>a.o</tt> and <tt>b.o</tt> 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 whenever 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 prerequisites 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 <tt>copy_<em>xxx</em></tt> 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.
- == Tasks with Arguments
- Prior to version 0.8.0, rake was only able to handle command line
- arguments of the form NAME=VALUE that were passed into Rake via the
- ENV hash. Many folks had asked for some kind of simple command line
- arguments, perhaps using "--" to separate regular task names from
- argument values on the command line. The problem is that there was no
- easy way to associate positional arguments on the command line with
- different tasks. Suppose both tasks :a and :b expect a command line
- argument: does the first value go with :a? What if :b is run first?
- Should it then get the first command line argument.
- Rake 0.8.0 solves this problem by explicitly passing values directly
- to the tasks that need them. For example, if I had a release task
- that required a version number, I could say:
- rake release[0.8.2]
- And the string "0.8.2" will be passed to the :release task. Multiple
- arguments can be passed by separating them with a comma, for example:
- rake name[john,doe]
- Just a few words of caution. The rake task name and its arguments
- need to be a single command line argument to rake. This generally
- means no spaces. If spaces are needed, then the entire rake +
- argument string should be quoted. Something like this:
- rake "name[billy bob, smith]"
- (Quoting rules vary between operating systems and shells, so make sure
- you consult the proper docs for your OS/shell).
- === Tasks Arguments and the Environment
- Task argument values can also be picked up from the environment. For
- example, if the "release" task expected a parameter named
- "release_version", then either
- rake release[0.8.2]
- or
- RELEASE_VERSION rake release
- will work. Environment variable names must either match the task
- parameter exactly, or match an all-uppercase version of the task
- parameter.
- === Tasks that Expect Parameters
- Parameters are only given to tasks that are setup to expect them. In
- order to handle named parameters, the task declaration syntax for
- tasks has been extended slightly.
- For example, a task that needs a first name and last name might be
- declared as:
- task :name, [:first_name, :last_name]
- The first argument is still the name of the task (:name in this case).
- The next two arguments are the names of the parameters expected by
- :name in an array (:first_name and :last_name in the example).
- To access the values of the parameters, the block defining the task
- behaviour can now accept a second parameter:
- task :name, [:first_name, :last_name] do |t, args|
- puts "First name is #{args.first_name}"
- puts "Last name is #{args.last_name}"
- end
- The first argument of the block "t" is always bound to the current
- task object. The second argument "args" is an open-struct like object
- that allows access to the task arguments. Extra command line
- arguments to a task are ignored. Missing command line arguments are
- picked up from matching environment variables. If there are no
- matching environment variables, they are given the nil value.
- If you wish to specify default values for the arguments, you can use
- the with_defaults method in the task body. Here is the above example
- where we specify default values for the first and last names:
- task :name, [:first_name, :last_name] do |t, args|
- args.with_defaults(:first_name => "John", :last_name => "Dough")
- puts "First name is #{args.first_name}"
- puts "Last name is #{args.last_name}"
- end
- === Tasks that Expect Parameters and Have Prerequisites
- Tasks that use parameters have a slightly different format for
- prerequisites. Use the arrow notation to indicate the prerequisites
- for tasks with arguments. For example:
- task :name, [:first_name, :last_name] => [:pre_name] do |t, args|
- args.with_defaults(:first_name => "John", :last_name => "Dough")
- puts "First name is #{args.first_name}"
- puts "Last name is #{args.last_name}"
- end
- === Deprecated Task Parameters Format
- There is an older format for declaring task parameters that omitted
- the task argument array and used the :needs keyword to introduce the
- dependencies. That format is still supported for compatibility, but
- is not recommended for use. The older format may be dropped in future
- versions of rake.
- == Accessing Task Programmatically
- Sometimes it is useful to manipulate tasks programmatically in a
- Rakefile. To find a task object, use the <tt>:[]</tt> operator on the
- <tt>Rake::Task</tt>.
- === Programmatic Task Example
- For example, the following Rakefile defines two tasks. The :doit task
- simply prints a simple "DONE" message. The :dont class will lookup
- the doit class and remove (clear) all of its prerequisites and
- actions.
- task :doit do
- puts "DONE"
- end
- task :dont do
- Rake::Task[:doit].clear
- end
- Running this example:
- $ rake doit
- (in /Users/jim/working/git/rake/x)
- DONE
- $ rake dont doit
- (in /Users/jim/working/git/rake/x)
- $
- The ability to programmatically manipulate tasks gives rake very
- powerful meta-programming capabilities w.r.t. task execution, but
- should be used with cation.
- == 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
- <b>NOTE:</b> 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
- <b>NOTE:</b> +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 +Rake.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 invoked. 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.
- <b>NOTE:</b> Starting in Rake version 0.9.0, the top level +import+
- command is deprecated and we recommend using the scoped
- "+Rake.import+" command mentioned above. Future versions of Rake will
- drop support for the top level +import+ command.
- === Example:
- require 'rake/loaders/makefile'
- file ".depends.mf" => [SRC_LIST] do |t|
- sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}"
- end
- Rake.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 description. 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 interfere with
- each other.
- For example:
- namespace "main" do
- 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"
- == FileLists
- FileLists are the way Rake manages lists of files. You can treat a
- FileList as an array of strings for the most part, but FileLists
- support some additional operations.
- === Creating a FileList
- Creating a file list is easy. Just give it the list of file names:
- fl = FileList['file1.rb', file2.rb']
- Or give it a glob pattern:
- fl = FileList['*.rb']
- == Odds and Ends
- === do/end versus { }
- 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 parentheses 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
- prerequisites 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
- ----
- == See
- * README.rdoc -- Main documentation for Rake.
|