module Chrysalis # Loader is responsible for loading rakefiles. # # The loader loads the rakefiles defined by dependencies. The tasks defined # within a dependency rakefile are intercepted and re-synthesized as # all:* tasks. # # Additionally, a hook is declared in order to receive notification that the # main rakefile has been loaded. The tasks in the main rakefile serve to seed # the all: namespace with tasks, prior to retrieving any # dependencies. class Loader include Singleton def initialize @loading = :main @icept_scope = [] @icept_tasks = [] @last_comment = nil Chrysalis.hook :rakefile_loaded, self.method(:on_rakefile_loaded).to_proc end # Returns the URL of the project currently being loaded. def loading @loading end # Loads a dependency's rakefile. # # The +url+ is the location of the repository from which the dependency was # retrieved. The +working_copy+ is the WorkingCopy retrieved. # # This function will search for and load the rakefile found in the working # copy's path, if one exists. def load(url, working_copy) @loading = url Rake::Application::DEFAULT_RAKEFILES.each do |file| path = Pathname.new(working_copy.path).join(file) if File.exist?(path) Kernel.load(path, true) return end end ensure # If the manifest does not contain an entry for the URL being loaded, one # of two things occured: # 1. a project was not instantiated in the rakefile -- or -- # 2. no rakefile existed in the working copy's path # # In either case, an empty project will be created as a placeholder. if Chrysalis::Manifest.instance[@loading].nil? proj = Project.new end Chrysalis::Manifest.instance[@loading].working_copy = working_copy Chrysalis.post :rakefile_loaded end def on_rakefile_loaded # :nodoc: return if Chrysalis::Manifest.instance[@loading].nil? @icept_tasks.each do |task| Chrysalis::Manifest.instance[@loading].task(task[0], task[1]) Chrysalis::TaskManager.synthesize_task(task[0], task[1]) end ensure @loading = :main @icept_tasks = [] end def task_intercept(name) scoped_name = (@icept_scope + [name]).join(':') @icept_tasks << [scoped_name, @last_comment] @last_comment = nil yield if @loading == :main end def file_task_intercept(name) @icept_tasks << [name, @last_comment] @last_comment = nil yield if @loading == :main end def desc_intercept(comment) @last_comment = comment yield if @loading == :main end #-- # TODO: If name is nil, Rake generates an anonymous namespace. Chrysalis # currently does not support this feature. In practice, anonymous # namespaces are rarely used. However, support should be implemented # for maximum compatibility. def namespace_intercept(name, orig_block) @icept_scope.push(name) # Call the original block given to the namespace. This allows is to # progress deeper, intercepting further tasks within the namespace. # # TODO: Rake instantiates a NameSpace, which is yeiled as a parameter. # In practice, most code doesn't expect this parameter. However, # support for it is currently unimplemented, but should be # implemented for maximum compatibility. # # Example: # orig_block.call(a_namespace) if @loading != :main orig_block.call else yield end ensure @icept_scope.pop end def rule_intercept yield if @loading == :main end end module Hookable @@handlers = [] def hook(evt, proc) @@handlers << [evt, proc] end def post(evt, *args) callbacks = @@handlers.select { |e, ignore| evt == e } callbacks.each do |ignore, proc| proc.call(*args) end end end class << self include Hookable end end