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:debugandbuild: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:
- Set the build mode
- Tell Rake to run the
buildjob - Reset the
buildjob - Set the next build mode
- Run it again
That is why Rake tasks are similar to methods in some ways, but not identical.
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:
reenableclears the task's already-run stateinvokeruns 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_typeto:debug - run
some_other_tasksand thenbuild - reenable
build - set
$build_typeto:release - run
buildagain
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:
prepareruns- then
buildruns
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.
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
| Approach | How it works | Good for | Drawbacks |
|---|---|---|---|
Rake::Task["build"].invoke | Runs a named task and its prerequisites | Triggering one task from another | Runs only once unless reenabled |
reenable + invoke | Resets the task, then runs it again | Repeating the same task in a loop | Easy to forget prerequisites may also need reset |
| Plain Ruby method | Extract logic into def build_project(type) | Reusable logic, easy testing | Not automatically part of Rake dependency graph |
| Separate tasks | build:debug, build:release, build:all | Clear build targets | Can create more task definitions |
| Environment variables | ENV["BUILD_TYPE"] | CLI flexibility | Configuration is more implicit than arguments |
| Task arguments | task :build, [:type] | Explicit inputs | Slightly more syntax to learn |
invoke vs execute
Rake also has execute, but beginners should usually prefer invoke.
| Method | Runs prerequisites? | Honors already-invoked state? | Typical use |
|---|---|---|---|
invoke | Yes | Yes | Normal task execution |
execute | No | No | Low-level direct execution |
For most cases, especially when you want the task to behave like a normal Rake task, use invoke.
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"].invoketo run a task from another task. invokeruns prerequisites too.- A task normally runs only once per process.
- Use
reenableif 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
invokeagain withoutreenable-> task will not rerunreenableonly 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.
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
preparetask that ensures an output directory exists. - Create a
buildtask that writes a file based on the current build type. - Create a
build_alltask that runs the build twice: once for:debugand 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.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.