require 'pathname' require 'gratr' require 'gratr/dot' require 'chrysalis/repository' require 'chrysalis/core_ext/string' module Chrysalis # Represents a software development project, typically an application or # library. class Project attr_accessor :name attr_accessor :version attr_accessor :working_copy def initialize Chrysalis::Manifest.instance << self @working_copy = nil @tasks = {} @dependencies = [] yield self if block_given? end def url Chrysalis::Manifest.instance[self] end # Add a dependency, located at +url+, to the project. def dependency(url, params = {}) @dependencies << [url, params] end alias_method :dependency=, :dependency def task(name, comment) desc = (@tasks.has_key?(name) ? @tasks[name] : '') desc.concat_sep(comment, " / ") @tasks[name] = desc end def task?(name) @tasks.has_key? name end # Retrieve all dependencies required by the project. def retrieve # A project can be declared as a dependency multiple times, which is a # common occurance for shared libraries. After the initial retrieval, the # retrieved flag is set. Subsequent retrievals are unnessesary. return if @retrieved @retrieved = true FileUtils.mkdir_p(Chrysalis.options.prefix) # Retrieve each direct dependency from its repository, and load it into # the manifest. @dependencies.each do |url, params| # If the URL of the project is already in the manifest, it has already # been loaded. if !Chrysalis::Manifest.instance[url] working_copy = Repository.retrieve(url, Chrysalis.options.prefix, params) Chrysalis::Loader.instance.load(url, working_copy) end end # Transitively retrieve dependencies. @dependencies.each do |url, params| Chrysalis::Manifest.instance[url].retrieve end end def info n = (@name ? @name : "") v = (@version ? "(#{@version})" : "") printf "- %s %s\n", n, v puts " #{self.url}" if self.url.is_a?(String) if Rake.application.options.trace @tasks.each do |name, comment| printf " %-18s # %s\n", name, comment end end end def graph(g = GRATR::Digraph.new) @dependencies.each do |url, params| raise "Illegal declaration of dependency on self." if self.url == url # If an arc has already been connected between a project and a # dependency, a cycle has been induced. This needs to be detected to # prevent this function from looping indefinately. # # For example: # libone ---depends-on--> libtwo # libtwo ---depends-on--> libone # # Would graph as follows: # libone --> libtwo # ^ | # '-----------' # # If not prevented this will keep adding edges from libone -> libtwo -> # libone -> libtwo -> libone, and on and on and on, ad infinitum. if !g.edge?(self.url, url) g.add_edge!(self.url, url) # Build the graph recursively, by telling the dependency to construct # its portion. dep = Chrysalis::Manifest.instance[url] dep.graph(g) if !dep.nil? end end return g.add_vertex!(self.url) end def task_exec_order g = graph raise "Cyclic dependency detected." if g.cyclic? g.topsort.reverse end end end