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