Skip to content

New dsl method: lazy(&block) #339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module Rake; end
require "rake/late_time"
require "rake/name_space"
require "rake/task_manager"
require "rake/lazy_task_definition"
require "rake/application"
require "rake/backtrace"

Expand Down
27 changes: 19 additions & 8 deletions lib/rake/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "optparse"

require "rake/task_manager"
require "rake/lazy_task_definition"
require "rake/file_list"
require "rake/thread_pool"
require "rake/thread_history_display"
Expand All @@ -18,6 +19,7 @@ module Rake

class Application
include TaskManager
include LazyTaskDefinition
include TraceOutput

# The name of the application (typically 'rake')
Expand Down Expand Up @@ -129,16 +131,20 @@ def load_rakefile

# Run the top level tasks of a Rake application.
def top_level
run_with_threads do
if options.show_tasks
display_tasks_and_comments
elsif options.show_prereqs
display_prerequisites
else
top_level_tasks.each { |task_name| invoke_task(task_name) }
end
run_with_threads { top_level_intern }
end

def top_level_intern
execute_all_lazy_definitions if options.loadlazydefinitions
if options.show_tasks
display_tasks_and_comments
elsif options.show_prereqs
display_prerequisites
else
top_level_tasks.each { |task_name| invoke_task(task_name) }
end
end
private :top_level_intern

# Run the given block with the thread startup and shutdown.
def run_with_threads
Expand Down Expand Up @@ -530,6 +536,10 @@ def standard_rake_options # :nodoc:
"-N", "Do not search parent directories for the Rakefile.",
lambda { |value| options.nosearch = true }
],
["--load-lazy-definitions", "--loadlazydefinitions",
"-L", "Include all lazy definitions when listing tasks with --tasks",
lambda { |value| options.loadlazydefinitions = true}
],
["--prereqs", "-P",
"Display the tasks and dependencies, then exit.",
lambda { |value| options.show_prereqs = true }
Expand Down Expand Up @@ -838,6 +848,7 @@ def set_default_options # :nodoc:
options.job_stats = false
options.load_system = false
options.nosearch = false
options.loadlazydefinitions = false
options.rakelib = %w[rakelib]
options.show_all_tasks = false
options.show_prereqs = false
Expand Down
6 changes: 6 additions & 0 deletions lib/rake/dsl_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ def import(*fns) # :doc:
Rake.application.add_import(fn)
end
end

# Associate a block to the current scope and
# execute it only when a lookup for a task is performed in the same scope.
def lazy(&block)
Rake.application.register_lazy_definition(&block)
end
end
extend FileUtilsExt
end
Expand Down
53 changes: 53 additions & 0 deletions lib/rake/lazy_task_definition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true
module Rake

# The LazyTaskDefinition module is a mixin for managing lazy defined tasks.
module LazyTaskDefinition

# Execute all definitions: usefull for rake -T for instance
def execute_all_lazy_definitions
lazy_definitions.each do |scope_path, _definitions|
execute_lazy_definitions(scope_path)
end
end

# Execute all definitions linked to specified +scope_path+
# and its parent scopes.
def execute_lazy_definitions(scope_path)
scope_path_elements = scope_path.split(':')
sub_scope_elements = []
scope_path_elements.each do |e|
sub_scope_elements << e
sub_scope_path = sub_scope_elements.join(':')
definitions = lazy_definitions[sub_scope_path]
next unless definitions
definitions.each do |definition|
definition.call
end
definitions.clear
end
end

# Evaluate the block in specified +scope+.
def in_scope(scope)
cur_scope = @scope
@scope = scope
yield
ensure
@scope = cur_scope
end

# Register a block which will be called only when necessary during the lookup
# of tasks
def register_lazy_definition(&block)
cur_scope = @scope
lazy_definitions[cur_scope.path] ||= []
lazy_definitions[cur_scope.path] << ->() { in_scope(cur_scope, &block) }
end

def lazy_definitions
@lazy_definitions ||= {}
end
private :lazy_definitions
end
end
2 changes: 2 additions & 0 deletions lib/rake/task_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ def lookup(task_name, initial_scope=nil)
def lookup_in_scope(name, scope)
loop do
tn = scope.path_with_task_name(name)
# use lazy definitions if mixin LazyTaskDefinition is enabled
execute_lazy_definitions(tn) if respond_to?(:execute_lazy_definitions)
task = @tasks[tn]
return task if task
break if scope.empty?
Expand Down
33 changes: 33 additions & 0 deletions test/test_rake_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,37 @@ def test_no_commands_constant
assert ! defined?(Commands), "should not define Commands"
end

def test_lazy
call_count_t1 = 0
call_count_t3 = 0
namespace "a" do
lazy do
task "t1"
call_count_t1 += 1
end
end
namespace "b" do
task "t2"
namespace "c" do
lazy do
namespace "d" do
lazy do
task "t3"
call_count_t3 += 1
end
end
end
end
end
refute_nil Rake::Task["b:t2"]
assert_equal 0, call_count_t1
assert_equal 0, call_count_t3
refute_nil Rake::Task["a:t1"]
assert_equal 1, call_count_t1
assert_equal 0, call_count_t3
refute_nil Rake::Task["b:c:d:t3"]
assert_equal 1, call_count_t1
assert_equal 1, call_count_t3
end

end
60 changes: 60 additions & 0 deletions test/test_rake_lazy_task_definition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true
require File.expand_path("../helper", __FILE__)

class TestRakeLazyTaskDefinition < Rake::TestCase # :nodoc:

def setup
super

@tm = Rake::TestCase::TaskManager.new
@tm.extend Rake::LazyTaskDefinition
end

def test_lazy_definition
t1, t2, t3 = nil, nil, nil
lazy_definition_call_count = 0
@tm.in_namespace("a1") do
@tm.register_lazy_definition do
t1 = @tm.define_task(Rake::Task, :t1)
lazy_definition_call_count += 1
@tm.in_namespace("a2") do
t2 = @tm.define_task(Rake::Task, :t2)
end
end
end
@tm.in_namespace("b") do
t3 = @tm.define_task(Rake::Task, :t3)
end
# task t3 is not lazy. It can be found
assert_equal t3, @tm[:t3, Rake::Scope.make("b")]
# lazy definition is not called until we look for task in namespace a
assert_equal lazy_definition_call_count, 0

# task t2 can be found
found_task_t2 = @tm[:t2, Rake::Scope.make("a1:a2")]
assert_equal t2, found_task_t2
# lazy definition is expected to be called
assert_equal lazy_definition_call_count, 1

# task t1 can also be found
found_task_t1 = @tm[:t1, Rake::Scope.make("a1")]
assert_equal t1, found_task_t1
# lazy definition is called at most once
assert_equal lazy_definition_call_count, 1
end

def test_execute_all_lazy_definitions
lazy_definition_call_count = 0
@tm.in_namespace("a") do
@tm.register_lazy_definition do
lazy_definition_call_count += 1
end
end
assert_equal lazy_definition_call_count, 0
@tm.execute_all_lazy_definitions
assert_equal lazy_definition_call_count, 1
@tm.execute_all_lazy_definitions
assert_equal lazy_definition_call_count, 1
end

end