bundler-maglev- 1.0.21
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/.gitignore +22 -0
- data/.travis.yml +32 -0
- data/CHANGELOG.md +805 -0
- data/ISSUES.md +62 -0
- data/LICENSE +23 -0
- data/README.md +29 -0
- data/Rakefile +212 -0
- data/UPGRADING.md +103 -0
- data/bin/bundle +22 -0
- data/bundler.gemspec +30 -0
- data/lib/bundler.rb +286 -0
- data/lib/bundler/capistrano.rb +11 -0
- data/lib/bundler/cli.rb +520 -0
- data/lib/bundler/definition.rb +438 -0
- data/lib/bundler/dependency.rb +134 -0
- data/lib/bundler/deployment.rb +58 -0
- data/lib/bundler/dsl.rb +257 -0
- data/lib/bundler/environment.rb +47 -0
- data/lib/bundler/gem_helper.rb +151 -0
- data/lib/bundler/gem_installer.rb +9 -0
- data/lib/bundler/gem_tasks.rb +2 -0
- data/lib/bundler/graph.rb +130 -0
- data/lib/bundler/index.rb +138 -0
- data/lib/bundler/installer.rb +97 -0
- data/lib/bundler/lazy_specification.rb +74 -0
- data/lib/bundler/lockfile_parser.rb +108 -0
- data/lib/bundler/remote_specification.rb +59 -0
- data/lib/bundler/resolver.rb +464 -0
- data/lib/bundler/rubygems_ext.rb +237 -0
- data/lib/bundler/rubygems_integration.rb +349 -0
- data/lib/bundler/runtime.rb +152 -0
- data/lib/bundler/settings.rb +115 -0
- data/lib/bundler/setup.rb +23 -0
- data/lib/bundler/shared_helpers.rb +71 -0
- data/lib/bundler/source.rb +708 -0
- data/lib/bundler/spec_set.rb +135 -0
- data/lib/bundler/templates/Executable +16 -0
- data/lib/bundler/templates/Gemfile +4 -0
- data/lib/bundler/templates/newgem/Gemfile.tt +4 -0
- data/lib/bundler/templates/newgem/Rakefile.tt +1 -0
- data/lib/bundler/templates/newgem/bin/newgem.tt +3 -0
- data/lib/bundler/templates/newgem/gitignore.tt +4 -0
- data/lib/bundler/templates/newgem/lib/newgem.rb.tt +9 -0
- data/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +7 -0
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +24 -0
- data/lib/bundler/ui.rb +73 -0
- data/lib/bundler/vendor/thor.rb +358 -0
- data/lib/bundler/vendor/thor/actions.rb +314 -0
- data/lib/bundler/vendor/thor/actions/create_file.rb +105 -0
- data/lib/bundler/vendor/thor/actions/create_link.rb +57 -0
- data/lib/bundler/vendor/thor/actions/directory.rb +93 -0
- data/lib/bundler/vendor/thor/actions/empty_directory.rb +134 -0
- data/lib/bundler/vendor/thor/actions/file_manipulation.rb +270 -0
- data/lib/bundler/vendor/thor/actions/inject_into_file.rb +109 -0
- data/lib/bundler/vendor/thor/base.rb +576 -0
- data/lib/bundler/vendor/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/bundler/vendor/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/bundler/vendor/thor/error.rb +30 -0
- data/lib/bundler/vendor/thor/group.rb +273 -0
- data/lib/bundler/vendor/thor/invocation.rb +168 -0
- data/lib/bundler/vendor/thor/parser.rb +4 -0
- data/lib/bundler/vendor/thor/parser/argument.rb +67 -0
- data/lib/bundler/vendor/thor/parser/arguments.rb +161 -0
- data/lib/bundler/vendor/thor/parser/option.rb +120 -0
- data/lib/bundler/vendor/thor/parser/options.rb +175 -0
- data/lib/bundler/vendor/thor/rake_compat.rb +66 -0
- data/lib/bundler/vendor/thor/runner.rb +309 -0
- data/lib/bundler/vendor/thor/shell.rb +88 -0
- data/lib/bundler/vendor/thor/shell/basic.rb +302 -0
- data/lib/bundler/vendor/thor/shell/color.rb +108 -0
- data/lib/bundler/vendor/thor/shell/html.rb +121 -0
- data/lib/bundler/vendor/thor/task.rb +113 -0
- data/lib/bundler/vendor/thor/util.rb +229 -0
- data/lib/bundler/vendor/thor/version.rb +3 -0
- data/lib/bundler/vendored_thor.rb +7 -0
- data/lib/bundler/version.rb +6 -0
- data/lib/bundler/vlad.rb +11 -0
- data/man/bundle-config.ronn +90 -0
- data/man/bundle-exec.ronn +111 -0
- data/man/bundle-install.ronn +317 -0
- data/man/bundle-package.ronn +59 -0
- data/man/bundle-update.ronn +176 -0
- data/man/bundle.ronn +80 -0
- data/man/gemfile.5.ronn +284 -0
- data/man/index.txt +6 -0
- data/spec/bundler/gem_helper_spec.rb +143 -0
- data/spec/cache/gems_spec.rb +230 -0
- data/spec/cache/git_spec.rb +12 -0
- data/spec/cache/path_spec.rb +27 -0
- data/spec/cache/platform_spec.rb +57 -0
- data/spec/install/deploy_spec.rb +197 -0
- data/spec/install/deprecated_spec.rb +37 -0
- data/spec/install/gems/c_ext_spec.rb +48 -0
- data/spec/install/gems/env_spec.rb +107 -0
- data/spec/install/gems/flex_spec.rb +313 -0
- data/spec/install/gems/groups_spec.rb +259 -0
- data/spec/install/gems/packed_spec.rb +84 -0
- data/spec/install/gems/platform_spec.rb +192 -0
- data/spec/install/gems/resolving_spec.rb +72 -0
- data/spec/install/gems/simple_case_spec.rb +770 -0
- data/spec/install/gems/sudo_spec.rb +74 -0
- data/spec/install/gems/win32_spec.rb +26 -0
- data/spec/install/gemspec_spec.rb +125 -0
- data/spec/install/git_spec.rb +570 -0
- data/spec/install/invalid_spec.rb +35 -0
- data/spec/install/path_spec.rb +405 -0
- data/spec/install/upgrade_spec.rb +26 -0
- data/spec/lock/git_spec.rb +35 -0
- data/spec/lock/lockfile_spec.rb +739 -0
- data/spec/other/check_spec.rb +221 -0
- data/spec/other/config_spec.rb +40 -0
- data/spec/other/console_spec.rb +54 -0
- data/spec/other/exec_spec.rb +248 -0
- data/spec/other/ext_spec.rb +37 -0
- data/spec/other/help_spec.rb +39 -0
- data/spec/other/init_spec.rb +40 -0
- data/spec/other/newgem_spec.rb +46 -0
- data/spec/other/open_spec.rb +35 -0
- data/spec/other/show_spec.rb +82 -0
- data/spec/quality_spec.rb +62 -0
- data/spec/resolver/basic_spec.rb +20 -0
- data/spec/resolver/platform_spec.rb +82 -0
- data/spec/runtime/executable_spec.rb +110 -0
- data/spec/runtime/load_spec.rb +107 -0
- data/spec/runtime/platform_spec.rb +90 -0
- data/spec/runtime/require_spec.rb +231 -0
- data/spec/runtime/setup_spec.rb +730 -0
- data/spec/runtime/with_clean_env_spec.rb +15 -0
- data/spec/spec_helper.rb +92 -0
- data/spec/support/builders.rb +597 -0
- data/spec/support/helpers.rb +239 -0
- data/spec/support/indexes.rb +112 -0
- data/spec/support/matchers.rb +77 -0
- data/spec/support/path.rb +71 -0
- data/spec/support/platforms.rb +53 -0
- data/spec/support/ruby_ext.rb +20 -0
- data/spec/support/rubygems_ext.rb +37 -0
- data/spec/support/rubygems_hax/platform.rb +11 -0
- data/spec/support/sudo.rb +21 -0
- data/spec/update/gems_spec.rb +122 -0
- data/spec/update/git_spec.rb +196 -0
- data/spec/update/source_spec.rb +51 -0
- metadata +296 -0
@@ -0,0 +1,108 @@
|
|
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(/(\r?\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
|
+
value = $2
|
43
|
+
value = true if value == "true"
|
44
|
+
value = false if value == "false"
|
45
|
+
|
46
|
+
key = $1
|
47
|
+
|
48
|
+
if @opts[key]
|
49
|
+
@opts[key] = Array(@opts[key])
|
50
|
+
@opts[key] << value
|
51
|
+
else
|
52
|
+
@opts[key] = value
|
53
|
+
end
|
54
|
+
else
|
55
|
+
parse_spec(line)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'
|
60
|
+
|
61
|
+
def parse_dependency(line)
|
62
|
+
if line =~ %r{^ {2}#{NAME_VERSION}(!)?$}
|
63
|
+
name, version, pinned = $1, $2, $4
|
64
|
+
version = version.split(",").map { |d| d.strip } if version
|
65
|
+
|
66
|
+
dep = Bundler::Dependency.new(name, version)
|
67
|
+
|
68
|
+
if pinned && dep.name != 'bundler'
|
69
|
+
spec = @specs.find { |s| s.name == dep.name }
|
70
|
+
dep.source = spec.source if spec
|
71
|
+
|
72
|
+
# Path sources need to know what the default name / version
|
73
|
+
# to use in the case that there are no gemspecs present. A fake
|
74
|
+
# gemspec is created based on the version set on the dependency
|
75
|
+
# TODO: Use the version from the spec instead of from the dependency
|
76
|
+
if version && version.size == 1 && version.first =~ /^\s*= (.+)\s*$/ && dep.source.is_a?(Bundler::Source::Path)
|
77
|
+
dep.source.name = name
|
78
|
+
dep.source.version = $1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@dependencies << dep
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_spec(line)
|
87
|
+
if line =~ %r{^ {4}#{NAME_VERSION}$}
|
88
|
+
name, version = $1, Gem::Version.new($2)
|
89
|
+
platform = $3 ? Gem::Platform.new($3) : Gem::Platform::RUBY
|
90
|
+
@current_spec = LazySpecification.new(name, version, platform)
|
91
|
+
@current_spec.source = @current_source
|
92
|
+
@specs << @current_spec
|
93
|
+
elsif line =~ %r{^ {6}#{NAME_VERSION}$}
|
94
|
+
name, version = $1, $2
|
95
|
+
version = version.split(',').map { |d| d.strip } if version
|
96
|
+
dep = Gem::Dependency.new(name, version)
|
97
|
+
@current_spec.dependencies << dep
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_platform(line)
|
102
|
+
if line =~ /^ (.*)$/
|
103
|
+
@platforms << Gem::Platform.new($1)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "rubygems/spec_fetcher"
|
3
|
+
|
4
|
+
module Bundler
|
5
|
+
# Represents a lazily loaded gem specification, where the full specification
|
6
|
+
# is on the source server in rubygems' "quick" index. The proxy object is to
|
7
|
+
# be seeded with what we're given from the source's abbreviated index - the
|
8
|
+
# full specification will only be fetched when necesary.
|
9
|
+
class RemoteSpecification
|
10
|
+
include MatchPlatform
|
11
|
+
|
12
|
+
attr_reader :name, :version, :platform
|
13
|
+
attr_accessor :source
|
14
|
+
|
15
|
+
def initialize(name, version, platform, source_uri)
|
16
|
+
@name = name
|
17
|
+
@version = version
|
18
|
+
@platform = platform
|
19
|
+
@source_uri = source_uri
|
20
|
+
end
|
21
|
+
|
22
|
+
# Needed before installs, since the arch matters then and quick
|
23
|
+
# specs don't bother to include the arch in the platform string
|
24
|
+
def fetch_platform
|
25
|
+
@platform = _remote_specification.platform
|
26
|
+
end
|
27
|
+
|
28
|
+
def full_name
|
29
|
+
if platform == Gem::Platform::RUBY or platform.nil? then
|
30
|
+
"#{@name}-#{@version}"
|
31
|
+
else
|
32
|
+
"#{@name}-#{@version}-#{platform}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Because Rubyforge cannot be trusted to provide valid specifications
|
37
|
+
# once the remote gem is downloaded, the backend specification will
|
38
|
+
# be swapped out.
|
39
|
+
def __swap__(spec)
|
40
|
+
@specification = spec
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def _remote_specification
|
46
|
+
@specification ||= begin
|
47
|
+
Gem::SpecFetcher.new.fetch_spec([@name, @version, @platform], URI(@source_uri.to_s))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(method, *args, &blk)
|
52
|
+
if Gem::Specification.new.respond_to?(method)
|
53
|
+
_remote_specification.send(method, *args, &blk)
|
54
|
+
else
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,464 @@
|
|
1
|
+
require 'set'
|
2
|
+
# This is the latest iteration of the gem dependency resolving algorithm. As of now,
|
3
|
+
# it can resolve (as a success or failure) any set of gem dependencies we throw at it
|
4
|
+
# in a reasonable amount of time. The most iterations I've seen it take is about 150.
|
5
|
+
# The actual implementation of the algorithm is not as good as it could be yet, but that
|
6
|
+
# can come later.
|
7
|
+
|
8
|
+
# Extending Gem classes to add necessary tracking information
|
9
|
+
module Gem
|
10
|
+
class Specification
|
11
|
+
def required_by
|
12
|
+
@required_by ||= []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
class Dependency
|
16
|
+
def required_by
|
17
|
+
@required_by ||= []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Bundler
|
23
|
+
class Resolver
|
24
|
+
ALL = Bundler::Dependency::PLATFORM_MAP.values.uniq.freeze
|
25
|
+
|
26
|
+
class SpecGroup < Array
|
27
|
+
include GemHelpers
|
28
|
+
|
29
|
+
attr_reader :activated, :required_by
|
30
|
+
|
31
|
+
def initialize(a)
|
32
|
+
super
|
33
|
+
@required_by = []
|
34
|
+
@activated = []
|
35
|
+
@dependencies = nil
|
36
|
+
@specs = {}
|
37
|
+
|
38
|
+
ALL.each do |p|
|
39
|
+
@specs[p] = reverse.find { |s| s.match_platform(p) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize_copy(o)
|
44
|
+
super
|
45
|
+
@required_by = o.required_by.dup
|
46
|
+
@activated = o.activated.dup
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_specs
|
50
|
+
specs = {}
|
51
|
+
|
52
|
+
@activated.each do |p|
|
53
|
+
if s = @specs[p]
|
54
|
+
platform = generic(Gem::Platform.new(s.platform))
|
55
|
+
next if specs[platform]
|
56
|
+
|
57
|
+
lazy_spec = LazySpecification.new(name, version, platform, source)
|
58
|
+
lazy_spec.dependencies.replace s.dependencies
|
59
|
+
specs[platform] = lazy_spec
|
60
|
+
end
|
61
|
+
end
|
62
|
+
specs.values
|
63
|
+
end
|
64
|
+
|
65
|
+
def activate_platform(platform)
|
66
|
+
unless @activated.include?(platform)
|
67
|
+
@activated << platform
|
68
|
+
return __dependencies[platform] || []
|
69
|
+
end
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
|
73
|
+
def name
|
74
|
+
@name ||= first.name
|
75
|
+
end
|
76
|
+
|
77
|
+
def version
|
78
|
+
@version ||= first.version
|
79
|
+
end
|
80
|
+
|
81
|
+
def source
|
82
|
+
@source ||= first.source
|
83
|
+
end
|
84
|
+
|
85
|
+
def for?(platform)
|
86
|
+
@specs[platform]
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
"#{name} (#{version})"
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def __dependencies
|
96
|
+
@dependencies ||= begin
|
97
|
+
dependencies = {}
|
98
|
+
ALL.each do |p|
|
99
|
+
if spec = @specs[p]
|
100
|
+
dependencies[p] = []
|
101
|
+
spec.dependencies.each do |dep|
|
102
|
+
next if dep.type == :development
|
103
|
+
dependencies[p] << DepProxy.new(dep, p)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
dependencies
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_reader :errors
|
113
|
+
|
114
|
+
# Figures out the best possible configuration of gems that satisfies
|
115
|
+
# the list of passed dependencies and any child dependencies without
|
116
|
+
# causing any gem activation errors.
|
117
|
+
#
|
118
|
+
# ==== Parameters
|
119
|
+
# *dependencies<Gem::Dependency>:: The list of dependencies to resolve
|
120
|
+
#
|
121
|
+
# ==== Returns
|
122
|
+
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
|
123
|
+
# collection of gemspecs is returned. Otherwise, nil is returned.
|
124
|
+
def self.resolve(requirements, index, source_requirements = {}, base = [])
|
125
|
+
base = SpecSet.new(base) unless base.is_a?(SpecSet)
|
126
|
+
resolver = new(index, source_requirements, base)
|
127
|
+
result = catch(:success) do
|
128
|
+
resolver.start(requirements)
|
129
|
+
raise resolver.version_conflict
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
SpecSet.new(result)
|
133
|
+
end
|
134
|
+
|
135
|
+
def initialize(index, source_requirements, base)
|
136
|
+
@errors = {}
|
137
|
+
@stack = []
|
138
|
+
@base = base
|
139
|
+
@index = index
|
140
|
+
@gems_size = {}
|
141
|
+
@missing_gems = Hash.new(0)
|
142
|
+
@source_requirements = source_requirements
|
143
|
+
end
|
144
|
+
|
145
|
+
def debug
|
146
|
+
if ENV['DEBUG_RESOLVER']
|
147
|
+
debug_info = yield
|
148
|
+
debug_info = debug_info.inspect unless debug_info.is_a?(String)
|
149
|
+
$stderr.puts debug_info
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def successify(activated)
|
154
|
+
activated.values.map { |s| s.to_specs }.flatten.compact
|
155
|
+
end
|
156
|
+
|
157
|
+
def start(reqs)
|
158
|
+
activated = {}
|
159
|
+
|
160
|
+
resolve(reqs, activated)
|
161
|
+
end
|
162
|
+
|
163
|
+
def resolve(reqs, activated)
|
164
|
+
# If the requirements are empty, then we are in a success state. Aka, all
|
165
|
+
# gem dependencies have been resolved.
|
166
|
+
throw :success, successify(activated) if reqs.empty?
|
167
|
+
|
168
|
+
debug { print "\e[2J\e[f" ; "==== Iterating ====\n\n" }
|
169
|
+
|
170
|
+
# Sort dependencies so that the ones that are easiest to resolve are first.
|
171
|
+
# Easiest to resolve is defined by:
|
172
|
+
# 1) Is this gem already activated?
|
173
|
+
# 2) Do the version requirements include prereleased gems?
|
174
|
+
# 3) Sort by number of gems available in the source.
|
175
|
+
reqs = reqs.sort_by do |a|
|
176
|
+
[ activated[a.name] ? 0 : 1,
|
177
|
+
a.requirement.prerelease? ? 0 : 1,
|
178
|
+
@errors[a.name] ? 0 : 1,
|
179
|
+
activated[a.name] ? 0 : gems_size(a) ]
|
180
|
+
end
|
181
|
+
|
182
|
+
debug { "Activated:\n" + activated.values.map {|a| " #{a}" }.join("\n") }
|
183
|
+
debug { "Requirements:\n" + reqs.map {|r| " #{r}"}.join("\n") }
|
184
|
+
|
185
|
+
activated = activated.dup
|
186
|
+
|
187
|
+
# Pull off the first requirement so that we can resolve it
|
188
|
+
current = reqs.shift
|
189
|
+
|
190
|
+
debug { "Attempting:\n #{current}"}
|
191
|
+
|
192
|
+
# Check if the gem has already been activated, if it has, we will make sure
|
193
|
+
# that the currently activated gem satisfies the requirement.
|
194
|
+
existing = activated[current.name]
|
195
|
+
if existing || current.name == 'bundler'
|
196
|
+
# Force the current
|
197
|
+
if current.name == 'bundler' && !existing
|
198
|
+
existing = search(DepProxy.new(Gem::Dependency.new('bundler', VERSION), Gem::Platform::RUBY)).first
|
199
|
+
raise GemNotFound, %Q{Bundler could not find gem "bundler" (#{VERSION})} unless existing
|
200
|
+
existing.required_by << existing
|
201
|
+
activated['bundler'] = existing
|
202
|
+
end
|
203
|
+
|
204
|
+
if current.requirement.satisfied_by?(existing.version)
|
205
|
+
debug { " * [SUCCESS] Already activated" }
|
206
|
+
@errors.delete(existing.name)
|
207
|
+
# Since the current requirement is satisfied, we can continue resolving
|
208
|
+
# the remaining requirements.
|
209
|
+
|
210
|
+
# I have no idea if this is the right way to do it, but let's see if it works
|
211
|
+
# The current requirement might activate some other platforms, so let's try
|
212
|
+
# adding those requirements here.
|
213
|
+
reqs.concat existing.activate_platform(current.__platform)
|
214
|
+
|
215
|
+
resolve(reqs, activated)
|
216
|
+
else
|
217
|
+
debug { " * [FAIL] Already activated" }
|
218
|
+
@errors[existing.name] = [existing, current]
|
219
|
+
debug { current.required_by.map {|d| " * #{d.name} (#{d.requirement})" }.join("\n") }
|
220
|
+
# debug { " * All current conflicts:\n" + @errors.keys.map { |c| " - #{c}" }.join("\n") }
|
221
|
+
# Since the current requirement conflicts with an activated gem, we need
|
222
|
+
# to backtrack to the current requirement's parent and try another version
|
223
|
+
# of it (maybe the current requirement won't be present anymore). If the
|
224
|
+
# current requirement is a root level requirement, we need to jump back to
|
225
|
+
# where the conflicting gem was activated.
|
226
|
+
parent = current.required_by.last
|
227
|
+
# `existing` could not respond to required_by if it is part of the base set
|
228
|
+
# of specs that was passed to the resolver (aka, instance of LazySpecification)
|
229
|
+
parent ||= existing.required_by.last if existing.respond_to?(:required_by)
|
230
|
+
# We track the spot where the current gem was activated because we need
|
231
|
+
# to keep a list of every spot a failure happened.
|
232
|
+
if parent && parent.name != 'bundler'
|
233
|
+
debug { " -> Jumping to: #{parent.name}" }
|
234
|
+
required_by = existing.respond_to?(:required_by) && existing.required_by.last
|
235
|
+
throw parent.name, required_by && required_by.name
|
236
|
+
else
|
237
|
+
# The original set of dependencies conflict with the base set of specs
|
238
|
+
# passed to the resolver. This is by definition an impossible resolve.
|
239
|
+
raise version_conflict
|
240
|
+
end
|
241
|
+
end
|
242
|
+
else
|
243
|
+
# There are no activated gems for the current requirement, so we are going
|
244
|
+
# to find all gems that match the current requirement and try them in decending
|
245
|
+
# order. We also need to keep a set of all conflicts that happen while trying
|
246
|
+
# this gem. This is so that if no versions work, we can figure out the best
|
247
|
+
# place to backtrack to.
|
248
|
+
conflicts = Set.new
|
249
|
+
|
250
|
+
# Fetch all gem versions matching the requirement
|
251
|
+
#
|
252
|
+
# TODO: Warn / error when no matching versions are found.
|
253
|
+
matching_versions = search(current)
|
254
|
+
|
255
|
+
if matching_versions.empty?
|
256
|
+
if current.required_by.empty?
|
257
|
+
if base = @base[current.name] and !base.empty?
|
258
|
+
version = base.first.version
|
259
|
+
message = "You have requested:\n" \
|
260
|
+
" #{current.name} #{current.requirement}\n\n" \
|
261
|
+
"The bundle currently has #{current.name} locked at #{version}.\n" \
|
262
|
+
"Try running `bundle update #{current.name}`"
|
263
|
+
elsif current.source
|
264
|
+
name = current.name
|
265
|
+
versions = @source_requirements[name][name].map { |s| s.version }
|
266
|
+
message = "Could not find gem '#{current}' in #{current.source}.\n"
|
267
|
+
if versions.any?
|
268
|
+
message << "Source contains '#{name}' at: #{versions.join(', ')}"
|
269
|
+
else
|
270
|
+
message << "Source does not contain any versions of '#{current}'"
|
271
|
+
end
|
272
|
+
else
|
273
|
+
message = "Could not find gem '#{current}' "
|
274
|
+
if @index.sources.include?(Bundler::Source::Rubygems)
|
275
|
+
message << "in any of the gem sources listed in your Gemfile."
|
276
|
+
else
|
277
|
+
message << "in the gems available on this machine."
|
278
|
+
end
|
279
|
+
end
|
280
|
+
raise GemNotFound, message
|
281
|
+
else
|
282
|
+
@errors[current.name] = [nil, current]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
matching_versions.reverse_each do |spec_group|
|
287
|
+
conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup)
|
288
|
+
conflicts << conflict if conflict
|
289
|
+
end
|
290
|
+
# If the current requirement is a root level gem and we have conflicts, we
|
291
|
+
# can figure out the best spot to backtrack to.
|
292
|
+
if current.required_by.empty? && !conflicts.empty?
|
293
|
+
# Check the current "catch" stack for the first one that is included in the
|
294
|
+
# conflicts set. That is where the parent of the conflicting gem was required.
|
295
|
+
# By jumping back to this spot, we can try other version of the parent of
|
296
|
+
# the conflicting gem, hopefully finding a combination that activates correctly.
|
297
|
+
@stack.reverse_each do |savepoint|
|
298
|
+
if conflicts.include?(savepoint)
|
299
|
+
debug { " -> Jumping to: #{savepoint}" }
|
300
|
+
throw savepoint
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def resolve_requirement(spec_group, requirement, reqs, activated)
|
308
|
+
# We are going to try activating the spec. We need to keep track of stack of
|
309
|
+
# requirements that got us to the point of activating this gem.
|
310
|
+
spec_group.required_by.replace requirement.required_by
|
311
|
+
spec_group.required_by << requirement
|
312
|
+
|
313
|
+
activated[spec_group.name] = spec_group
|
314
|
+
debug { " Activating: #{spec_group.name} (#{spec_group.version})" }
|
315
|
+
debug { spec_group.required_by.map { |d| " * #{d.name} (#{d.requirement})" }.join("\n") }
|
316
|
+
|
317
|
+
dependencies = spec_group.activate_platform(requirement.__platform)
|
318
|
+
|
319
|
+
# Now, we have to loop through all child dependencies and add them to our
|
320
|
+
# array of requirements.
|
321
|
+
debug { " Dependencies"}
|
322
|
+
dependencies.each do |dep|
|
323
|
+
next if dep.type == :development
|
324
|
+
debug { " * #{dep.name} (#{dep.requirement})" }
|
325
|
+
dep.required_by.replace(requirement.required_by)
|
326
|
+
dep.required_by << requirement
|
327
|
+
reqs << dep
|
328
|
+
end
|
329
|
+
|
330
|
+
# We create a savepoint and mark it by the name of the requirement that caused
|
331
|
+
# the gem to be activated. If the activated gem ever conflicts, we are able to
|
332
|
+
# jump back to this point and try another version of the gem.
|
333
|
+
length = @stack.length
|
334
|
+
@stack << requirement.name
|
335
|
+
retval = catch(requirement.name) do
|
336
|
+
resolve(reqs, activated)
|
337
|
+
end
|
338
|
+
# Since we're doing a lot of throw / catches. A push does not necessarily match
|
339
|
+
# up to a pop. So, we simply slice the stack back to what it was before the catch
|
340
|
+
# block.
|
341
|
+
@stack.slice!(length..-1)
|
342
|
+
retval
|
343
|
+
end
|
344
|
+
|
345
|
+
def gems_size(dep)
|
346
|
+
@gems_size[dep] ||= search(dep).size
|
347
|
+
end
|
348
|
+
|
349
|
+
def search(dep)
|
350
|
+
if base = @base[dep.name] and base.any?
|
351
|
+
reqs = [dep.requirement.as_list, base.first.version.to_s].flatten.compact
|
352
|
+
d = Gem::Dependency.new(base.first.name, *reqs)
|
353
|
+
else
|
354
|
+
d = dep.dep
|
355
|
+
end
|
356
|
+
index = @source_requirements[d.name] || @index
|
357
|
+
results = index.search_for_all_platforms(d, @base[d.name])
|
358
|
+
|
359
|
+
if results.any?
|
360
|
+
version = results.first.version
|
361
|
+
nested = [[]]
|
362
|
+
results.each do |spec|
|
363
|
+
if spec.version != version
|
364
|
+
nested << []
|
365
|
+
version = spec.version
|
366
|
+
end
|
367
|
+
nested.last << spec
|
368
|
+
end
|
369
|
+
nested.map { |a| SpecGroup.new(a) }.select { |sg| sg.for?(dep.__platform) }
|
370
|
+
else
|
371
|
+
[]
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def clean_req(req)
|
376
|
+
if req.to_s.include?(">= 0")
|
377
|
+
req.to_s.gsub(/ \(.*?\)$/, '')
|
378
|
+
else
|
379
|
+
req.to_s.gsub(/\, (runtime|development)\)$/, ')')
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def version_conflict
|
384
|
+
VersionConflict.new(errors.keys, error_message)
|
385
|
+
end
|
386
|
+
|
387
|
+
# For a given conflicted requirement, print out what exactly went wrong
|
388
|
+
def gem_message(requirement)
|
389
|
+
m = ""
|
390
|
+
|
391
|
+
# A requirement that is required by itself is actually in the Gemfile, and does
|
392
|
+
# not "depend on" itself
|
393
|
+
if requirement.required_by.first && requirement.required_by.first.name != requirement.name
|
394
|
+
m << " #{clean_req(requirement.required_by.first)} depends on\n"
|
395
|
+
m << " #{clean_req(requirement)}\n"
|
396
|
+
else
|
397
|
+
m << " #{clean_req(requirement)}\n"
|
398
|
+
end
|
399
|
+
m << "\n"
|
400
|
+
end
|
401
|
+
|
402
|
+
def error_message
|
403
|
+
errors.inject("") do |o, (conflict, (origin, requirement))|
|
404
|
+
|
405
|
+
# origin is the SpecSet of specs from the Gemfile that is conflicted with
|
406
|
+
if origin
|
407
|
+
|
408
|
+
o << %{Bundler could not find compatible versions for gem "#{origin.name}":\n}
|
409
|
+
o << " In Gemfile:\n"
|
410
|
+
|
411
|
+
o << gem_message(requirement)
|
412
|
+
|
413
|
+
# If the origin is "bundler", the conflict is us
|
414
|
+
if origin.name == "bundler"
|
415
|
+
o << " Current Bundler version:\n"
|
416
|
+
other_bundler_required = !requirement.requirement.satisfied_by?(origin.version)
|
417
|
+
# If the origin is a LockfileParser, it does not respond_to :required_by
|
418
|
+
elsif !origin.respond_to?(:required_by) || !(origin.required_by.first)
|
419
|
+
o << " In snapshot (Gemfile.lock):\n"
|
420
|
+
end
|
421
|
+
|
422
|
+
o << gem_message(origin)
|
423
|
+
|
424
|
+
# If the bundle wants a newer bundler than the running bundler, explain
|
425
|
+
if origin.name == "bundler" && other_bundler_required
|
426
|
+
o << "This Gemfile requires a different version of Bundler.\n"
|
427
|
+
o << "Perhaps you need to update Bundler by running `gem install bundler`?"
|
428
|
+
end
|
429
|
+
|
430
|
+
# origin is nil if the required gem and version cannot be found in any of
|
431
|
+
# the specified sources
|
432
|
+
else
|
433
|
+
|
434
|
+
# if the gem cannot be found because of a version conflict between lockfile and gemfile,
|
435
|
+
# print a useful error that suggests running `bundle update`, which may fix things
|
436
|
+
#
|
437
|
+
# @base is a SpecSet of the gems in the lockfile
|
438
|
+
# conflict is the name of the gem that could not be found
|
439
|
+
if locked = @base[conflict].first
|
440
|
+
o << "Bundler could not find compatible versions for gem #{conflict.inspect}:\n"
|
441
|
+
o << " In snapshot (Gemfile.lock):\n"
|
442
|
+
o << " #{clean_req(locked)}\n\n"
|
443
|
+
|
444
|
+
o << " In Gemfile:\n"
|
445
|
+
o << gem_message(requirement)
|
446
|
+
o << "Running `bundle update` will rebuild your snapshot from scratch, using only\n"
|
447
|
+
o << "the gems in your Gemfile, which may resolve the conflict.\n"
|
448
|
+
|
449
|
+
# the rest of the time, the gem cannot be found because it does not exist in the known sources
|
450
|
+
else
|
451
|
+
if requirement.required_by.first
|
452
|
+
o << "Could not find gem '#{clean_req(requirement)}', which is required by "
|
453
|
+
o << "gem '#{clean_req(requirement.required_by.first)}', in any of the sources."
|
454
|
+
else
|
455
|
+
o << "Could not find gem '#{clean_req(requirement)} in any of the sources\n"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
end
|
460
|
+
o
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|