Limit scope of rule defined inside namespace - ruby

I have the following Rakefile (this is a simplified example):
namespace :green do
rule(/^build:/) do |t|
puts "[green] #{t}"
end
task :start do
puts '[green] start'
end
task run: ['build:app', :start]
end
namespace :blue do
rule(/^build:/) do |t|
puts "[blue] #{t}"
end
task :start do
puts '[blue] start'
end
task run: ['build:app', :start]
end
I would like each "build" rule to apply only within the namespace where it's defined. In other words, this is what I want to happen:
$ rake blue:run
[blue] build:app
[blue] start
But what actually happens is this (with Rake 12.3.1):
$ rake blue:run
[green] build:app
[blue] start
Is there a way to limit the scope of the "build" rules so that the rule defined in the "green" namespace isn't accessible from the "blue" namespace?

It appears that Rake doesn't support this natively. Tasks are scoped to the namespace they are defined in (by adding the scope path as a prefix), but rules get no such prefix.
I was able to get this to work by monkey-patching Rake, which is not ideal:
# Monkey-patch rake
module Rake
module TaskManager
# Copied from rake 12.3.1 and enhanced for scoped rules
def lookup_in_scope(name, scope)
loop do
tn = scope.path_with_task_name(name)
task = #tasks[tn]
return task if task
break if scope.empty?
# BEGIN ADDED LINES
task = enhance_with_matching_rule(tn)
return task if task
# END ADDED LINES
scope = scope.tail
end
nil
end
end
module DSL
# Create a rule inside a namespace scope
def scoped_rule(name, &block)
pattern = "^#{Rake.application.current_scope.path}:#{name}:"
Rake.application.create_rule(Regexp.new(pattern), &block)
end
end
end
namespace :green do
scoped_rule :build do |t|
puts t
end
task :start do |t|
puts t
end
task run: ['build:app', :start]
end
namespace :blue do
scoped_rule :build do |t|
puts t
end
task :start do |t|
puts t
end
task run: ['build:app', :start]
end
Output:
$ rake green:run
green:build:app
green:start
$ rake blue:run
blue:build:app
blue:start

I know that it's not the best solution, but I think it maybe can help you.
rule(/^build*/) do |t|
Rake::Task["green:build"].invoke if ARGV[0].start_with? "green"
Rake::Task["blue:build"].invoke if ARGV[0].start_with? "blue"
end
namespace :green do
task :build do
puts '[green] build'
end
task :start do
puts '[green] start'
end
task run: ['build.app', :start]
end
namespace :blue do
task :build do
puts '[blue] build'
end
task :start do
puts '[blue] start'
end
task run: ['build.app', :start]
end
Testing it we have:
rake green:run # [green] build
# [green] start
rake blue:run # [blue] build
# [blue] start

Related

How to know which task is being executed with rake

I'd like to know from within a rake ask what is the name of the task that is being executed? How to do this? For example, in the code bellow, when I run rake my_incredible_task, it should print "my_incredible_task":
task :boot do
task_name = <what comes here?>
puts task_name
end
task :my_incredible_task => [:boot] do
#do some stuff
end
I'm not sure there is a way out of the box, however you can do something like this:
require 'rake'
module Rake
class Application
attr_accessor :current_task_name
end
class Task
alias :old_execute :execute
def execute(args=nil)
Rake.application.current_task_name = #name
old_execute(args)
end
end
end
namespace :so do
task :my_task do
puts Rake.application.current_task_name
end
end
Not sure how this will work out with tasks that run in parallel...
You can use a parameter in the block :
task :my_incredible_task do |t|
puts t.name
end
EDIT
You could use methods instead :
task :boot do |t|
boot(t.name)
end
task :my_incredible_task do |t|
boot(t.name)
#do some stuff
end
def boot task_name
# do stuff
end

Rake before task hook

Is there a straight forward way to modify a Rake task to run some bit of code before running the existing task? I'm looking for something equivalent to enhance, that runs at the beginning rather than the end of the task.
Rake::Task['lame'].enhance(['i_run_afterwards_ha_ha'])
You can use the dependency of Rake task to do that, and the fact that Rake allows you to redefine existing task.
Rakefile
task :your_task do
puts 'your_task'
end
task :before do
puts "before"
end
task :your_task => :before
As result
$ rake your_task
before
your_task
Or you could use the rake-hooks gem to do before and after hooks:
https://github.com/guillermo/rake-hooks
namespace :greetings do
task :hola do puts "Hola!" end ;
task :bonjour do puts "Bonjour!" end ;
task :gday do puts "G'day!" end ;
end
before "greetings:hola", "greetings:bonjour", "greetings:gday" do
puts "Hello!"
end
rake greetings:hola # => "Hello! Hola!"

How do I run multiple Rake tasks programmatically at once?

At the command line I can run multiple tasks like this
rake environment task1 task2 task3
How can I do this programmatically? I know that I can run one task like this
Rake::Task['task1'].invoke
You can call two tasks:
require 'rake'
task :task1 do |t|
p t
end
task :task2 do |t|
p t
end
Rake::Task["task1"].invoke
Rake::Task["task2"].invoke
I would prefer a new tast with prerequisites:
require 'rake'
task :task1 do |t|
p t
end
task :task2 do |t|
p t
end
desc "Common task"
task :all => [ :task1, :task2 ]
Rake::Task["all"].invoke
If I misunderstood your question and you want to execute the same task twice: You can reenable tasks:
require 'rake'
task :task1 do |t|
p t
end
Rake::Task["task1"].invoke
Rake::Task["task1"].reenable
Rake::Task["task1"].invoke
Make a rake task for it :P
# in /lib/tasks/some_file.rake
namespace :myjobs do
desc "Doing work, son"
task :do_work => :environment do
Rake::Task['resque:work'].invoke
start_some_other_task
end
def start_some_other_task
# custom code here
end
end
Then just call it:
rake myjobs:do_work

Is it possible to get a list of all available rake tasks in a namespace?

Is it possible from within a rake task to get a list of tasks in a namespace? A sort of programatic 'rake -T db' ?
I've found out the answer:
tasks = Rake.application.tasks
This will return an array of Rake::Task objects that can be examined. Further details at http://rake.rubyforge.org/
As you wrote, with Rake.application.tasks you get all tasks.
But inside the namespace, you can select only the tasks of the namespace (task mytest:tasklist)
And you may restrict the tasks to a namespace (task tasklist_mytest).
require 'rake'
namespace :mytest do |ns|
task :foo do |t|
puts "You called task #{t}"
end
task :bar do |t|
puts "You called task #{t}"
end
desc 'Get tasks inside actual namespace'
task :tasklist do
puts 'All tasks of "mytest":'
puts ns.tasks #ns is defined as block-argument
end
end
desc 'Get all tasks'
task :tasklist do
puts 'All tasks:'
puts Rake.application.tasks
end
desc 'Get tasks outside the namespace'
task :tasklist_mytest do
puts 'All tasks of "mytest":'
Rake.application.in_namespace(:mytest){|x|
puts x.tasks
}
end
if $0 == __FILE__
Rake.application['tasklist'].invoke() #all tasks
Rake.application['mytest:tasklist'].invoke() #tasks of mytest
Rake.application['tasklist_mytest'].invoke() #tasks of mytest
end
You can use the grep command like this
desc 'Test'
task :test do
# You can change db: by any other namespaces
result = %x[rake -T | sed -n '/db:/{/grep/!p;}' | awk '{print$2}']
result.each_line do |t|
puts t # Where t is your task name
end
end

Default task for namespace in Rake

Given something like:
namespace :my_tasks do
task :foo do
do_something
end
task :bar do
do_something_else
end
task :all => [:foo, :bar]
end
How do I make :all be the default task, so that running rake my_tasks will call it (instead of having to call rake my_tasks:all)?
Place it outside the namespace like this:
namespace :my_tasks do
task :foo do
do_something
end
task :bar do
do_something_else
end
end
task :all => ["my_tasks:foo", "my_tasks:bar"]
Also... if your tasks require arguments then:
namespace :my_tasks do
task :foo, :arg1, :arg2 do |t, args|
do_something
end
task :bar, :arg1, :arg2 do |t, args|
do_something_else
end
end
task :my_tasks, :arg1, :arg2 do |t, args|
Rake::Task["my_tasks:foo"].invoke( args.arg1, args.arg2 )
Rake::Task["my_tasks:bar"].invoke( args.arg1, args.arg2 )
end
Notice how in the 2nd example you can call the task the same name as the namespace, ie 'my_tasks'
Not very intuitive, but you can have a namespace and a task that have the same name, and that effectively gives you what you want. For instance
namespace :my_task do
task :foo do
do_foo
end
task :bar do
do_bar
end
end
task :my_task do
Rake::Task['my_task:foo'].invoke
Rake::Task['my_task:bar'].invoke
end
Now you can run commands like,
rake my_task:foo
and
rake my_task
I suggest you to use this if you have lots of tasks in the namespace.
task :my_tasks do
Rake.application.in_namespace(:my_tasks){|namespace| namespace.tasks.each(&:invoke)}
end
And then you can run all tasks in the namespace by:
rake my_tasks
With this, you don't need to worry to change your :all task when you add new tasks into that namespace.
I use this Rakefile for cucumber:
require 'cucumber'
require 'cucumber/rake/task'
namespace :features do
Cucumber::Rake::Task.new(:fast) do |t|
t.profile = 'fast'
end
Cucumber::Rake::Task.new(:slow) do |t|
t.profile = 'slow'
end
task :ci => [:fast, :slow]
end
task :default => "features:ci"
Then if I type just:
rake
It runs the default task, which runs both fast and slow tests.
I learned this from Cheezy's blog.
The way I'm reading obvio171's question is that he is asking1) for a systematic way to invoke a certain task in a namespace by invoking the namespace as a task.
I've frequently encountered the same need. I like to logically group tasks into namespaces. Often that grouping resembles a hierarchy. Hence the desire to invoke the group makes very much sense to me.
Here's my take:
module Rake::DSL
def group(name, &block)
ns = namespace name, &block
default = ns[:default]
task name => "#{name}:default" if default
ns
end
end
group :foo do
task :foo1 do |t| puts t.name end
task :foo2 do |t| puts t.name end
task :default => [:foo1, :foo2]
end
task :default => :foo
1)...or was asking, years ago. Nonetheless a still interesting question.
Add the following task outside of the namespace:
desc "Run all my tasks"
task :my_tasks => ["my_tasks:all"]
Keep in mind, that you can have a task with the same name as the namespace.
And hier a bigger example, that shows, how you can make use of tasks, which have the same name as the namespace, even when nesting namespaces:
namespace :job1 do
task :do_something1 do
puts "job1:do_something1"
end
task :do_something2 do
puts "job1:do_something2"
end
task :all => [:do_something1, :do_something2]
end
desc "Job 1"
task :job1 => ["job1:all"]
# You do not need the "all"-task, but it might be handier to have one.
namespace :job2 do
task :do_something1 do
puts "job2:do_something1"
end
task :do_something2 do
puts "job2:do_something2"
end
end
desc "Job 2"
task :job2 => ["job2:do_something1", "job2:do_something2"]
namespace :superjob do
namespace :job1 do
task :do_something1 do
puts "superjob:job1:do_something1"
end
task :do_something2 do
puts "superjob:job1:do_something2"
end
end
desc "Job 1 in Superjob"
task :job1 => ["job1:do_something1", "job1:do_something2"]
namespace :job2 do
task :do_something1 do
puts "superjob:job2:do_something1"
end
task :do_something2 do
puts "superjob:job2:do_something2"
end
end
desc "Job 2 in Superjob"
task :job2 => ["job2:do_something1", "job2:do_something2"]
end
desc "My Super Job"
task :superjob => ["superjob:job1", "superjob:job2"]
# Do them all just by calling "$ rake"
task :default => [:job1, :job2, :superjob]
Just copy it and try it out.
Based on Rocky's solution Default task for namespace in Rake
And this dexter's answer Is there a way to know the current rake task?
namespace :root do
namespace :foo do
end
namespace :target do
task :all do |task_all|
Rake.application.in_namespace(task_all.scope.path) do |ns|
ns.tasks.each { |task| task.invoke unless task.name == task_all.name }
end
end
task :one do
end
task :another do
end
end
end
Combining Szymon LipiƄski's and Shyam Habarakada's answers, here is what I think is the most idiomatic and consise answer:
namespace :my_tasks do
task :foo do
do_something
end
task :bar do
do_something_else
end
end
task :my_tasks => ["my_tasks:foo", "my_tasks:bar"]
allows you to do rake my_tasks while avoiding cumbersome invocation of the subtasks.

Resources