skip to content

In Rake, what is the difference between Rake::Task#invoke, #execute and #reenable, and why does a second invoke in one run do nothing?

level: middleimportance: should knowfreq 32%

answer

  1. Rake::Task["gem:build"] looks it up
  2. invoke: prerequisites, then actions, once
  3. execute: actions only, every time
  4. reenable clears the invoked flag
  5. a failed invoke re-raises

basics

~20 s

Rake::Task#invoke runs prerequisites and then the actions, but only on the first call in a run; later calls return immediately. #execute runs only the actions, every time, skipping prerequisites. #reenable resets the flag so invoke runs again.

solid answer

~40 s

`Rake::Task["gem:build"]` looks a task up. **`invoke`** is what `rake` itself uses: it marks the task as invoked, invokes its prerequisites, then runs its actions if `needed?` (always true for plain tasks, timestamp-based for file tasks). A second `invoke` in the same process returns immediately — or, if the first attempt raised, re-raises that exception. **`execute`** runs only the task's actions: no prerequisites, no invoked check, no `needed?` check, so it runs every time you call it. **`reenable`** clears the invoked flag (and the remembered exception), so the next `invoke` runs prerequisites and actions again, though the prerequisites keep their own flags. Calling `invoke` from inside another task's action is how you run a task conditionally or with computed arguments; `invoke("1.4.0")` passes task arguments positionally.

code

ruby · 16 lines
ruby
task :build_all do
  %w[1.3.9 1.4.0].each do |v|
    Rake::Task["gem:build"].reenable
    Rake::Task["gem:build"].invoke(v)
  end
end

namespace :gem do
  task :build, [:version] => :clean do |t, args|
    puts "building #{args.version}"
  end
end

task :clean do
  puts "clean"
end

go deeper

for a junior

Know that Rake::Task["name"].invoke runs another task from Ruby code.

for a middle

Explain invoke's once-per-run flag, execute's actions-only behaviour and when reenable is needed.

for a senior

Prefer declared prerequisites to imperative invoke calls, and use execute and reenable only deliberately, knowing their effect on stale artefacts.

for a principal

Keep automation graphs declarative so they stay testable and parallelisable, pushing imperative orchestration into plain Ruby methods.

## Tasks as objects Every task in a Rakefile is a **`Rake::Task`** object, found with `Rake::Task["name"]` (namespaced names use colons: `Rake::Task["gem:build"]`). Most Rakefiles never touch these objects, but calling tasks from other tasks, or from a test, means choosing between `invoke`, `execute` and `reenable`. ## invoke: the normal path `rake gem:release` calls `invoke` on the task. `invoke`: 1. checks whether the task was **already invoked** in this process — if so it returns at once, or re-raises the exception from the earlier failed attempt; 2. marks the task as invoked; 3. invokes each **prerequisite** in order, applying the same rules to them; 4. runs the task's **actions** only if `needed?` is true — always for plain tasks, and only when the file is missing or older than a prerequisite for file tasks. The invoked flag is why a shared prerequisite runs once, and why this surprises people: - `Rake::Task["gem:build"].invoke` twice in one action builds once; - calling a task in a loop with `invoke` runs it on the first iteration only. Arguments go in positionally: `Rake::Task["bump"].invoke("1.4.0")` is the in-Ruby form of `rake bump[1.4.0]`. ## execute: actions only `execute` runs the task's actions, full stop: - **no prerequisites** are invoked; - the **invoked flag is ignored** and not set, so it runs every time; - **`needed?` is not consulted**, so a file task's actions run even if the file is fresh. That makes it useful when you explicitly want the body again, and dangerous when the body silently depends on a prerequisite — for example `gem:push` executed without `gem:build` having produced the file. ## reenable: allow another invoke `reenable` resets the invoked flag and forgets any stored exception. After it, `invoke` behaves as on the first call: prerequisites are invoked again (each is still subject to its **own** flag, so already-run prerequisites stay skipped unless you reenable them too) and the actions run. ## Side by side | | `invoke` | `execute` | `reenable` then `invoke` | |---|---|---|---| | Runs prerequisites | yes | no | yes (those not yet run) | | Respects once-per-run | yes | no | flag was reset | | Checks `needed?` | yes | no | yes | | Typical use | calling a task from another task | re-running only the body | running a task twice deliberately | ## Patterns - **Conditional delegation**: `task :release do Rake::Task["gem:build"].invoke unless File.exist?(GEM_FILE) end` — but a declared prerequisite or a file task is usually clearer. - **Loop over versions**: call `reenable` before each `invoke`, or move the body into a Ruby method and call that. - **Tests of a Rakefile**: load it into a `Rake::Application`, invoke the task, and `reenable` between examples, since the invoked flag persists for the process. ## Failure memory If a task raised during its first invocation, later `invoke` calls re-raise the same exception rather than retrying. Under `multitask` this means every parallel dependent sees the one failure instead of rerunning a broken step. `reenable` clears it when a retry is really wanted.

  • Why might Rake::Task["gem:push"].execute upload a stale gem?
    `execute` runs only `gem:push`'s actions. It does not invoke prerequisites such as `gem:build`, so if the package in `pkg/` is left over from an earlier version, it gets pushed. Use `invoke`, which runs the build first, or make the gem file a file task prerequisite.
  • After reenable, do a task's prerequisites run again?
    Only if they have not already run. `reenable` resets the flag on that one task; each prerequisite keeps its own invoked flag, so one that already ran in this process is skipped. Reenable the prerequisites as well if they must repeat.
  • A task raised on its first invoke. What does a second invoke do?
    It re-raises the stored exception without running the task again. Rake remembers the failure on the task object so that dependents, including parallel ones under `multitask`, all fail consistently. `reenable` clears the stored exception if you truly want a retry.

saying these in an interview costs you the question

  • execute runs prerequisites first, just like invoke.
  • Calling invoke twice in one run executes the task twice.
  • reenable also resets every prerequisite's invoked flag.
  • execute skips a file task whose file is already up to date.
  • A failed task is retried automatically on its next invoke.