chrysalis 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/README +5 -0
- data/Rakefile +58 -0
- data/lib/chrysalis/ar/tar.rb +56 -0
- data/lib/chrysalis/ar/tar_bz2.rb +32 -0
- data/lib/chrysalis/ar/tar_gz.rb +51 -0
- data/lib/chrysalis/ar/zip.rb +48 -0
- data/lib/chrysalis/ar.rb +4 -0
- data/lib/chrysalis/archive.rb +55 -0
- data/lib/chrysalis/core_ext/string.rb +19 -0
- data/lib/chrysalis/errors.rb +18 -0
- data/lib/chrysalis/loader.rb +151 -0
- data/lib/chrysalis/manifest.rb +193 -0
- data/lib/chrysalis/project.rb +129 -0
- data/lib/chrysalis/rake_ext/intercept.rb +90 -0
- data/lib/chrysalis/repository.rb +142 -0
- data/lib/chrysalis/task.rb +78 -0
- data/lib/chrysalis/vcs/file/repository.rb +63 -0
- data/lib/chrysalis/vcs/file.rb +1 -0
- data/lib/chrysalis/vcs/http/repository.rb +67 -0
- data/lib/chrysalis/vcs/http.rb +1 -0
- data/lib/chrysalis/vcs/svn/repository.rb +88 -0
- data/lib/chrysalis/vcs/svn.rb +1 -0
- data/lib/chrysalis/vcs.rb +3 -0
- data/lib/chrysalis/version.rb +3 -0
- data/lib/chrysalis.rb +28 -0
- metadata +96 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'chrysalis/project'
|
3
|
+
require 'chrysalis/vcs'
|
4
|
+
require 'chrysalis/errors'
|
5
|
+
require 'chrysalis/core_ext/string'
|
6
|
+
|
7
|
+
|
8
|
+
module Chrysalis
|
9
|
+
|
10
|
+
META_DIRECTORY = '.chrysalis'.freeze
|
11
|
+
META_CACHE_FILE = '.chrysalis/cache.yml'.freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def boot
|
15
|
+
Chrysalis::Loader.instance
|
16
|
+
Chrysalis::Cache.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
#--
|
20
|
+
# TODO: A proper solution for configuration options needs to be implemented.
|
21
|
+
def options
|
22
|
+
@options ||= OpenStruct.new :prefix => './lib'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Contains an entry for each project included as a component of larger
|
28
|
+
# application or library.
|
29
|
+
class Manifest
|
30
|
+
include Singleton
|
31
|
+
|
32
|
+
def initialize # :nodoc:
|
33
|
+
@projects = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a Project if +key+ is a url. Returns a URL if +key+ is a Project.
|
37
|
+
#
|
38
|
+
# The Manifest functions as a two-way hash, where either a URL or a Project
|
39
|
+
# can be used as a key, returning its associated value.
|
40
|
+
def [](key)
|
41
|
+
return @projects.invert[key] if key.is_a?(Project)
|
42
|
+
@projects[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def <<(proj) # :nodoc:
|
46
|
+
key = Chrysalis::Loader.instance.loading
|
47
|
+
|
48
|
+
raise ManifestError, "Project already exists in manifest. (URL: #{key})" if @projects[key]
|
49
|
+
@projects[key] = proj
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
# Stores the results of retrieving a working copy from a repository.
|
55
|
+
#--
|
56
|
+
# The cache is implemented as hash, where each URL-based key serves as an
|
57
|
+
# index to another hash. The entire structure is serialized to the cache file.
|
58
|
+
# When deserializing, the structure is used to instantiate WorkingCopy
|
59
|
+
# instances, effectively recovering the state of previously retrieved working
|
60
|
+
# copies.
|
61
|
+
class Cache
|
62
|
+
include Singleton
|
63
|
+
|
64
|
+
def initialize # :nodoc:
|
65
|
+
@hash = {}
|
66
|
+
load
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](url)
|
70
|
+
@hash[url]
|
71
|
+
end
|
72
|
+
|
73
|
+
def []=(url, working_copy)
|
74
|
+
@hash[url] = working_copy.to_hash
|
75
|
+
serialize
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def load
|
80
|
+
return if (!File.exist?(META_CACHE_FILE))
|
81
|
+
|
82
|
+
File.open(META_CACHE_FILE) do |input|
|
83
|
+
@hash = YAML.load(input)
|
84
|
+
@hash = {} if @hash.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
expired = @hash.reject do |url, value|
|
88
|
+
working_copy = WorkingCopy.create(value)
|
89
|
+
if working_copy.nil?
|
90
|
+
false
|
91
|
+
else
|
92
|
+
if !working_copy.exist?
|
93
|
+
false
|
94
|
+
else
|
95
|
+
Chrysalis::Loader.instance.load(url, working_copy)
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
expired.each { |key, value| @hash.delete(key) }
|
102
|
+
serialize
|
103
|
+
end
|
104
|
+
|
105
|
+
def serialize
|
106
|
+
FileUtils.mkdir_p(META_DIRECTORY) if (!File.exist?(META_DIRECTORY))
|
107
|
+
|
108
|
+
File.open(META_CACHE_FILE, 'w') do |out|
|
109
|
+
YAML.dump(@hash, out)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Manages tasks synthesized by Chrysalis.
|
117
|
+
#
|
118
|
+
# Chrysalis synthesizes any tasks into an <em>all:</em> namespace. When
|
119
|
+
# executing a task in this namespace, Chrysalis will retrieve any required
|
120
|
+
# dependencies and execute the task on them.
|
121
|
+
#
|
122
|
+
# Graph algorithms are used to ensure that task execution is done in the
|
123
|
+
# correct order. Tasks are guaranteed to be executed on dependencies before
|
124
|
+
# being executed on any dependents.
|
125
|
+
#
|
126
|
+
# For example, if a <tt>build</tt> task is declared, then an <tt>all:build</tt>
|
127
|
+
# task will be synthesized. Executing <em>rake all:build</em> will first
|
128
|
+
# retrieve dependencies, execute build upon them, and then invoke the main
|
129
|
+
# project's build task.
|
130
|
+
|
131
|
+
module TaskManager
|
132
|
+
@@tasks = []
|
133
|
+
|
134
|
+
# Synthesizes a task into the <em>all:</em> namespace.
|
135
|
+
def self.synthesize_task(name, comment)
|
136
|
+
# Don't synthesize tasks "all:*", "project:*", or "retrieve" tasks. These
|
137
|
+
# tasks are specific to Chrysalis, and only useful in the context of the
|
138
|
+
# main project.
|
139
|
+
if name.begin?('all:') || name.begin?('project:') || name.eql?('retrieve')
|
140
|
+
return
|
141
|
+
end
|
142
|
+
|
143
|
+
if !@@tasks.include?(name)
|
144
|
+
|
145
|
+
rake_namespace :all do
|
146
|
+
rake_task name => "^retrieve"
|
147
|
+
|
148
|
+
rake_desc "Invoke #{name} task, including dependencies."
|
149
|
+
rake_task name do
|
150
|
+
|
151
|
+
order = Chrysalis::Manifest.instance[:main].task_exec_order
|
152
|
+
order.delete(:main)
|
153
|
+
|
154
|
+
order.each do |url|
|
155
|
+
proj = Chrysalis::Manifest.instance[url]
|
156
|
+
if proj.task?(name)
|
157
|
+
cur_dir = FileUtils.pwd
|
158
|
+
FileUtils.cd(proj.working_copy.path)
|
159
|
+
|
160
|
+
puts ""
|
161
|
+
puts "Invoking #{name} in #{proj.working_copy.path}"
|
162
|
+
|
163
|
+
cmd = "rake #{name}"
|
164
|
+
cmd << " --trace" if Rake.application.options.trace
|
165
|
+
|
166
|
+
# This is a work-around for funky handling of processes on
|
167
|
+
# Windows. If not done, rake will always fail with the following
|
168
|
+
# message:
|
169
|
+
# > rake aborted!
|
170
|
+
# > undefined method `exitstatus' for nil:NilClass
|
171
|
+
cmd << " 2>&1" if RUBY_PLATFORM.match(/mswin/)
|
172
|
+
|
173
|
+
sh cmd
|
174
|
+
|
175
|
+
FileUtils.cd(cur_dir)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
if Chrysalis::Manifest.instance[:main].task?(name)
|
180
|
+
puts ""
|
181
|
+
puts "Invoking #{name}"
|
182
|
+
Rake.application[name].invoke
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
@@tasks << name
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'gratr'
|
3
|
+
require 'gratr/dot'
|
4
|
+
require 'chrysalis/repository'
|
5
|
+
require 'chrysalis/core_ext/string'
|
6
|
+
|
7
|
+
|
8
|
+
module Chrysalis
|
9
|
+
|
10
|
+
# Represents a software development project, typically an application or
|
11
|
+
# library.
|
12
|
+
class Project
|
13
|
+
attr_accessor :name
|
14
|
+
attr_accessor :version
|
15
|
+
attr_accessor :working_copy
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
Chrysalis::Manifest.instance << self
|
19
|
+
|
20
|
+
@working_copy = nil
|
21
|
+
@tasks = {}
|
22
|
+
@dependencies = []
|
23
|
+
yield self if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def url
|
27
|
+
Chrysalis::Manifest.instance[self]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add a dependency, located at +url+, to the project.
|
31
|
+
def dependency(url, params = {})
|
32
|
+
@dependencies << [url, params]
|
33
|
+
end
|
34
|
+
alias_method :dependency=, :dependency
|
35
|
+
|
36
|
+
|
37
|
+
def task(name, comment)
|
38
|
+
desc = (@tasks.has_key?(name) ? @tasks[name] : '')
|
39
|
+
desc.concat_sep(comment, " / ")
|
40
|
+
@tasks[name] = desc
|
41
|
+
end
|
42
|
+
|
43
|
+
def task?(name)
|
44
|
+
@tasks.has_key? name
|
45
|
+
end
|
46
|
+
|
47
|
+
# Retrieve all dependencies required by the project.
|
48
|
+
def retrieve
|
49
|
+
# A project can be declared as a dependency multiple times, which is a
|
50
|
+
# common occurance for shared libraries. After the initial retrieval, the
|
51
|
+
# retrieved flag is set. Subsequent retrievals are unnessesary.
|
52
|
+
return if @retrieved
|
53
|
+
@retrieved = true
|
54
|
+
|
55
|
+
FileUtils.mkdir_p(Chrysalis.options.prefix)
|
56
|
+
|
57
|
+
# Retrieve each direct dependency from its repository, and load it into
|
58
|
+
# the manifest.
|
59
|
+
@dependencies.each do |url, params|
|
60
|
+
# If the URL of the project is already in the manifest, it has already
|
61
|
+
# been loaded.
|
62
|
+
if !Chrysalis::Manifest.instance[url]
|
63
|
+
working_copy = Repository.retrieve(url, Chrysalis.options.prefix, params)
|
64
|
+
Chrysalis::Loader.instance.load(url, working_copy)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Transitively retrieve dependencies.
|
69
|
+
@dependencies.each do |url, params|
|
70
|
+
Chrysalis::Manifest.instance[url].retrieve
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def info
|
75
|
+
n = (@name ? @name : "<Unknown Name>")
|
76
|
+
v = (@version ? "(#{@version})" : "")
|
77
|
+
|
78
|
+
printf "- %s %s\n", n, v
|
79
|
+
puts " #{self.url}" if self.url.is_a?(String)
|
80
|
+
|
81
|
+
if Rake.application.options.trace
|
82
|
+
@tasks.each do |name, comment|
|
83
|
+
printf " %-18s # %s\n", name, comment
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def graph(g = GRATR::Digraph.new)
|
89
|
+
@dependencies.each do |url, params|
|
90
|
+
raise "Illegal declaration of dependency on self." if self.url == url
|
91
|
+
|
92
|
+
# If an arc has already been connected between a project and a
|
93
|
+
# dependency, a cycle has been induced. This needs to be detected to
|
94
|
+
# prevent this function from looping indefinately.
|
95
|
+
#
|
96
|
+
# For example:
|
97
|
+
# libone ---depends-on--> libtwo
|
98
|
+
# libtwo ---depends-on--> libone
|
99
|
+
#
|
100
|
+
# Would graph as follows:
|
101
|
+
# libone --> libtwo
|
102
|
+
# ^ |
|
103
|
+
# '-----------'
|
104
|
+
#
|
105
|
+
# If not prevented this will keep adding edges from libone -> libtwo ->
|
106
|
+
# libone -> libtwo -> libone, and on and on and on, ad infinitum.
|
107
|
+
if !g.edge?(self.url, url)
|
108
|
+
g.add_edge!(self.url, url)
|
109
|
+
|
110
|
+
# Build the graph recursively, by telling the dependency to construct
|
111
|
+
# its portion.
|
112
|
+
dep = Chrysalis::Manifest.instance[url]
|
113
|
+
dep.graph(g) if !dep.nil?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
return g.add_vertex!(self.url)
|
118
|
+
end
|
119
|
+
|
120
|
+
def task_exec_order
|
121
|
+
g = graph
|
122
|
+
raise "Cyclic dependency detected." if g.cyclic?
|
123
|
+
|
124
|
+
g.topsort.reverse
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'chrysalis/loader'
|
3
|
+
|
4
|
+
|
5
|
+
# Intercept Rake's methods for declaring tasks.
|
6
|
+
#
|
7
|
+
# Task interception is used to construct internal tables, and synthesize
|
8
|
+
# tasks in the <em>all:</em> namespace.
|
9
|
+
#
|
10
|
+
# Tasks declared in the course of loading a dependency are intercepted
|
11
|
+
# completely, and are unknown to Rake. This prevents dependencies from
|
12
|
+
# polluting the task space of their dependents.
|
13
|
+
#
|
14
|
+
# Tasks declared by the main rakefile are forwarded to Rake's original methods,
|
15
|
+
# ensuring that the system remains consistent.
|
16
|
+
|
17
|
+
class Object
|
18
|
+
|
19
|
+
alias_method :rake_task, :task
|
20
|
+
def task(args, &block)
|
21
|
+
task_name, deps = Rake.application.resolve_args(args)
|
22
|
+
Chrysalis::Loader.instance.task_intercept(task_name) { rake_task(args, &block) }
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :rake_file, :file
|
26
|
+
def file(args, &block)
|
27
|
+
task_name, deps = Rake.application.resolve_args(args)
|
28
|
+
Chrysalis::Loader.instance.file_task_intercept(task_name) { rake_file(args, &block) }
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :rake_file_create, :file_create
|
32
|
+
def file_create(args, &block)
|
33
|
+
task_name, deps = Rake.application.resolve_args(args)
|
34
|
+
Chrysalis::Loader.instance.file_task_intercept(task_name) { rake_file_create(args, &block) }
|
35
|
+
end
|
36
|
+
|
37
|
+
#--
|
38
|
+
# NOTE: Intercepting directory tasks is unnecessary, because Rake itself
|
39
|
+
# implements them as a set of file_create tasks. Thus, the interception
|
40
|
+
# of file_create is sufficient.
|
41
|
+
#alias_method :rake_directory, :directory
|
42
|
+
#def directory(dir)
|
43
|
+
#end
|
44
|
+
|
45
|
+
alias_method :rake_multitask, :multitask
|
46
|
+
def multitask(args, &block)
|
47
|
+
task_name, deps = Rake.application.resolve_args(args)
|
48
|
+
Chrysalis::Loader.instance.task_intercept(task_name) { rake_multitask(args, &block) }
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :rake_namespace, :namespace
|
52
|
+
def namespace(name=nil, &block)
|
53
|
+
Chrysalis::Loader.instance.namespace_intercept(name, block) { rake_namespace(name, &block) }
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :rake_rule, :rule
|
57
|
+
def rule(args, &block)
|
58
|
+
Chrysalis::Loader.instance.rule_intercept { rake_rule(args, &block) }
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :rake_desc, :desc
|
62
|
+
def desc(comment)
|
63
|
+
Chrysalis::Loader.instance.desc_intercept(comment) { rake_desc(comment) }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
module Rake
|
70
|
+
|
71
|
+
class Application
|
72
|
+
|
73
|
+
alias_method :rake_top_level, :top_level
|
74
|
+
|
75
|
+
# Invoked after the main rakefile has been loaded, but before any tasks are
|
76
|
+
# executed.
|
77
|
+
#
|
78
|
+
# Chrysalis reimplements this method for the purpose of hooking into the
|
79
|
+
# loader and seeding the <em>all:</em> namespace with tasks, prior to
|
80
|
+
# retrieval of dependencies.
|
81
|
+
#
|
82
|
+
# After invoking the hook, execution proceeds with Rake's original method.
|
83
|
+
def top_level
|
84
|
+
Chrysalis.post :rakefile_loaded
|
85
|
+
rake_top_level
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'chrysalis/errors'
|
3
|
+
|
4
|
+
|
5
|
+
module Chrysalis
|
6
|
+
|
7
|
+
# Represents a repository from which dependencies can be retrieved.
|
8
|
+
#
|
9
|
+
# This class is intended to be subclassed in order to implement support for
|
10
|
+
# concrete types of repositories.
|
11
|
+
class Repository
|
12
|
+
@@repositories = []
|
13
|
+
|
14
|
+
def self.inherited(repository) # :nodoc:
|
15
|
+
@@repositories << repository
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieves a WorkingCopy from the repository located at +url+. The working
|
19
|
+
# copy is placed at the path +to+, which defaults to the current directory.
|
20
|
+
# An optional set of parameters can be specified in +params+.
|
21
|
+
#
|
22
|
+
# Returns a WorkingCopy.
|
23
|
+
#
|
24
|
+
# Using the factory design pattern, this method locates a subclass of
|
25
|
+
# Repository that implements support for the given URL. That subclass will
|
26
|
+
# be instructed to retrieve a working copy from the repository.
|
27
|
+
#
|
28
|
+
# If the working copy has already been retrieved, it will be found in the
|
29
|
+
# cache and returned directly.
|
30
|
+
#
|
31
|
+
# If support for the given URL is not implemented, a RepositoryError will be
|
32
|
+
# raised.
|
33
|
+
def self.retrieve(url, to = '.', params = {})
|
34
|
+
cached = WorkingCopy.create(Chrysalis::Cache.instance[url])
|
35
|
+
return cached if cached
|
36
|
+
|
37
|
+
@@repositories.reverse.each { |repository|
|
38
|
+
if repository.retrieves?(url, params)
|
39
|
+
working_copy = repository.new(url, params).retrieve(to)
|
40
|
+
Chrysalis::Cache.instance[url] = working_copy
|
41
|
+
return working_copy
|
42
|
+
end
|
43
|
+
}
|
44
|
+
raise RepositoryError, "Unknown version control system. (URL: #{url})"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Returns +true+ if this class implements support for +url+. Otherwise,
|
49
|
+
# returns +false+.
|
50
|
+
#
|
51
|
+
# Because this class is abstract, +false+ is returned unconditionally.
|
52
|
+
# Subclasses are expected to provide an implementation.
|
53
|
+
def self.retrieves?(url, params = {})
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(url, params = {})
|
58
|
+
end
|
59
|
+
|
60
|
+
# Retrieves a working copy from the repository.
|
61
|
+
#
|
62
|
+
# Because this class is abstract, always raises an UnimplementedError.
|
63
|
+
# Subclasses are expected to provide an implementation.
|
64
|
+
def retrieve(to = '.')
|
65
|
+
raise UnimplementedError, "Repository#retrieve not implemented"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Represents a working copy that has been retrieved from a repository.
|
72
|
+
#
|
73
|
+
# This class implements support for a file system-based working copy stored in
|
74
|
+
# a directory.
|
75
|
+
#
|
76
|
+
# Subclasses can provide implementation for working copies with additional
|
77
|
+
# functionality, such as those retrieved from a version control system.
|
78
|
+
class WorkingCopy
|
79
|
+
@@working_copies = [self]
|
80
|
+
|
81
|
+
def self.inherited(working_copies) # :nodoc:
|
82
|
+
@@working_copies << working_copies
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a WorkingCopy constructed with +params+.
|
86
|
+
#
|
87
|
+
# Using the factory design pattern, this method instantiates a new instance
|
88
|
+
# of WorkingCopy based on the key <tt>:type</tt> in the +params+ hash.
|
89
|
+
#
|
90
|
+
# If <tt>:type</tt> is not supported, +nil+ is returned.
|
91
|
+
#
|
92
|
+
# This method is intended to be used for the purpose of instantiating a
|
93
|
+
# working copies directly from the cache, to obviate the need for retrieval
|
94
|
+
# when the working copy already exists on the system.
|
95
|
+
def self.create(params)
|
96
|
+
return nil if params.nil?
|
97
|
+
type = params[:type]
|
98
|
+
|
99
|
+
@@working_copies.each do |working_copy|
|
100
|
+
return working_copy.new(params) if working_copy.type == type
|
101
|
+
end
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
attr_reader :url
|
107
|
+
attr_reader :path
|
108
|
+
|
109
|
+
def self.type
|
110
|
+
:directory
|
111
|
+
end
|
112
|
+
|
113
|
+
def initialize(params = {})
|
114
|
+
@url = params[:url]
|
115
|
+
@path = params[:path]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns +true+ if the working copy exists on disk. Otherwise, returns
|
119
|
+
# +false+.
|
120
|
+
#
|
121
|
+
# Typically, if the working copy no longer exits on disk, it has been
|
122
|
+
# explicitly removed by a developer.
|
123
|
+
def exist?
|
124
|
+
Pathname.new(@path).exist?
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns a hash which will be serialized to the cache.
|
128
|
+
#
|
129
|
+
# At a minimum, a <tt>:type</tt> key must be present in the hash. This key
|
130
|
+
# is used when loading the cache, to instantiate working copies that have
|
131
|
+
# previously been retrieved.
|
132
|
+
#
|
133
|
+
# Subclasses can add any additional keys and values to the hash, and utilize
|
134
|
+
# them for purposes of deserialization.
|
135
|
+
def to_hash
|
136
|
+
{ :type => self.class.type,
|
137
|
+
:url => @url,
|
138
|
+
:path => @path }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require 'chrysalis/manifest'
|
4
|
+
|
5
|
+
|
6
|
+
module Rake
|
7
|
+
|
8
|
+
class ChrysalisTask < TaskLib
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
yield self if block_given?
|
12
|
+
define
|
13
|
+
end
|
14
|
+
|
15
|
+
def define
|
16
|
+
namespace :project do
|
17
|
+
|
18
|
+
desc "Show project information."
|
19
|
+
task :info do
|
20
|
+
raise "No project has been defined." if Chrysalis::Manifest.instance[:main].nil?
|
21
|
+
|
22
|
+
puts ""
|
23
|
+
puts "Project information: "
|
24
|
+
|
25
|
+
main = Chrysalis::Manifest.instance[:main]
|
26
|
+
main.graph.topsort.each do |url|
|
27
|
+
p = Chrysalis::Manifest.instance[url]
|
28
|
+
if p.nil?
|
29
|
+
puts "* #{url} [Not Loaded]"
|
30
|
+
else
|
31
|
+
p.info
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Display task execution order."
|
38
|
+
task :order do
|
39
|
+
raise "No project has been defined." if Chrysalis::Manifest.instance[:main].nil?
|
40
|
+
|
41
|
+
order = Chrysalis::Manifest.instance[:main].task_exec_order
|
42
|
+
|
43
|
+
puts ""
|
44
|
+
puts "Tasks execution order: "
|
45
|
+
order.each { |url| puts "- #{url}" }
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Draw dependency graph."
|
49
|
+
task :graph do
|
50
|
+
raise "No project has been defined." if Chrysalis::Manifest.instance[:main].nil?
|
51
|
+
|
52
|
+
g = Chrysalis::Manifest.instance[:main].graph
|
53
|
+
|
54
|
+
name = Chrysalis::Manifest.instance[:main].name
|
55
|
+
file = (name ? name.downcase.concat("-graph") : "graph")
|
56
|
+
|
57
|
+
dot = g.write_to_graphic_file('jpg', file)
|
58
|
+
|
59
|
+
puts "Dependency graph saved to #{dot}"
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Retrieve dependencies."
|
65
|
+
task :retrieve do
|
66
|
+
raise "No project has been defined." if Chrysalis::Manifest.instance[:main].nil?
|
67
|
+
Chrysalis::Manifest.instance[:main].retrieve
|
68
|
+
end
|
69
|
+
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
Chrysalis.boot
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'pathname'
|
3
|
+
require 'chrysalis/repository'
|
4
|
+
require 'chrysalis/ar'
|
5
|
+
|
6
|
+
|
7
|
+
module Chrysalis
|
8
|
+
module VCS
|
9
|
+
module File
|
10
|
+
|
11
|
+
# Implements support for copying dependencies from a file system.
|
12
|
+
#
|
13
|
+
# Repositories of this type are identified by <em>file://</em> URLs.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
# - file:///Users/jaredhanson/Projects/libfoo
|
17
|
+
|
18
|
+
class Repository < Chrysalis::Repository
|
19
|
+
|
20
|
+
def self.retrieves?(url, params = {})
|
21
|
+
uri = URI::parse(url)
|
22
|
+
return uri.scheme == 'file'
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def initialize(url, params = {})
|
27
|
+
@url = url
|
28
|
+
@params = params
|
29
|
+
end
|
30
|
+
|
31
|
+
def retrieve(to = '.')
|
32
|
+
target = Pathname.new(to).join(copy_to)
|
33
|
+
raise IOError, "Refusing to overwrite existing path. (path: #{target})" if target.exist?
|
34
|
+
|
35
|
+
unless @params[:noop]
|
36
|
+
puts ""
|
37
|
+
puts "Copying #{@url} ..."
|
38
|
+
|
39
|
+
source = URI::parse(@url)
|
40
|
+
FileUtils.cp_r(source.path, target)
|
41
|
+
end
|
42
|
+
|
43
|
+
extract_path = Archive.extract(target.to_s, to, @params)
|
44
|
+
WorkingCopy.new(:url => @url,
|
45
|
+
:path => extract_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def copy_to
|
50
|
+
return @params[:copy_to] if @params[:copy_to]
|
51
|
+
|
52
|
+
uri = URI::parse(@url)
|
53
|
+
dir = uri.path.split('/')[-1]
|
54
|
+
|
55
|
+
raise ParameterError, "Invalid URL. (URL: #{@url})" if dir.nil?
|
56
|
+
return dir
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'chrysalis/vcs/file/repository'
|