Question

How to Run One Rake Task from Another in Ruby

rubyrake

Question

I have a Rakefile that builds a project in two different ways based on the global variable $build_type, which can be either :debug or :release. Each build writes its output to a separate directory.

task :build => [:some_other_tasks] do
  # build logic here
end

I want to create another task that builds the project in both configurations, one after the other, something like this:

task :build_all do
  [:debug, :release].each do |t|
    $build_type = t
    # run :build together with all of its prerequisite tasks
  end
end

Is there a way to call a Rake task from inside another task as if it were a method? If not, how can I achieve similar behavior correctly in Rake?

Short Answer

By the end of this page, you will understand how Rake tasks can call other tasks, how invoke works, why tasks usually run only once, and how reenable lets you run the same task multiple times in a loop. You will also see cleaner alternatives, such as extracting shared build logic into plain Ruby methods or using task arguments instead of global variables.

Concept

Rake tasks are not ordinary Ruby methods, but they can be triggered programmatically from inside other tasks.

The key idea is that a Rake task is an object managed by Rake's task system. You usually run tasks from the command line:

rake build

But inside a Rakefile, you can also look up a task and execute it:

Rake::Task["build"].invoke

Important behavior: tasks run only once by default

This is the part that surprises many beginners.

When you call:

Rake::Task["build"].invoke

Rake remembers that build has already run. If you call invoke again later in the same process, it will not run a second time unless you reset it with:

Rake::Task["build"].reenable

This matters a lot in your case because you want to run the same build task twice:

  • once with $build_type = :debug
  • once with $build_type = :release

Without reenable, only the first call will actually execute.

Why this concept matters

In real projects, build and automation scripts often need to:

  • run setup before another task
  • execute the same task for multiple environments
  • generate multiple output variants
  • chain tasks together safely

Understanding invoke, prerequisites, and reenable helps you write automation that behaves predictably.

A better design than global state

Your example uses a global variable:

$build_type = :debug

That works, but global state can make task behavior harder to reason about. In many real codebases, developers prefer one of these patterns:

  • task arguments
  • environment variables
  • extracting build logic into a Ruby method
  • separate tasks like build:debug and build:release

Still, if you want to keep the current design, Rake can support it.

Mental Model

Think of a Rake task like a checklist item in a build system.

  • A Ruby method is like a function you can call whenever you want.
  • A Rake task is more like a named job on a project board.

When Rake completes a job, it marks it as done. If you ask for it again, Rake says, “That one already ran.”

If you really want to run it again, you must explicitly clear that done-state with reenable.

So the flow is:

  1. Set the build mode
  2. Tell Rake to run the build job
  3. Reset the build job
  4. Set the next build mode
  5. Run it again

That is why Rake tasks are similar to methods in some ways, but not identical.

Take Quiz

Syntax and Examples

The core API for running tasks from other tasks is:

Rake::Task["task_name"].invoke

If you want to run the same task again in the same process:

Rake::Task["task_name"].reenable
Rake::Task["task_name"].invoke

Basic example

task :hello do
  puts "Hello from :hello"
end

task :greet do
  Rake::Task["hello"].invoke
end

Running:

rake greet

will print:

Hello from :hello

Running a task multiple times

task :build do
  puts "Building #{$build_type}"
end

task :build_all do
  [:debug, :release].each do |type|
    $build_type = type
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

This works because:

  • reenable clears the task's already-run state
  • invoke runs the task and its prerequisites

Your use case

task :build => [:some_other_tasks] do
  puts "Compiling in #{$build_type} mode"
end

task :build_all do
  [:debug, :release].each do |type|
    $build_type = type
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

When build_all runs, Rake will:

  • set $build_type to :debug
  • run some_other_tasks and then build
  • reenable build
  • set $build_type to :release
  • run build again

Extracting shared logic into a method

Often the cleanest approach is to keep Rake for orchestration and put build logic into normal Ruby methods:

def build_project(type)
  puts "Compiling in #{type} mode"
end

task :build do
  build_project($build_type)
end

task :build_all do
  [:debug, :release].each do |type|
    build_project(type)
  end
end

This avoids some of the complexity of re-running the same task object.

Step by Step Execution

Consider this example:

task :prepare do
  puts "Preparing files"
end

task :build => [:prepare] do
  puts "Building #{$build_type}"
end

task :build_all do
  [:debug, :release].each do |type|
    $build_type = type
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

If you run:

rake build_all

here is what happens.

First iteration: :debug

$build_type = :debug

Now the global variable holds :debug.

Rake::Task["build"].reenable

This makes sure build is allowed to run, even if it ran earlier.

Rake::Task["build"].invoke

Rake checks prerequisites first:

  • prepare runs
  • then build runs

Output:

Preparing files
Building debug

Second iteration: :release

$build_type = :release

Now the build mode changes.

Rake::Task["build"].reenable
Rake::Task["build"].invoke

build is allowed to run again.

One subtle point: prerequisite tasks also have run-once behavior. Depending on what you need, you may also need to reenable them.

If prepare must run again too, then do this:

task :build_all do
  [:debug, :release].each do |type|
    $build_type = type
    Rake::Task["prepare"].reenable
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

Then output becomes:

Preparing files
Building debug
Preparing files
Building release

Key takeaway

Re-enabling build only resets build itself. If its prerequisites also need to rerun, reenable them too.

Real World Use Cases

Running tasks from other tasks is common in automation and build systems.

Building multiple variants

For example:

  • debug and release builds
  • staging and production assets
  • desktop and mobile bundles

Database workflows

A task may orchestrate several smaller tasks:

  • create database
  • load schema
  • seed sample data
  • run checks

CI/CD pipelines

A top-level task might run:

  • lint
  • test
  • package
  • deploy

Data processing

A batch task may process several input modes:

task :process_all do
  [:csv, :json, :xml].each do |format|
    # run processing task for each format
  end
end

Project setup scripts

A setup task might trigger tasks for:

  • installing dependencies
  • generating config files
  • compiling assets
  • preparing local data

These are all examples of Rake acting as a task orchestrator.

Take Quiz

Real Codebase Usage

In real projects, developers often use Rake tasks as thin wrappers around reusable Ruby code.

Common pattern: orchestration in Rake, logic in methods/classes

def compile_project(type)
  puts "Compiling #{type} build"
  # real build steps
end

task :build_debug do
  compile_project(:debug)
end

task :build_release do
  compile_project(:release)
end

task :build_all do
  compile_project(:debug)
  compile_project(:release)
end

This is easier to test and avoids hidden global state.

Guard clauses and validation

Developers often validate inputs before running work:

def compile_project(type)
  unless [:debug, :release].include?(type)
    raise ArgumentError, "Unknown build type: #{type}"
  end

  puts "Compiling #{type} build"
end

Namespaced tasks

Real Rakefiles often organize tasks clearly:

namespace :build do
  task :debug do
    compile_project(:debug)
  end

  task :release do
    compile_project(:release)
  end

  task :all => [:debug, :release]
end

This is often cleaner than switching a global variable.

Environment-based configuration

Another common pattern is reading from ENV:

task :build do
  type = ENV["BUILD_TYPE"] || "debug"
  puts "Compiling #{type} build"
end

This lets developers run:

rake build BUILD_TYPE=release

Error handling

In build scripts, it is common to fail early:

def compile_project(type)
  raise "Source directory missing" unless Dir.exist?("src")
  puts "Compiling #{type} build"
end

This makes automation safer and easier to debug.

Common Mistakes

1. Assuming invoke behaves like a normal method call

Beginners often expect this to run every time:

Rake::Task["build"].invoke
Rake::Task["build"].invoke

But the second call usually does nothing.

Fix

Use reenable before invoking again:

Rake::Task["build"].reenable
Rake::Task["build"].invoke

2. Forgetting that prerequisites may also need re-enabling

Broken expectation:

task :prepare do
  puts "Preparing"
end

task :build => [:prepare] do
  puts "Building"
end

If build is reenabled and invoked again, prepare may still not rerun.

Fix

Reenable prerequisite tasks if needed:

Rake::Task["prepare"].reenable
Rake::Task["build"].reenable
Rake::Task["build"].invoke

3. Using global variables carelessly

This works:

$build_type = :debug

But globals can make behavior harder to follow, especially in larger files.

Better options

  • use task arguments
  • use environment variables
  • extract shared logic into methods
  • create separate tasks

4. Putting too much logic directly inside tasks

Less maintainable:

task :build_all do
  # lots of file operations
  # lots of conditionals
  # lots of shell commands
end

Better approach

Move logic into Ruby methods or classes, and keep the task focused on orchestration.

5. Mixing task names and method names

This is invalid thinking:

build()

unless you actually defined a Ruby method named build.

A Rake task is not automatically a Ruby method.

Correct way

Rake::Task["build"].invoke

6. Ignoring task arguments as a cleaner design

Instead of mutating a global variable repeatedly, a more explicit approach may be better.

For example:

task :build, [:type] do |t, args|
  puts "Building #{args[:type]}"
end

Then invoke with arguments where appropriate.

Comparisons

ApproachHow it worksGood forDrawbacks
Rake::Task["build"].invokeRuns a named task and its prerequisitesTriggering one task from anotherRuns only once unless reenabled
reenable + invokeResets the task, then runs it againRepeating the same task in a loopEasy to forget prerequisites may also need reset
Plain Ruby methodExtract logic into def build_project(type)Reusable logic, easy testingNot automatically part of Rake dependency graph
Separate tasksbuild:debug, build:release, build:allClear build targetsCan create more task definitions
Environment variablesENV["BUILD_TYPE"]CLI flexibilityConfiguration is more implicit than arguments
Task argumentstask :build, [:type]Explicit inputsSlightly more syntax to learn

invoke vs execute

Rake also has execute, but beginners should usually prefer invoke.

MethodRuns prerequisites?Honors already-invoked state?Typical use
invokeYesYesNormal task execution
executeNoNoLow-level direct execution

For most cases, especially when you want the task to behave like a normal Rake task, use invoke.

Take Quiz

Cheat Sheet

# Run another task
Rake::Task["build"].invoke

# Allow a task to run again
Rake::Task["build"].reenable

# Run it again after re-enabling
Rake::Task["build"].invoke

Rules to remember

  • A Rake task is not the same as a Ruby method.
  • Use Rake::Task["name"].invoke to run a task from another task.
  • invoke runs prerequisites too.
  • A task normally runs only once per process.
  • Use reenable if you need to run it again.
  • If prerequisites must rerun too, reenable them as well.

Common pattern

task :build_all do
  [:debug, :release].each do |type|
    $build_type = type
    Rake::Task["build"].reenable
    Rake::Task["build"].invoke
  end
end

Cleaner alternative

def build_project(type)
  puts "Building #{type}"
end

task :build_all do
  [:debug, :release].each do |type|
    build_project(type)
  end
end

Edge cases

  • invoke again without reenable -> task will not rerun
  • reenable only resets that task, not automatically every prerequisite
  • global variables work, but can make builds less clear

FAQ

Can one Rake task call another?

Yes. Use:

Rake::Task["task_name"].invoke

Why does my Rake task only run once inside a loop?

Because invoke marks the task as already executed. Later calls in the same process do nothing unless you call reenable first.

How do I run the same Rake task twice?

Use:

Rake::Task["build"].reenable
Rake::Task["build"].invoke

Does invoke also run prerequisite tasks?

Yes. That is one reason it is usually preferred over execute.

Should I use a global variable like $build_type?

You can, but it is often better to use task arguments, environment variables, or a plain Ruby method with parameters.

What is the difference between a Rake task and a Ruby method?

A Ruby method is normal executable code you can call directly. A Rake task is a named automation unit managed by Rake, with prerequisite handling and run-once behavior.

What if a prerequisite task also needs to run again?

Reenable that prerequisite task too before invoking the main task again.

Is execute better than invoke?

Usually no for this use case. invoke is safer because it follows normal Rake behavior, including prerequisites.

Take Quiz

Mini Project

Description

Create a small Rake-based build script that generates two output files: one for a debug build and one for a release build. This project demonstrates how to run one task from another, how prerequisites work, and why reenable is needed when the same task must run more than once.

Goal

Build both debug and release outputs from a single build_all task using proper Rake task invocation.

Requirements

  • Create a prepare task that ensures an output directory exists.
  • Create a build task that writes a file based on the current build type.
  • Create a build_all task that runs the build twice: once for :debug and once for :release.
  • Ensure the build task can run multiple times in one execution.
  • Make the generated files easy to verify after running the task.
Take Quiz

Keep learning

Related questions

View more