In Rake, what is the difference between Rake::Task#invoke, #execute and #reenable, and why does a second invoke in one run do nothing?
answer
- Rake::Task["gem:build"] looks it up
- invoke: prerequisites, then actions, once
- execute: actions only, every time
- reenable clears the invoked flag
- a failed invoke re-raises
basics
~20 sRake::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 linestask :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"
endgo deeper
Know that Rake::Task["name"].invoke runs another task from Ruby code.
Explain invoke's once-per-run flag, execute's actions-only behaviour and when reenable is needed.
Prefer declared prerequisites to imperative invoke calls, and use execute and reenable only deliberately, knowing their effect on stale artefacts.
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.