bundler08 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ require "rubygems/source_index"
2
+
3
+ module Bundler
4
+ class InvalidCacheArgument < StandardError; end
5
+ class SourceNotCached < StandardError; end
6
+
7
+ class Environment
8
+ attr_reader :dependencies
9
+ attr_accessor :rubygems, :system_gems
10
+
11
+ def initialize(bundle)
12
+ @bundle = bundle # TODO: remove this
13
+ @default_sources = default_sources
14
+ @sources = []
15
+ @priority_sources = []
16
+ @dependencies = []
17
+ @rubygems = true
18
+ @system_gems = true
19
+ end
20
+
21
+ def environment_rb(specs, options)
22
+ load_paths = load_paths_for_specs(specs, options)
23
+ bindir = @bundle.bindir.relative_path_from(@bundle.gem_path).to_s
24
+ filename = @bundle.gemfile.relative_path_from(@bundle.gem_path).to_s
25
+
26
+ template = File.read(File.join(File.dirname(__FILE__), "templates", "environment.erb"))
27
+ erb = ERB.new(template, nil, '-')
28
+ erb.result(binding)
29
+ end
30
+
31
+ def require_env(env = nil)
32
+ dependencies.each { |d| d.require_env(env) }
33
+ end
34
+
35
+ def sources
36
+ sources = @priority_sources + [SystemGemSource.new(@bundle)] + @sources + @default_sources
37
+ sources.reject! {|s| !s.local? } if Bundler.local?
38
+ sources
39
+ end
40
+
41
+ def add_source(source)
42
+ @sources << source
43
+ end
44
+
45
+ def add_priority_source(source)
46
+ @priority_sources << source
47
+ end
48
+
49
+ def clear_sources
50
+ @sources.clear
51
+ @default_sources.clear
52
+ end
53
+
54
+ alias rubygems? rubygems
55
+ alias system_gems? system_gems
56
+
57
+ private
58
+
59
+ def default_sources
60
+ [GemSource.new(@bundle, :uri => "http://gems.rubyforge.org")]
61
+ end
62
+
63
+ def load_paths_for_specs(specs, options)
64
+ load_paths = []
65
+ specs.each do |spec|
66
+ next if spec.no_bundle?
67
+ full_gem_path = Pathname.new(spec.full_gem_path)
68
+
69
+ if spec.bindir && full_gem_path.join(spec.bindir).exist?
70
+ load_paths << load_path_for(full_gem_path, spec.bindir)
71
+ end
72
+ spec.require_paths.each do |path|
73
+ load_paths << load_path_for(full_gem_path, path)
74
+ end
75
+ end
76
+ load_paths
77
+ end
78
+
79
+ def load_path_for(gem_path, path)
80
+ gem_path.join(path).relative_path_from(@bundle.gem_path).to_s
81
+ end
82
+
83
+ def spec_file_for(spec)
84
+ spec.loaded_from.relative_path_from(@bundle.gem_path).to_s
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,51 @@
1
+ module Bundler
2
+ # Finder behaves like a rubygems source index in that it responds
3
+ # to #search. It also resolves a list of dependencies finding the
4
+ # best possible configuration of gems that satisifes all requirements
5
+ # without causing any gem activation errors.
6
+ class Finder
7
+
8
+ # Takes an array of gem sources and fetches the full index of
9
+ # gems from each one. It then combines the indexes together keeping
10
+ # track of the original source so that any resolved gem can be
11
+ # fetched from the correct source.
12
+ #
13
+ # ==== Parameters
14
+ # *sources<String>:: URI pointing to the gem repository
15
+ def initialize(*sources)
16
+ @cache = {}
17
+ @index = {}
18
+ @sources = sources
19
+ end
20
+
21
+ # Searches for a gem that matches the dependency
22
+ #
23
+ # ==== Parameters
24
+ # dependency<Gem::Dependency>:: The gem dependency to search for
25
+ #
26
+ # ==== Returns
27
+ # [Gem::Specification]:: A collection of gem specifications
28
+ # matching the search
29
+ def search(dependency)
30
+ @cache[dependency.hash] ||= begin
31
+ find_by_name(dependency.name).select do |spec|
32
+ dependency =~ spec
33
+ end.sort_by {|s| s.version }
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def find_by_name(name)
40
+ matches = @index[name] ||= begin
41
+ versions = {}
42
+ @sources.reverse_each do |source|
43
+ versions.merge! source.specs[name] || {}
44
+ end
45
+ versions
46
+ end
47
+ matches.values
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ module Bundler
2
+ class GemBundle < Array
3
+ def download
4
+ sort_by {|s| s.full_name.downcase }.each do |spec|
5
+ spec.source.download(spec)
6
+ end
7
+
8
+ self
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ module Gem
2
+ class Installer
3
+ remove_method(:app_script_text) if method_defined?(:app_script_text)
4
+
5
+ def app_script_text(bin_file_name)
6
+ path = @gem_home
7
+ template = File.read(File.join(File.dirname(__FILE__), "templates", "app_script.erb"))
8
+ erb = ERB.new(template, nil, '-')
9
+ erb.result(binding)
10
+ end
11
+ end
12
+
13
+ class Specification
14
+ attr_accessor :source, :location, :no_bundle
15
+
16
+ alias no_bundle? no_bundle
17
+
18
+ remove_method(:specification_version) if method_defined?(:specification_version)
19
+
20
+ # Hack to fix github's strange marshal file
21
+ def specification_version
22
+ @specification_version && @specification_version.to_i
23
+ end
24
+
25
+ alias full_gem_path_without_location full_gem_path
26
+ def full_gem_path
27
+ if defined?(@location) && @location
28
+ @location
29
+ else
30
+ full_gem_path_without_location
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ module Bundler
2
+ # Represents a lazily loaded gem specification, where the full specification
3
+ # is on the source server in rubygems' "quick" index. The proxy object is to
4
+ # be seeded with what we're given from the source's abbreviated index - the
5
+ # full specification will only be fetched when necesary.
6
+ class RemoteSpecification
7
+ attr_reader :name, :version, :platform
8
+ attr_accessor :source
9
+
10
+ def initialize(name, version, platform, source_uri)
11
+ @name = name
12
+ @version = version
13
+ @platform = platform
14
+ @source_uri = source_uri
15
+ end
16
+
17
+ def full_name
18
+ if platform == Gem::Platform::RUBY or platform.nil? then
19
+ "#{@name}-#{@version}"
20
+ else
21
+ "#{@name}-#{@version}-#{platform}"
22
+ end
23
+ end
24
+
25
+ # Because Rubyforge cannot be trusted to provide valid specifications
26
+ # once the remote gem is donwloaded, the backend specification will
27
+ # be swapped out.
28
+ def __swap__(spec)
29
+ @specification = spec
30
+ end
31
+
32
+ private
33
+
34
+ def _remote_uri
35
+ # "#{@source_uri}/quick/Marshal.4.8/#{@name}-#{@version}.gemspec.rz"
36
+ tuple = [@name, @version, @platform]
37
+ tuple = tuple - [nil, 'ruby', '']
38
+ "#{@source_uri}/quick/Marshal.4.8/#{tuple.join("-")}.gemspec.rz"
39
+ end
40
+
41
+ def _remote_specification
42
+ @specification ||= begin
43
+ deflated = Gem::RemoteFetcher.fetcher.fetch_path(_remote_uri)
44
+ inflated = Gem.inflate(deflated)
45
+ Marshal.load(inflated)
46
+ end
47
+ end
48
+
49
+ def method_missing(method, *args, &blk)
50
+ _remote_specification.send(method, *args, &blk)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,250 @@
1
+ # This is the latest iteration of the gem dependency resolving algorithm. As of now,
2
+ # it can resolve (as a success of failure) any set of gem dependencies we throw at it
3
+ # in a reasonable amount of time. The most iterations I've seen it take is about 150.
4
+ # The actual implementation of the algorithm is not as good as it could be yet, but that
5
+ # can come later.
6
+
7
+ # Extending Gem classes to add necessary tracking information
8
+ module Gem
9
+ class Dependency
10
+ def required_by
11
+ @required_by ||= []
12
+ end
13
+ end
14
+ class Specification
15
+ def required_by
16
+ @required_by ||= []
17
+ end
18
+ end
19
+ end
20
+
21
+ module Bundler
22
+ class GemNotFound < StandardError; end
23
+ class VersionConflict < StandardError; end
24
+
25
+ class Resolver
26
+
27
+ attr_reader :errors
28
+
29
+ # Figures out the best possible configuration of gems that satisfies
30
+ # the list of passed dependencies and any child dependencies without
31
+ # causing any gem activation errors.
32
+ #
33
+ # ==== Parameters
34
+ # *dependencies<Gem::Dependency>:: The list of dependencies to resolve
35
+ #
36
+ # ==== Returns
37
+ # <GemBundle>,nil:: If the list of dependencies can be resolved, a
38
+ # collection of gemspecs is returned. Otherwise, nil is returned.
39
+ def self.resolve(requirements, sources)
40
+ source_requirements = {}
41
+
42
+ requirements.each do |r|
43
+ next unless r.source
44
+ source_requirements[r.name] = r.source
45
+ end
46
+
47
+ resolver = new(sources, source_requirements)
48
+ result = catch(:success) do
49
+ resolver.resolve(requirements, {})
50
+ output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
51
+ o << " Conflict on: #{conflict.inspect}:\n"
52
+ o << " * #{conflict} (#{origin.version}) activated by #{origin.required_by.first}\n"
53
+ o << " * #{requirement} required by #{requirement.required_by.first}\n"
54
+ o << " All possible versions of origin requirements conflict."
55
+ end
56
+ raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
57
+ nil
58
+ end
59
+ if result
60
+ # Order gems in order of dependencies. Every gem's dependency is at
61
+ # a smaller index in the array.
62
+ ordered = []
63
+ result.values.each do |spec1|
64
+ spec1.no_bundle = true if source_requirements[spec1.name] == SystemGemSource.instance
65
+ index = nil
66
+ place = ordered.detect do |spec2|
67
+ spec1.dependencies.any? { |d| d.name == spec2.name }
68
+ end
69
+ place ?
70
+ ordered.insert(ordered.index(place), spec1) :
71
+ ordered << spec1
72
+ end
73
+ ordered.reverse
74
+ end
75
+ end
76
+
77
+ def initialize(sources, source_requirements)
78
+ @errors = {}
79
+ @stack = []
80
+ @specs = Hash.new { |h,k| h[k] = [] }
81
+ @by_gem = source_requirements
82
+ @cache = {}
83
+ @index = {}
84
+
85
+ sources.each do |source|
86
+ source.gems.each do |name, specs|
87
+ # Hack to work with a regular Gem::SourceIndex
88
+ specs = [specs] unless specs.is_a?(Array)
89
+ specs.compact.each do |spec|
90
+ next if @specs[spec.name].any? { |s| s.version == spec.version && s.platform == spec.platform }
91
+ @specs[spec.name] << spec
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def debug
98
+ puts yield if defined?($debug) && $debug
99
+ end
100
+
101
+ def resolve(reqs, activated)
102
+ # If the requirements are empty, then we are in a success state. Aka, all
103
+ # gem dependencies have been resolved.
104
+ throw :success, activated if reqs.empty?
105
+
106
+ debug { STDIN.gets ; print "\e[2J\e[f" ; "==== Iterating ====\n\n" }
107
+
108
+ # Sort dependencies so that the ones that are easiest to resolve are first.
109
+ # Easiest to resolve is defined by:
110
+ # 1) Is this gem already activated?
111
+ # 2) Do the version requirements include prereleased gems?
112
+ # 3) Sort by number of gems available in the source.
113
+ reqs = reqs.sort_by do |a|
114
+ [ activated[a.name] ? 0 : 1,
115
+ a.version_requirements.prerelease? ? 0 : 1,
116
+ @errors[a.name] ? 0 : 1,
117
+ activated[a.name] ? 0 : search(a).size ]
118
+ end
119
+
120
+ debug { "Activated:\n" + activated.values.map { |a| " #{a.name} (#{a.version})" }.join("\n") }
121
+ debug { "Requirements:\n" + reqs.map { |r| " #{r.name} (#{r.version_requirements})"}.join("\n") }
122
+
123
+ activated = activated.dup
124
+ # Pull off the first requirement so that we can resolve it
125
+ current = reqs.shift
126
+
127
+ debug { "Attempting:\n #{current.name} (#{current.version_requirements})"}
128
+
129
+ # Check if the gem has already been activated, if it has, we will make sure
130
+ # that the currently activated gem satisfies the requirement.
131
+ if existing = activated[current.name]
132
+ if current.version_requirements.satisfied_by?(existing.version)
133
+ debug { " * [SUCCESS] Already activated" }
134
+ @errors.delete(existing.name)
135
+ # Since the current requirement is satisfied, we can continue resolving
136
+ # the remaining requirements.
137
+ resolve(reqs, activated)
138
+ else
139
+ debug { " * [FAIL] Already activated" }
140
+ @errors[existing.name] = [existing, current]
141
+ debug { current.required_by.map {|d| " * #{d.name} (#{d.version_requirements})" }.join("\n") }
142
+ # debug { " * All current conflicts:\n" + @errors.keys.map { |c| " - #{c}" }.join("\n") }
143
+ # Since the current requirement conflicts with an activated gem, we need
144
+ # to backtrack to the current requirement's parent and try another version
145
+ # of it (maybe the current requirement won't be present anymore). If the
146
+ # current requirement is a root level requirement, we need to jump back to
147
+ # where the conflicting gem was activated.
148
+ parent = current.required_by.last || existing.required_by.last
149
+ # We track the spot where the current gem was activated because we need
150
+ # to keep a list of every spot a failure happened.
151
+ debug { " -> Jumping to: #{parent.name}" }
152
+ throw parent.name, existing.required_by.last.name
153
+ end
154
+ else
155
+ # There are no activated gems for the current requirement, so we are going
156
+ # to find all gems that match the current requirement and try them in decending
157
+ # order. We also need to keep a set of all conflicts that happen while trying
158
+ # this gem. This is so that if no versions work, we can figure out the best
159
+ # place to backtrack to.
160
+ conflicts = Set.new
161
+
162
+ # Fetch all gem versions matching the requirement
163
+ #
164
+ # TODO: Warn / error when no matching versions are found.
165
+ matching_versions = search(current)
166
+
167
+ if matching_versions.empty?
168
+ if current.required_by.empty?
169
+ location = @by_gem[current.name] ? @by_gem[current.name] : "any of the sources"
170
+ raise GemNotFound, "Could not find gem '#{current}' in #{location}"
171
+ end
172
+ Bundler.logger.warn "Could not find gem '#{current}' (required by '#{current.required_by.last}') in any of the sources"
173
+ end
174
+
175
+ matching_versions.reverse_each do |spec|
176
+ conflict = resolve_requirement(spec, current, reqs.dup, activated.dup)
177
+ conflicts << conflict if conflict
178
+ end
179
+ # If the current requirement is a root level gem and we have conflicts, we
180
+ # can figure out the best spot to backtrack to.
181
+ if current.required_by.empty? && !conflicts.empty?
182
+ # Check the current "catch" stack for the first one that is included in the
183
+ # conflicts set. That is where the parent of the conflicting gem was required.
184
+ # By jumping back to this spot, we can try other version of the parent of
185
+ # the conflicting gem, hopefully finding a combination that activates correctly.
186
+ @stack.reverse_each do |savepoint|
187
+ if conflicts.include?(savepoint)
188
+ debug { " -> Jumping to: #{savepoint}" }
189
+ throw savepoint
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def resolve_requirement(spec, requirement, reqs, activated)
197
+ # We are going to try activating the spec. We need to keep track of stack of
198
+ # requirements that got us to the point of activating this gem.
199
+ spec.required_by.replace requirement.required_by
200
+ spec.required_by << requirement
201
+
202
+ activated[spec.name] = spec
203
+ debug { " Activating: #{spec.name} (#{spec.version})" }
204
+ debug { spec.required_by.map { |d| " * #{d.name} (#{d.version_requirements})" }.join("\n") }
205
+
206
+ # Now, we have to loop through all child dependencies and add them to our
207
+ # array of requirements.
208
+ debug { " Dependencies"}
209
+ spec.dependencies.each do |dep|
210
+ next if dep.type == :development
211
+ debug { " * #{dep.name} (#{dep.version_requirements})" }
212
+ dep.required_by.replace(requirement.required_by)
213
+ dep.required_by << requirement
214
+ reqs << dep
215
+ end
216
+
217
+ # We create a savepoint and mark it by the name of the requirement that caused
218
+ # the gem to be activated. If the activated gem ever conflicts, we are able to
219
+ # jump back to this point and try another version of the gem.
220
+ length = @stack.length
221
+ @stack << requirement.name
222
+ retval = catch(requirement.name) do
223
+ resolve(reqs, activated)
224
+ end
225
+ # Since we're doing a lot of throw / catches. A push does not necessarily match
226
+ # up to a pop. So, we simply slice the stack back to what it was before the catch
227
+ # block.
228
+ @stack.slice!(length..-1)
229
+ retval
230
+ end
231
+
232
+ def search(dependency)
233
+ @cache[dependency.hash] ||= begin
234
+ pinned = @by_gem[dependency.name].gems if @by_gem[dependency.name]
235
+ specs = (pinned || @specs)[dependency.name]
236
+
237
+ wants_prerelease = dependency.version_requirements.prerelease?
238
+ only_prerelease = specs.all? {|spec| spec.version.prerelease? }
239
+
240
+ found = specs.select { |spec| dependency =~ spec }
241
+
242
+ unless wants_prerelease || (pinned && only_prerelease)
243
+ found.reject! { |spec| spec.version.prerelease? }
244
+ end
245
+
246
+ found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
247
+ end
248
+ end
249
+ end
250
+ end