bundler 0.9.26 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

@@ -0,0 +1,67 @@
1
+ require "uri"
2
+ require "rubygems/spec_fetcher"
3
+
4
+ module Bundler
5
+ class LazySpecification
6
+ include Gem::MatchPlatform
7
+
8
+ attr_reader :name, :version, :dependencies, :platform
9
+ attr_accessor :source
10
+
11
+ def initialize(name, version, platform, source = nil)
12
+ @name = name
13
+ @version = version
14
+ @dependencies = []
15
+ @platform = platform
16
+ @source = source
17
+ @specification = nil
18
+ end
19
+
20
+ def full_name
21
+ if platform == Gem::Platform::RUBY or platform.nil? then
22
+ "#{@name}-#{@version}"
23
+ else
24
+ "#{@name}-#{@version}-#{platform}"
25
+ end
26
+ end
27
+
28
+ def satisfies?(dependency)
29
+ @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version))
30
+ end
31
+
32
+ def to_lock
33
+ if platform == Gem::Platform::RUBY or platform.nil?
34
+ out = " #{name} (#{version})\n"
35
+ else
36
+ out = " #{name} (#{version}-#{platform})\n"
37
+ end
38
+
39
+ dependencies.sort_by {|d| d.name }.each do |dep|
40
+ next if dep.type == :development
41
+ out << " #{dep.to_lock}\n"
42
+ end
43
+
44
+ out
45
+ end
46
+
47
+ def __materialize__
48
+ @specification = source[self]
49
+ end
50
+
51
+ def respond_to?(*args)
52
+ super || @specification.respond_to?(*args)
53
+ end
54
+
55
+ private
56
+
57
+ def method_missing(method, *args, &blk)
58
+ if Gem::Specification.new.respond_to?(method)
59
+ raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification
60
+ @specification.send(method, *args, &blk)
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,100 @@
1
+ require "strscan"
2
+
3
+ module Bundler
4
+ class LockfileParser
5
+ attr_reader :sources, :dependencies, :specs, :platforms
6
+
7
+ def initialize(lockfile)
8
+ @platforms = []
9
+ @sources = []
10
+ @dependencies = []
11
+ @specs = []
12
+ @state = :source
13
+
14
+ lockfile.split(/\n+/).each do |line|
15
+ if line == "DEPENDENCIES"
16
+ @state = :dependency
17
+ elsif line == "PLATFORMS"
18
+ @state = :platform
19
+ else
20
+ send("parse_#{@state}", line)
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ TYPES = {
28
+ "GIT" => Bundler::Source::Git,
29
+ "GEM" => Bundler::Source::Rubygems,
30
+ "PATH" => Bundler::Source::Path
31
+ }
32
+
33
+ def parse_source(line)
34
+ case line
35
+ when "GIT", "GEM", "PATH"
36
+ @current_source = nil
37
+ @opts, @type = {}, line
38
+ when " specs:"
39
+ @current_source = TYPES[@type].from_lock(@opts)
40
+ @sources << @current_source
41
+ when /^ ([a-z]+): (.*)$/i
42
+ if @opts[$1]
43
+ @opts[$1] = Array(@opts[$1])
44
+ @opts[$1] << $2
45
+ else
46
+ @opts[$1] = $2
47
+ end
48
+ else
49
+ parse_spec(line)
50
+ end
51
+ end
52
+
53
+ NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'
54
+
55
+ def parse_dependency(line)
56
+ if line =~ %r{^ {2}#{NAME_VERSION}(!)?$}
57
+ name, version, pinned = $1, $2, $3
58
+
59
+ dep = Bundler::Dependency.new(name, version)
60
+
61
+ if pinned
62
+ dep.source = @specs.find { |s| s.name == dep.name }.source
63
+
64
+ # Path sources need to know what the default name / version
65
+ # to use in the case that there are no gemspecs present. A fake
66
+ # gemspec is created based on the version set on the dependency
67
+ # TODO: Use the version from the spec instead of from the dependency
68
+ if version =~ /^= (.+)$/ && dep.source.is_a?(Bundler::Source::Path)
69
+ dep.source.name = name
70
+ dep.source.version = $1
71
+ end
72
+ end
73
+
74
+ @dependencies << dep
75
+ end
76
+ end
77
+
78
+ def parse_spec(line)
79
+ if line =~ %r{^ {4}#{NAME_VERSION}$}
80
+ name, version = $1, Gem::Version.new($2)
81
+ platform = $3 ? Gem::Platform.new($3) : Gem::Platform::RUBY
82
+ @current_spec = LazySpecification.new(name, version, platform)
83
+ @current_spec.source = @current_source
84
+ @specs << @current_spec
85
+ elsif line =~ %r{^ {6}#{NAME_VERSION}$}
86
+ name, version = $1, $2
87
+ version = version.split(',').map { |d| d.strip } if version
88
+ dep = Gem::Dependency.new(name, version)
89
+ @current_spec.dependencies << dep
90
+ end
91
+ end
92
+
93
+ def parse_platform(line)
94
+ if line =~ /^ (.*)$/
95
+ @platforms << Gem::Platform.new($1)
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -7,6 +7,8 @@ module Bundler
7
7
  # be seeded with what we're given from the source's abbreviated index - the
8
8
  # full specification will only be fetched when necesary.
9
9
  class RemoteSpecification
10
+ include Gem::MatchPlatform
11
+
10
12
  attr_reader :name, :version, :platform
11
13
  attr_accessor :source
12
14
 
@@ -42,7 +44,6 @@ module Bundler
42
44
 
43
45
  def _remote_specification
44
46
  @specification ||= begin
45
- Bundler.ui.debug "Fetching spec for #{full_name}"
46
47
  Gem::SpecFetcher.new.fetch_spec([@name, @version, @platform], URI(@source_uri.to_s))
47
48
  end
48
49
  end
@@ -7,12 +7,12 @@ require 'set'
7
7
 
8
8
  # Extending Gem classes to add necessary tracking information
9
9
  module Gem
10
- class Dependency
10
+ class Specification
11
11
  def required_by
12
12
  @required_by ||= []
13
13
  end
14
14
  end
15
- class Specification
15
+ class Dependency
16
16
  def required_by
17
17
  @required_by ||= []
18
18
  end
@@ -21,6 +21,85 @@ end
21
21
 
22
22
  module Bundler
23
23
  class Resolver
24
+ ALL = [ Gem::Platform::RUBY,
25
+ Gem::Platform::JAVA,
26
+ Gem::Platform::MSWIN,
27
+ Gem::Platform::MING]
28
+
29
+ class SpecGroup < Array
30
+ attr_reader :activated, :required_by
31
+
32
+ def initialize(a)
33
+ super
34
+ @required_by = []
35
+ @activated = []
36
+ @dependencies = nil
37
+ @specs = {}
38
+
39
+ ALL.each do |p|
40
+ @specs[p] = reverse.find { |s| s.match_platform(p) }
41
+ end
42
+ end
43
+
44
+ def initialize_copy(o)
45
+ super
46
+ @required_by = o.required_by.dup
47
+ @activated = o.activated.dup
48
+ end
49
+
50
+ def to_specs
51
+ specs = {}
52
+ each do |s|
53
+ next if specs[s.platform]
54
+ lazy_spec = LazySpecification.new(s.name, s.version, s.platform, s.source)
55
+ lazy_spec.dependencies.replace s.dependencies
56
+ specs[s.platform] = lazy_spec
57
+ end
58
+ specs.values
59
+ end
60
+
61
+ def activate_platform(platform)
62
+ unless @activated.include?(platform)
63
+ @activated << platform
64
+ return __dependencies[platform] || []
65
+ end
66
+ []
67
+ end
68
+
69
+ def name
70
+ @name ||= first.name
71
+ end
72
+
73
+ def version
74
+ @version ||= first.version
75
+ end
76
+
77
+ def source
78
+ @source ||= first.source
79
+ end
80
+
81
+ def for?(platform)
82
+ @specs[platform]
83
+ end
84
+
85
+ private
86
+
87
+ def __dependencies
88
+ @dependencies ||= begin
89
+ dependencies = {}
90
+ ALL.each do |p|
91
+ if spec = @specs[p]
92
+ dependencies[p] = []
93
+ spec.dependencies.each do |dep|
94
+ next if dep.type == :development
95
+ dependencies[p] << DepProxy.new(dep, p)
96
+ end
97
+ end
98
+ end
99
+ dependencies
100
+ end
101
+ end
102
+ end
24
103
 
25
104
  attr_reader :errors
26
105
 
@@ -34,35 +113,21 @@ module Bundler
34
113
  # ==== Returns
35
114
  # <GemBundle>,nil:: If the list of dependencies can be resolved, a
36
115
  # collection of gemspecs is returned. Otherwise, nil is returned.
37
- def self.resolve(requirements, index, source_requirements = {})
38
- resolver = new(index, source_requirements)
116
+ def self.resolve(requirements, index, source_requirements = {}, base = [])
117
+ base = SpecSet.new(base) unless base.is_a?(SpecSet)
118
+ resolver = new(index, source_requirements, base)
39
119
  result = catch(:success) do
40
- resolver.resolve(requirements, {})
41
- output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
42
- if origin
43
- o << " Conflict on: #{conflict.inspect}:\n"
44
- o << " * #{conflict} (#{origin.version}) activated by #{origin.required_by.first}\n"
45
- o << " * #{requirement} required"
46
- if requirement.required_by.first
47
- o << " by #{requirement.required_by.first}\n"
48
- else
49
- o << " in Gemfile\n"
50
- end
51
- else
52
- o << " #{requirement} not found in any of the sources\n"
53
- o << " required by #{requirement.required_by.first}\n"
54
- end
55
- o << " All possible versions of origin requirements conflict."
56
- end
57
- raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
120
+ resolver.start(requirements)
121
+ raise resolver.version_conflict
58
122
  nil
59
123
  end
60
- SpecSet.new(result.values)
124
+ SpecSet.new(result)
61
125
  end
62
126
 
63
- def initialize(index, source_requirements)
127
+ def initialize(index, source_requirements, base)
64
128
  @errors = {}
65
129
  @stack = []
130
+ @base = base
66
131
  @index = index
67
132
  @source_requirements = source_requirements
68
133
  end
@@ -75,10 +140,20 @@ module Bundler
75
140
  end
76
141
  end
77
142
 
143
+ def successify(activated)
144
+ activated.values.map { |s| s.to_specs }.flatten.compact
145
+ end
146
+
147
+ def start(reqs)
148
+ activated = {}
149
+
150
+ resolve(reqs, activated)
151
+ end
152
+
78
153
  def resolve(reqs, activated)
79
154
  # If the requirements are empty, then we are in a success state. Aka, all
80
155
  # gem dependencies have been resolved.
81
- throw :success, activated if reqs.empty?
156
+ throw :success, successify(activated) if reqs.empty?
82
157
 
83
158
  debug { print "\e[2J\e[f" ; "==== Iterating ====\n\n" }
84
159
 
@@ -99,25 +174,31 @@ module Bundler
99
174
 
100
175
  activated = activated.dup
101
176
 
102
- if reqs.first.name == "bundler" && !activated["bundler"]
103
- # activate the current version of bundler before other versions
104
- bundler_version = ENV["BUNDLER_VERSION"] || Bundler::VERSION
105
- current = Gem::Dependency.new("bundler", bundler_version, reqs.first.type)
106
- else
107
- # Pull off the first requirement so that we can resolve it
108
- current = reqs.shift
109
- end
177
+ # Pull off the first requirement so that we can resolve it
178
+ current = reqs.shift
110
179
 
111
180
  debug { "Attempting:\n #{current.name} (#{current.requirement})"}
112
181
 
113
182
  # Check if the gem has already been activated, if it has, we will make sure
114
183
  # that the currently activated gem satisfies the requirement.
115
- if existing = activated[current.name]
184
+ if existing = activated[current.name] or current.name == 'bundler'
185
+ # Force the current
186
+ if current.name == 'bundler' && !existing
187
+ existing = search(DepProxy.new(Gem::Dependency.new('bundler', VERSION), Gem::Platform::RUBY)).first
188
+ activated[current.name] = existing
189
+ end
190
+
116
191
  if current.requirement.satisfied_by?(existing.version)
117
192
  debug { " * [SUCCESS] Already activated" }
118
193
  @errors.delete(existing.name)
119
194
  # Since the current requirement is satisfied, we can continue resolving
120
195
  # the remaining requirements.
196
+
197
+ # I have no idea if this is the right way to do it, but let's see if it works
198
+ # The current requirement might activate some other platforms, so let's try
199
+ # adding those requirements here.
200
+ reqs.concat existing.activate_platform(current.__platform)
201
+
121
202
  resolve(reqs, activated)
122
203
  else
123
204
  debug { " * [FAIL] Already activated" }
@@ -129,11 +210,20 @@ module Bundler
129
210
  # of it (maybe the current requirement won't be present anymore). If the
130
211
  # current requirement is a root level requirement, we need to jump back to
131
212
  # where the conflicting gem was activated.
132
- parent = current.required_by.last || existing.required_by.last
213
+ parent = current.required_by.last
214
+ # `existing` could not respond to required_by if it is part of the base set
215
+ # of specs that was passed to the resolver (aka, instance of LazySpecification)
216
+ parent ||= existing.required_by.last if existing.respond_to?(:required_by)
133
217
  # We track the spot where the current gem was activated because we need
134
218
  # to keep a list of every spot a failure happened.
135
219
  debug { " -> Jumping to: #{parent.name}" }
136
- throw parent.name, existing.required_by.last.name
220
+ if parent
221
+ throw parent.name, existing.respond_to?(:required_by) && existing.required_by.last.name
222
+ else
223
+ # The original set of dependencies conflict with the base set of specs
224
+ # passed to the resolver. This is by definition an impossible resolve.
225
+ raise version_conflict
226
+ end
137
227
  end
138
228
  else
139
229
  # There are no activated gems for the current requirement, so we are going
@@ -173,8 +263,8 @@ module Bundler
173
263
  end
174
264
  end
175
265
 
176
- matching_versions.reverse_each do |spec|
177
- conflict = resolve_requirement(spec, current, reqs.dup, activated.dup)
266
+ matching_versions.reverse_each do |spec_group|
267
+ conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup)
178
268
  conflicts << conflict if conflict
179
269
  end
180
270
  # If the current requirement is a root level gem and we have conflicts, we
@@ -194,20 +284,22 @@ module Bundler
194
284
  end
195
285
  end
196
286
 
197
- def resolve_requirement(spec, requirement, reqs, activated)
287
+ def resolve_requirement(spec_group, requirement, reqs, activated)
198
288
  # We are going to try activating the spec. We need to keep track of stack of
199
289
  # requirements that got us to the point of activating this gem.
200
- spec.required_by.replace requirement.required_by
201
- spec.required_by << requirement
290
+ spec_group.required_by.replace requirement.required_by
291
+ spec_group.required_by << requirement
202
292
 
203
- activated[spec.name] = spec
293
+ activated[spec_group.name] = spec_group
204
294
  debug { " Activating: #{spec.name} (#{spec.version})" }
205
295
  debug { spec.required_by.map { |d| " * #{d.name} (#{d.requirement})" }.join("\n") }
206
296
 
297
+ dependencies = spec_group.activate_platform(requirement.__platform)
298
+
207
299
  # Now, we have to loop through all child dependencies and add them to our
208
300
  # array of requirements.
209
301
  debug { " Dependencies"}
210
- spec.dependencies.each do |dep|
302
+ dependencies.each do |dep|
211
303
  next if dep.type == :development
212
304
  debug { " * #{dep.name} (#{dep.requirement})" }
213
305
  dep.required_by.replace(requirement.required_by)
@@ -231,8 +323,56 @@ module Bundler
231
323
  end
232
324
 
233
325
  def search(dep)
234
- index = @source_requirements[dep.name] || @index
235
- index.search(dep)
326
+ results = @base[dep.name]
327
+
328
+ if results.empty?
329
+ index = @source_requirements[dep.name] || @index
330
+ results = index.search_for_all_platforms(dep.dep)
331
+ end
332
+
333
+ if results.any?
334
+ version = results.first.version
335
+ nested = [[]]
336
+ results.each do |spec|
337
+ if spec.version != version
338
+ nested << []
339
+ version = spec.version
340
+ end
341
+ nested.last << spec
342
+ end
343
+ nested.map { |a| SpecGroup.new(a) }.select { |sg| sg.for?(dep.__platform) }
344
+ else
345
+ []
346
+ end
347
+ end
348
+
349
+ def version_conflict
350
+ VersionConflict.new(
351
+ errors.keys,
352
+ "No compatible versions could be found for required dependencies:\n #{error_message}")
353
+ end
354
+
355
+ def error_message
356
+ output = errors.inject("") do |o, (conflict, (origin, requirement))|
357
+ if origin
358
+ o << " Conflict on: #{conflict.inspect}:\n"
359
+ if origin.respond_to?(:required_by) && required_by = origin.required_by.first
360
+ o << " * #{conflict} (#{origin.version}) activated by #{required_by}\n"
361
+ else
362
+ o << " * #{conflict} (#{origin.version}) in Gemfile.lock\n"
363
+ end
364
+ o << " * #{requirement} required"
365
+ if requirement.required_by.first
366
+ o << " by #{requirement.required_by.first}\n"
367
+ else
368
+ o << " in Gemfile\n"
369
+ end
370
+ else
371
+ o << " #{requirement} not found in any of the sources\n"
372
+ o << " required by #{requirement.required_by.first}\n"
373
+ end
374
+ o << " All possible versions of origin requirements conflict."
375
+ end
236
376
  end
237
377
  end
238
378
  end