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.
- data/CHANGELOG.md +7 -0
- data/README.md +3 -1
- data/ROADMAP.md +19 -34
- data/TODO.md +16 -0
- data/lib/bundler.rb +87 -41
- data/lib/bundler/cli.rb +115 -73
- data/lib/bundler/definition.rb +203 -34
- data/lib/bundler/dependency.rb +77 -0
- data/lib/bundler/dsl.rb +57 -16
- data/lib/bundler/environment.rb +13 -109
- data/lib/bundler/graph.rb +130 -0
- data/lib/bundler/index.rb +19 -41
- data/lib/bundler/installer.rb +22 -58
- data/lib/bundler/lazy_specification.rb +67 -0
- data/lib/bundler/lockfile_parser.rb +100 -0
- data/lib/bundler/remote_specification.rb +2 -1
- data/lib/bundler/resolver.rb +185 -45
- data/lib/bundler/rubygems_ext.rb +105 -6
- data/lib/bundler/runtime.rb +24 -82
- data/lib/bundler/settings.rb +9 -5
- data/lib/bundler/setup.rb +6 -14
- data/lib/bundler/shared_helpers.rb +2 -25
- data/lib/bundler/source.rb +306 -132
- data/lib/bundler/spec_set.rb +77 -17
- data/lib/bundler/templates/Executable +28 -0
- data/lib/bundler/vendor/thor.rb +31 -4
- data/lib/bundler/vendor/thor/invocation.rb +1 -1
- data/lib/bundler/vendor/thor/parser/arguments.rb +14 -3
- data/lib/bundler/vendor/thor/parser/options.rb +0 -5
- data/lib/bundler/vendor/thor/shell/basic.rb +28 -0
- data/lib/bundler/vendor/thor/task.rb +6 -6
- data/lib/bundler/vendor/thor/util.rb +13 -14
- data/lib/bundler/vendor/thor/version.rb +1 -1
- data/lib/bundler/version.rb +1 -1
- metadata +11 -5
- data/lib/bundler/templates/environment.erb +0 -91
@@ -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
|
data/lib/bundler/resolver.rb
CHANGED
@@ -7,12 +7,12 @@ require 'set'
|
|
7
7
|
|
8
8
|
# Extending Gem classes to add necessary tracking information
|
9
9
|
module Gem
|
10
|
-
class
|
10
|
+
class Specification
|
11
11
|
def required_by
|
12
12
|
@required_by ||= []
|
13
13
|
end
|
14
14
|
end
|
15
|
-
class
|
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
|
-
|
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.
|
41
|
-
|
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
|
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
|
-
|
103
|
-
|
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
|
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
|
-
|
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 |
|
177
|
-
conflict = resolve_requirement(
|
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(
|
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
|
-
|
201
|
-
|
290
|
+
spec_group.required_by.replace requirement.required_by
|
291
|
+
spec_group.required_by << requirement
|
202
292
|
|
203
|
-
activated[
|
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
|
-
|
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
|
-
|
235
|
-
|
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
|