require 'singleton' require 'chrysalis/project' require 'chrysalis/vcs' require 'chrysalis/errors' require 'chrysalis/core_ext/string' module Chrysalis META_DIRECTORY = '.chrysalis'.freeze META_CACHE_FILE = '.chrysalis/cache.yml'.freeze class << self def boot Chrysalis::Loader.instance Chrysalis::Cache.instance end #-- # TODO: A proper solution for configuration options needs to be implemented. def options @options ||= OpenStruct.new :prefix => './lib' end end # Contains an entry for each project included as a component of larger # application or library. class Manifest include Singleton def initialize # :nodoc: @projects = {} end # Returns a Project if +key+ is a url. Returns a URL if +key+ is a Project. # # The Manifest functions as a two-way hash, where either a URL or a Project # can be used as a key, returning its associated value. def [](key) return @projects.invert[key] if key.is_a?(Project) @projects[key] end def <<(proj) # :nodoc: key = Chrysalis::Loader.instance.loading raise ManifestError, "Project already exists in manifest. (URL: #{key})" if @projects[key] @projects[key] = proj end end # Stores the results of retrieving a working copy from a repository. #-- # The cache is implemented as hash, where each URL-based key serves as an # index to another hash. The entire structure is serialized to the cache file. # When deserializing, the structure is used to instantiate WorkingCopy # instances, effectively recovering the state of previously retrieved working # copies. class Cache include Singleton def initialize # :nodoc: @hash = {} load end def [](url) @hash[url] end def []=(url, working_copy) @hash[url] = working_copy.to_hash serialize end private def load return if (!File.exist?(META_CACHE_FILE)) File.open(META_CACHE_FILE) do |input| @hash = YAML.load(input) @hash = {} if @hash.nil? end expired = @hash.reject do |url, value| working_copy = WorkingCopy.create(value) if working_copy.nil? false else if !working_copy.exist? false else Chrysalis::Loader.instance.load(url, working_copy) true end end end expired.each { |key, value| @hash.delete(key) } serialize end def serialize FileUtils.mkdir_p(META_DIRECTORY) if (!File.exist?(META_DIRECTORY)) File.open(META_CACHE_FILE, 'w') do |out| YAML.dump(@hash, out) end end end # Manages tasks synthesized by Chrysalis. # # Chrysalis synthesizes any tasks into an all: namespace. When # executing a task in this namespace, Chrysalis will retrieve any required # dependencies and execute the task on them. # # Graph algorithms are used to ensure that task execution is done in the # correct order. Tasks are guaranteed to be executed on dependencies before # being executed on any dependents. # # For example, if a build task is declared, then an all:build # task will be synthesized. Executing rake all:build will first # retrieve dependencies, execute build upon them, and then invoke the main # project's build task. module TaskManager @@tasks = [] # Synthesizes a task into the all: namespace. def self.synthesize_task(name, comment) # Don't synthesize tasks "all:*", "project:*", or "retrieve" tasks. These # tasks are specific to Chrysalis, and only useful in the context of the # main project. if name.begin?('all:') || name.begin?('project:') || name.eql?('retrieve') return end if !@@tasks.include?(name) rake_namespace :all do rake_task name => "^retrieve" rake_desc "Invoke #{name} task, including dependencies." rake_task name do order = Chrysalis::Manifest.instance[:main].task_exec_order order.delete(:main) order.each do |url| proj = Chrysalis::Manifest.instance[url] if proj.task?(name) cur_dir = FileUtils.pwd FileUtils.cd(proj.working_copy.path) puts "" puts "Invoking #{name} in #{proj.working_copy.path}" cmd = "rake #{name}" cmd << " --trace" if Rake.application.options.trace # This is a work-around for funky handling of processes on # Windows. If not done, rake will always fail with the following # message: # > rake aborted! # > undefined method `exitstatus' for nil:NilClass cmd << " 2>&1" if RUBY_PLATFORM.match(/mswin/) sh cmd FileUtils.cd(cur_dir) end end if Chrysalis::Manifest.instance[:main].task?(name) puts "" puts "Invoking #{name}" Rake.application[name].invoke end end end @@tasks << name end end end end