How would you package reusable gem build and release tasks with Rake::TaskLib, and how do Rake namespaces keep their names from clashing?
answer
- class ReleaseTasks < Rake::TaskLib
- initialize, yield self, define
- TaskLib includes Rake::DSL
- namespace :gem gives gem:build
- ^ reaches the parent namespace
basics
~10 sSubclass Rake::TaskLib, which includes Rake::DSL, collect options in initialize (yield self), then define the tasks inside namespace :gem so they become gem:build and gem:release. Each Rakefile adds the whole set with one ReleaseTasks.new call.
solid answer
~40 s`Rake::TaskLib` is the base class Rake's own libraries such as `Rake::PackageTask` use: it includes `Rake::DSL`, so `task`, `desc`, `file` and `namespace` work inside its methods. The convention is `initialize(name = :gem)` setting defaults, `yield self if block_given?` for configuration, then a `define` method that declares the tasks. Wrapping them in `namespace name do ... end` makes them `gem:build`, `gem:tag` and `gem:release`, so they do not collide with a project's own `build`. Inside a namespace, a bare `:build` resolves to the nearest `build`, searching outward through parent namespaces; `"^build"` starts one level up and `"rake:build"` means the top level. File task names are paths and are never namespaced. A consuming Rakefile just requires the file and calls `LedgerKit::ReleaseTasks.new { |t| t.gemspec = "ledger_kit.gemspec" }`.
code
ruby · 26 lines# lib/ledger_kit/release_tasks.rb
require "rake/tasklib"
class LedgerKit::ReleaseTasks < Rake::TaskLib
attr_accessor :gemspec, :version
def initialize(name = :gem)
@name, @gemspec = name, "ledger_kit.gemspec"
yield self if block_given?
define
end
def define
gem_file = "pkg/#{File.basename(gemspec, ".gemspec")}-#{version}.gem"
file gem_file => gemspec do
mkdir_p "pkg"
sh "gem", "build", gemspec, "-o", gem_file
end
namespace @name do
desc "Build #{gem_file}"
task build: gem_file
desc "Build and push #{gem_file}"
task(release: :build) { sh "gem", "push", gem_file }
end
end
endgo deeper
Know that namespace :gem turns task :build into gem:build and that you run it as rake gem:build.
Explain name resolution inside namespaces, including the outward search, ^ and rake:, and that file tasks are never namespaced.
Package shared automation as a Rake::TaskLib with a configurable namespace, documented tasks and extension points instead of copy-pasted Rakefiles.
Decide whether shared release tooling lives in a task library, an existing gem's tasks or CI templates, weighing drift against flexibility.
## The problem Several gems in one organisation need the same build, tag and release steps. Copying the Rakefile drifts; a shared set of tasks that each project configures is better. Rake provides two tools for that: **`Rake::TaskLib`** for packaging, **`namespace`** for naming. ## Rake::TaskLib `Rake::TaskLib` is a small base class: it includes `Rake::DSL` (so `task`, `desc`, `file`, `directory`, `namespace`, `sh` and the file helpers are available as instance methods) and `Rake::Cloneable`. Rake's own `Rake::PackageTask` follows a clear shape worth copying: 1. **`initialize`** stores defaults in attributes; 2. **`yield self if block_given?`** lets the caller configure it; 3. **`define`** declares the tasks using those attributes. Because tasks are registered in the global `Rake.application` when `define` runs, constructing the object is all a Rakefile has to do. ## Namespaces `namespace :gem do ... end` prefixes every ordinary task defined inside it: - `task :build` inside it becomes **`gem:build`**, run as `rake gem:build`; - namespaces nest: `gem:docs:build`; - `rake -T` shows the full names, so the listing groups naturally. Name resolution inside a namespace: | Reference inside `namespace :gem` | Resolves to | |---|---| | `:build` | `gem:build` if it exists, otherwise a `build` found by searching outward | | `"^build"` | the lookup starts in the **parent** namespace | | `"rake:build"` | the top-level `build` explicitly | | `"other:build"` | a task in another namespace | **File and directory tasks are not namespaced**: their names are paths, so `file "pkg/x.gem"` inside `namespace :gem` is still `pkg/x.gem`. ## Putting it together A library task class typically: - takes the namespace name as a constructor argument, so two instances can coexist (`:gem` and `:gem_pro`); - adds `desc` to every public task so `rake -T` documents it; - uses a file task for the `.gem` artefact and plain tasks for tagging and pushing; - keeps shelling out in `sh`, so failures stop the run. The consuming Rakefile then shrinks to a require and one constructor call, plus project-specific tasks such as `task default: "gem:build"`. ## Trade-offs - A task library hides steps; `rake -W gem:release` shows where the task was defined when someone needs to read it. - Extending a library task in the Rakefile — `task "gem:release" => :changelog` — adds a prerequisite without editing the library, because repeated declarations merge. - Clearing a library task with `Rake::Task["gem:release"].clear` is possible but makes the Rakefile harder to reason about; prefer configuration options. - Task libraries shipped by other gems may already cover the same steps; write your own only when the steps really differ.
- Inside namespace :gem, a task depends on :test, but only a top-level test task exists. Which task runs?The top-level `test`. Rake first looks for `gem:test`, and when it is missing searches the enclosing namespaces outward until it finds `test`. Writing `"rake:test"` makes the top-level target explicit, and `"^test"` starts the search in the parent namespace.
- Why does Rake::TaskLib include Rake::DSL?So the library's instance methods can call `task`, `desc`, `file`, `namespace`, `sh` and the file helpers directly. Without it those DSL methods are available only at the top level of a Rakefile, where Rake mixes the DSL into the main object.
saying these in an interview costs you the question
- Rake::TaskLib tasks must be copied into each Rakefile to run.
- A file task inside namespace :gem is named gem:pkg/x.gem.
- Inside a namespace, :build never falls back to a top-level build.
- Namespaced tasks are hidden from rake -T.
- Adding a prerequisite to a library task requires editing the library.