436

I have a Rakefile that compiles the project in two ways, according to the global variable $build_type, which can be :debug or :release (the results go in separate directories):

task :build => [:some_other_tasks] do
end

I wish to create a task that compiles the project with both configurations in turn, something like this:

task :build_all do
  [ :debug, :release ].each do |t|
    $build_type = t
    # call task :build with all the tasks it depends on (?)
  end
end

Is there a way to call a task as if it were a method? Or how can I achieve anything similar?

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • I'd go with the community vote and pick the answer upvoted 221 times (at time of writing). The original poster has left SO – MPritchard Mar 07 '13 at 08:07
  • 1
    correct answer is http://stackoverflow.com/a/1290119/1536309 – Blair Anderson Apr 27 '17 at 22:26
  • FYI, using something like `Rake::Task["build"].invoke` can be much more performant than using `system rake build` because it doesn't have to create a new thread and load up the Rails environment, which `system rake build` does have to do. – Joshua Pinter Nov 18 '18 at 19:31

7 Answers7

668

If you need the task to behave as a method, how about using an actual method?

task :build => [:some_other_tasks] do
  build
end

task :build_all do
  [:debug, :release].each { |t| build t }
end

def build(type = :debug)
  # ...
end

If you'd rather stick to rake's idioms, here are your possibilities, compiled from past answers:

  • This always executes the task, but it doesn't execute its dependencies:

    Rake::Task["build"].execute
    
  • This one executes the dependencies, but it only executes the task if it has not already been invoked:

    Rake::Task["build"].invoke
    
  • This first resets the task's already_invoked state, allowing the task to then be executed again, dependencies and all:

    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
    
  • Note that dependencies already invoked are not automatically re-executed unless they are re-enabled. In Rake >= 10.3.2, you can use the following to re-enable those as well:

    Rake::Task["build"].all_prerequisite_tasks.each(&:reenable)
    
swrobel
  • 4,053
  • 2
  • 33
  • 42
kch
  • 77,385
  • 46
  • 136
  • 148
  • 101
    Note that if your tasks are in namespaces, you must include the namespace when you invoke the task. Eg. `Rake::Task['db:reset'].invoke` – David Tuite Jul 06 '11 at 23:04
  • 138
    If the task in questions takes arguments, you can pass them as arguments to #invoke. Eg. `Rake::Task['with:args'].invoke("pizza")` – Trotter Aug 30 '11 at 01:01
  • 28
    If you need to set an environment variable, do that before calling invoke. For example: `ENV['VERSION'] = '20110408170816'; Rake::Task['db:migrate'].invoke` See [here](http://rubyforge.org/pipermail/rake-devel/2009-January/000712.html) for more explanation. – Michael Stalker Jun 18 '13 at 14:25
  • 16
    I recently discovered `#reenable()` doesn't re-enable pre-req's, and needed it. [This addition](https://github.com/jimweirich/rake/pull/168) to Rake (>= 10.3.2), [`#all_prerequisite_tasks()`](http://ruby-doc.org/stdlib-2.1.1/libdoc/rake/rdoc/Rake/Task.html#method-i-all_prerequisite_tasks) will iterate all tasks, including pre-req's of pre-req's. So, `Rake::Task[task].all_prerequisite_tasks.each &:reenable` – Richard Michael Jun 04 '14 at 15:24
  • 4
    @kch, can you string these together (like on the commandline `rake db:reset db:migrate` for example). Can you do something like: `Rake::Task["db:reset", "db:migrate"].invoke` – Jeff Mar 25 '15 at 15:51
  • 1
    Taken together, aren't these two statements effectively contradictory? "This first resets the task's already_invoked state, allowing the task to then be executed again, dependencies and all [...] Notice that dependencies already invoked are not re-executed" If a task has already been invoked, all of its dependencies would have had to have been invoked as well, right? So simply reenabling and then invoking will never result in any of the tasks's dependencies being re-run, unless you first reenable those tasks as well. – Ajedi32 Aug 24 '16 at 14:51
131

for example:

Rake::Task["db:migrate"].invoke
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
Marcin Urbanski
  • 2,493
  • 1
  • 16
  • 17
  • 7
    This invokes the task only if it wasn't already invoked. But I need to invoke the tasks with all other tasks it depends on twice. –  Feb 24 '09 at 08:45
61
task :build_all do
  [ :debug, :release ].each do |t|
    $build_type = t
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

That should sort you out, just needed the same thing myself.

darkliquid
  • 4,003
  • 1
  • 25
  • 17
20
task :invoke_another_task do
  # some code
  Rake::Task["another:task"].invoke
end
Neeraj Kumar
  • 6,045
  • 2
  • 31
  • 23
  • One of the reason why I needed a solution like this, is because rake task loading takes a lot of time. By implementing a solution like above, will it save on loading time? – Dipan Mehta Dec 11 '18 at 10:44
14
task :build_all do
  [ :debug, :release ].each do |t|
    $build_type = t
    Rake::Task["build"].execute
  end
end
pjb3
  • 5,191
  • 5
  • 25
  • 44
  • It doesn't work, because it just executes the body of the :build task and doesn't invoke the tasks that depend on it. –  Feb 23 '09 at 15:54
4

If you want each task to run regardless of any failures, you can do something like:

task :build_all do
  [:debug, :release].each do |t| 
    ts = 0
    begin  
      Rake::Task["build"].invoke(t)
    rescue
      ts = 1
      next
    ensure
      Rake::Task["build"].reenable # If you need to reenable
    end
    return ts # Return exit code 1 if any failed, 0 if all success
  end
end
bbbco
  • 1,508
  • 1
  • 10
  • 25
-1

I would suggest not to create general debug and release tasks if the project is really something that gets compiled and so results in files. You should go with file-tasks which is quite doable in your example, as you state, that your output goes into different directories. Say your project just compiles a test.c file to out/debug/test.out and out/release/test.out with gcc you could setup your project like this:

WAYS = ['debug', 'release']
FLAGS = {}
FLAGS['debug'] = '-g'
FLAGS['release'] = '-O'
def out_dir(way)
  File.join('out', way)
end
def out_file(way)
  File.join(out_dir(way), 'test.out')
end
WAYS.each do |way|
  desc "create output directory for #{way}"
  directory out_dir(way)

  desc "build in the #{way}-way"
  file out_file(way) => [out_dir(way), 'test.c'] do |t|
    sh "gcc #{FLAGS[way]} -c test.c -o #{t.name}"
  end
end
desc 'build all ways'
task :all => WAYS.map{|way|out_file(way)}

task :default => [:all]

This setup can be used like:

rake all # (builds debug and release)
rake debug # (builds only debug)
rake release # (builds only release)

This does a little more as asked for, but shows my points:

  1. output directories are created, as necessary.
  2. the files are only recompiled if needed (this example is only correct for the simplest of test.c files).
  3. you have all tasks readily at hand if you want to trigger the release build or the debug build.
  4. this example includes a way to also define small differences between debug and release-builds.
  5. no need to reenable a build-task that is parametrized with a global variable, because now the different builds have different tasks. the codereuse of the build-task is done by reusing the code to define the build-tasks. see how the loop does not execute the same task twice, but instead created tasks, that can later be triggered (either by the all-task or be choosing one of them on the rake commandline).
Gizmomogwai
  • 2,496
  • 1
  • 20
  • 21