bundler 1.3.6 → 1.4.0.pre.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.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -3
  3. data/CHANGELOG.md +27 -14
  4. data/CONTRIBUTING.md +2 -2
  5. data/{CONTRIBUTE.md → DEVELOPMENT.md} +31 -12
  6. data/ISSUES.md +1 -1
  7. data/README.md +6 -4
  8. data/Rakefile +1 -15
  9. data/bin/bundle +5 -8
  10. data/bundler.gemspec +1 -1
  11. data/lib/bundler.rb +37 -21
  12. data/lib/bundler/cli.rb +33 -21
  13. data/lib/bundler/constants.rb +5 -0
  14. data/lib/bundler/current_ruby.rb +88 -0
  15. data/lib/bundler/definition.rb +35 -11
  16. data/lib/bundler/dependency.rb +7 -78
  17. data/lib/bundler/dsl.rb +1 -1
  18. data/lib/bundler/fetcher.rb +37 -24
  19. data/lib/bundler/gem_helper.rb +2 -2
  20. data/lib/bundler/gem_installer.rb +9 -0
  21. data/lib/bundler/installer.rb +76 -7
  22. data/lib/bundler/parallel_workers.rb +18 -0
  23. data/lib/bundler/parallel_workers/thread_worker.rb +27 -0
  24. data/lib/bundler/parallel_workers/unix_worker.rb +88 -0
  25. data/lib/bundler/parallel_workers/worker.rb +68 -0
  26. data/lib/bundler/resolver.rb +17 -11
  27. data/lib/bundler/rubygems_ext.rb +2 -2
  28. data/lib/bundler/rubygems_integration.rb +37 -25
  29. data/lib/bundler/runtime.rb +8 -1
  30. data/lib/bundler/safe_catch.rb +101 -0
  31. data/lib/bundler/shared_helpers.rb +27 -1
  32. data/lib/bundler/source/git.rb +2 -1
  33. data/lib/bundler/source/git/git_proxy.rb +3 -3
  34. data/lib/bundler/source/path.rb +3 -2
  35. data/lib/bundler/source/rubygems.rb +5 -17
  36. data/lib/bundler/spec_set.rb +16 -1
  37. data/lib/bundler/templates/newgem/newgem.gemspec.tt +1 -1
  38. data/lib/bundler/vendor/net/http/persistent.rb +136 -38
  39. data/lib/bundler/vendor/thor.rb +211 -188
  40. data/lib/bundler/vendor/thor/actions.rb +19 -19
  41. data/lib/bundler/vendor/thor/actions/create_link.rb +3 -0
  42. data/lib/bundler/vendor/thor/actions/directory.rb +30 -10
  43. data/lib/bundler/vendor/thor/actions/empty_directory.rb +3 -19
  44. data/lib/bundler/vendor/thor/actions/file_manipulation.rb +6 -3
  45. data/lib/bundler/vendor/thor/base.rb +101 -97
  46. data/lib/bundler/vendor/thor/{task.rb → command.rb} +17 -13
  47. data/lib/bundler/vendor/thor/core_ext/io_binary_read.rb +12 -0
  48. data/lib/bundler/vendor/thor/error.rb +8 -11
  49. data/lib/bundler/vendor/thor/group.rb +35 -38
  50. data/lib/bundler/vendor/thor/invocation.rb +28 -26
  51. data/lib/bundler/vendor/thor/parser/options.rb +21 -19
  52. data/lib/bundler/vendor/thor/rake_compat.rb +3 -2
  53. data/lib/bundler/vendor/thor/runner.rb +22 -21
  54. data/lib/bundler/vendor/thor/shell/basic.rb +44 -22
  55. data/lib/bundler/vendor/thor/shell/color.rb +13 -9
  56. data/lib/bundler/vendor/thor/shell/html.rb +13 -9
  57. data/lib/bundler/vendor/thor/util.rb +214 -210
  58. data/lib/bundler/vendor/thor/version.rb +1 -1
  59. data/lib/bundler/version.rb +1 -1
  60. data/man/bundle-install.ronn +5 -1
  61. data/man/gemfile.5.ronn +10 -2
  62. data/spec/bundler/dsl_spec.rb +3 -3
  63. data/spec/bundler/gem_helper_spec.rb +14 -17
  64. data/spec/bundler/safe_catch_spec.rb +37 -0
  65. data/spec/install/gems/dependency_api_spec.rb +1 -36
  66. data/spec/install/gems/packed_spec.rb +4 -2
  67. data/spec/install/gems/resolving_spec.rb +37 -0
  68. data/spec/install/gems/simple_case_spec.rb +18 -16
  69. data/spec/install/git_spec.rb +1 -1
  70. data/spec/other/binstubs_spec.rb +24 -13
  71. data/spec/other/exec_spec.rb +24 -2
  72. data/spec/other/help_spec.rb +6 -6
  73. data/spec/other/outdated_spec.rb +3 -3
  74. data/spec/quality_spec.rb +3 -2
  75. data/spec/realworld/dependency_api_spec.rb +1 -1
  76. data/spec/realworld/edgecases_spec.rb +3 -3
  77. data/spec/realworld/parallel_install_spec.rb +19 -0
  78. data/spec/resolver/basic_spec.rb +11 -0
  79. data/spec/runtime/require_spec.rb +9 -0
  80. data/spec/runtime/setup_spec.rb +2 -3
  81. data/spec/spec_helper.rb +0 -1
  82. data/spec/support/builders.rb +2 -4
  83. data/spec/support/helpers.rb +4 -8
  84. data/spec/support/indexes.rb +18 -0
  85. data/spec/support/streams.rb +13 -0
  86. metadata +19 -11
  87. data/lib/bundler/vendor/thor/core_ext/dir_escape.rb +0 -0
  88. data/lib/bundler/vendor/thor/core_ext/file_binary_read.rb +0 -9
  89. data/spec/support/artifice/endpoint_host_redirect.rb +0 -15
  90. data/spec/support/permissions.rb +0 -11
@@ -0,0 +1,5 @@
1
+ module Bundler
2
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)!
3
+ FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
4
+ NULL = WINDOWS ? "NUL" : "/dev/null"
5
+ end
@@ -0,0 +1,88 @@
1
+ module Bundler
2
+ # Returns current version of Ruby
3
+ #
4
+ # @return [CurrentRuby] Current version of Ruby
5
+ def self.current_ruby
6
+ @current_ruby ||= CurrentRuby.new
7
+ end
8
+
9
+ class CurrentRuby
10
+ def on_18?
11
+ RUBY_VERSION =~ /^1\.8/
12
+ end
13
+
14
+ def on_19?
15
+ RUBY_VERSION =~ /^1\.9/
16
+ end
17
+
18
+ def on_20?
19
+ RUBY_VERSION =~ /^2\.0/
20
+ end
21
+
22
+ def ruby?
23
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev")
24
+ end
25
+
26
+ def ruby_18?
27
+ ruby? && on_18?
28
+ end
29
+
30
+ def ruby_19?
31
+ ruby? && on_19?
32
+ end
33
+
34
+ def ruby_20?
35
+ ruby? && on_20?
36
+ end
37
+
38
+ def mri?
39
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
40
+ end
41
+
42
+ def mri_18?
43
+ mri? && on_18?
44
+ end
45
+
46
+ def mri_19?
47
+ mri? && on_19?
48
+ end
49
+
50
+
51
+ def mri_20?
52
+ mri? && on_20?
53
+ end
54
+
55
+ def rbx?
56
+ ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
57
+ end
58
+
59
+ def jruby?
60
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
61
+ end
62
+
63
+ def maglev?
64
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
65
+ end
66
+
67
+ def mswin?
68
+ Bundler::WINDOWS
69
+ end
70
+
71
+ def mingw?
72
+ Bundler::WINDOWS && Gem::Platform.local.os == "mingw32"
73
+ end
74
+
75
+ def mingw_18?
76
+ mingw? && on_18?
77
+ end
78
+
79
+ def mingw_19?
80
+ mingw? && on_19?
81
+ end
82
+
83
+ def mingw_20?
84
+ mingw? && on_20?
85
+ end
86
+
87
+ end
88
+ end
@@ -8,6 +8,13 @@ module Bundler
8
8
  attr_reader :dependencies, :platforms, :sources, :ruby_version,
9
9
  :locked_deps
10
10
 
11
+ # Given a gemfile and lockfile creates a Bundler definition
12
+ #
13
+ # @param gemfile [Pathname] Path to Gemfile
14
+ # @param lockfile [Pathname,nil] Path to Gemfile.lock
15
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
16
+ # to be updated or true if all gems should be updated
17
+ # @return [Bundler::Definition]
11
18
  def self.build(gemfile, lockfile, unlock)
12
19
  unlock ||= {}
13
20
  gemfile = Pathname.new(gemfile).expand_path
@@ -19,18 +26,24 @@ module Bundler
19
26
  Dsl.evaluate(gemfile, lockfile, unlock)
20
27
  end
21
28
 
22
- =begin
23
- How does the new system work?
24
- ===
25
- * Load information from Gemfile and Lockfile
26
- * Invalidate stale locked specs
27
- * All specs from stale source are stale
28
- * All specs that are reachable only through a stale
29
- dependency are stale.
30
- * If all fresh dependencies are satisfied by the locked
31
- specs, then we can try to resolve locally.
32
- =end
33
29
 
30
+ #
31
+ # How does the new system work?
32
+ #
33
+ # * Load information from Gemfile and Lockfile
34
+ # * Invalidate stale locked specs
35
+ # * All specs from stale source are stale
36
+ # * All specs that are reachable only through a stale
37
+ # dependency are stale.
38
+ # * If all fresh dependencies are satisfied by the locked
39
+ # specs, then we can try to resolve locally.
40
+ #
41
+ # @param lockfile [Pathname] Path to Gemfile.lock
42
+ # @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile
43
+ # @param sources [Array(Bundler::Source::Rubygems)]
44
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
45
+ # to be updated or true if all gems should be updated
46
+ # @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
34
47
  def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil)
35
48
  @unlocking = unlock == true || !unlock.empty?
36
49
 
@@ -109,6 +122,12 @@ module Bundler
109
122
  specs
110
123
  end
111
124
 
125
+ # For given dependency list returns a SpecSet with Gemspec of all the required
126
+ # dependencies.
127
+ # 1. The method first resolves the dependencies specified in Gemfile
128
+ # 2. After that it tries and fetches gemspec of resolved dependencies
129
+ #
130
+ # @return [Bundler::SpecSet]
112
131
  def specs
113
132
  @specs ||= begin
114
133
  specs = resolve.materialize(requested_dependencies)
@@ -159,6 +178,11 @@ module Bundler
159
178
  specs.for(expand_dependencies(deps))
160
179
  end
161
180
 
181
+ # Resolve all the dependencies specified in Gemfile. It ensures that
182
+ # dependencies that have been already resolved via locked file and are fresh
183
+ # are reused when resolving dependencies
184
+ #
185
+ # @return [SpecSet] resolved dependencies
162
186
  def resolve
163
187
  @resolve ||= begin
164
188
  if Bundler.settings[:frozen] || (!@unlocking && nothing_changed?)
@@ -70,7 +70,9 @@ module Bundler
70
70
 
71
71
  def current_platform?
72
72
  return true if @platforms.empty?
73
- @platforms.any? { |p| send("#{p}?") }
73
+ @platforms.any? { |p|
74
+ Bundler.current_ruby.send("#{p}?")
75
+ }
74
76
  end
75
77
 
76
78
  def to_lock
@@ -79,84 +81,11 @@ module Bundler
79
81
  out << "\n"
80
82
  end
81
83
 
82
- private
83
84
 
84
- def on_18?
85
- RUBY_VERSION =~ /^1\.8/
85
+ def specific?
86
+ super
87
+ rescue NoMethodError
88
+ requirement != ">= 0"
86
89
  end
87
-
88
- def on_19?
89
- RUBY_VERSION =~ /^1\.9/
90
- end
91
-
92
- def on_20?
93
- RUBY_VERSION =~ /^2\.0/
94
- end
95
-
96
- def ruby?
97
- !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev")
98
- end
99
-
100
- def ruby_18?
101
- ruby? && on_18?
102
- end
103
-
104
- def ruby_19?
105
- ruby? && on_19?
106
- end
107
-
108
- def ruby_20?
109
- ruby? && on_20?
110
- end
111
-
112
- def mri?
113
- !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
114
- end
115
-
116
- def mri_18?
117
- mri? && on_18?
118
- end
119
-
120
- def mri_19?
121
- mri? && on_19?
122
- end
123
-
124
-
125
- def mri_20?
126
- mri? && on_20?
127
- end
128
-
129
- def rbx?
130
- ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
131
- end
132
-
133
- def jruby?
134
- defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
135
- end
136
-
137
- def maglev?
138
- defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
139
- end
140
-
141
- def mswin?
142
- Bundler::WINDOWS
143
- end
144
-
145
- def mingw?
146
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32"
147
- end
148
-
149
- def mingw_18?
150
- mingw? && on_18?
151
- end
152
-
153
- def mingw_19?
154
- mingw? && on_19?
155
- end
156
-
157
- def mingw_20?
158
- mingw? && on_20?
159
- end
160
-
161
90
  end
162
91
  end
@@ -230,7 +230,7 @@ module Bundler
230
230
 
231
231
  if github = opts.delete("github")
232
232
  github = "#{github}/#{github}" unless github.include?("/")
233
- opts["git"] = "git://github.com/#{github}.git"
233
+ opts["git"] = "https://github.com/#{github}.git"
234
234
  end
235
235
 
236
236
  if gist = opts.delete("gist")
@@ -4,11 +4,6 @@ module Bundler
4
4
 
5
5
  # Handles all the fetching with the rubygems server
6
6
  class Fetcher
7
- # How many redirects to allew in one request
8
- REDIRECT_LIMIT = 5
9
- # how long to wait for each gemcutter API call
10
- API_TIMEOUT = 10
11
-
12
7
  # This error is raised if the API returns a 413 (only printed in verbose)
13
8
  class FallbackError < HTTPError; end
14
9
  # This is the error raised if OpenSSL fails the cert verification
@@ -33,7 +28,7 @@ module Bundler
33
28
  end
34
29
 
35
30
  class << self
36
- attr_accessor :disable_endpoint
31
+ attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
37
32
 
38
33
  @@spec_fetch_map ||= {}
39
34
 
@@ -65,6 +60,13 @@ module Bundler
65
60
  end
66
61
 
67
62
  def initialize(remote_uri)
63
+ # How many redirects to allew in one request
64
+ @redirect_limit = 5
65
+ # How long to wait for each gemcutter API call
66
+ @api_timeout = 10
67
+ # How many retries for the gemcutter API call
68
+ @max_retries = 3
69
+
68
70
  @remote_uri = remote_uri
69
71
  @public_uri = remote_uri.dup
70
72
  @public_uri.user, @public_uri.password = nil, nil # don't print these
@@ -77,7 +79,7 @@ module Bundler
77
79
  raise SSLError if @remote_uri.scheme == "https"
78
80
  @connection = Net::HTTP.new(@remote_uri.host, @remote_uri.port)
79
81
  end
80
- @connection.read_timeout = API_TIMEOUT
82
+ @connection.read_timeout = @api_timeout
81
83
 
82
84
  Socket.do_not_reverse_lookup = true
83
85
  end
@@ -85,17 +87,28 @@ module Bundler
85
87
  # fetch a gem specification
86
88
  def fetch_spec(spec)
87
89
  spec = spec - [nil, 'ruby', '']
88
- spec_file_name = "#{spec.join '-'}.gemspec.rz"
89
-
90
- uri = URI.parse("#{@remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}")
90
+ spec_file_name = "#{spec.join '-'}.gemspec"
91
91
 
92
- spec_rz = (uri.scheme == "file") ? Gem.read_binary(uri.path) : fetch(uri)
93
- Bundler.load_marshal Gem.inflate(spec_rz)
92
+ uri = URI.parse("#{@remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
93
+ if uri.scheme == 'file'
94
+ Bundler.load_marshal Gem.inflate(Gem.read_binary(uri.path))
95
+ elsif cached_spec_path = gemspec_cached_path(spec_file_name)
96
+ Bundler.load_gemspec(cached_spec_path)
97
+ else
98
+ Bundler.load_marshal Gem.inflate(fetch(uri))
99
+ end
94
100
  rescue MarshalError => e
95
101
  raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
96
102
  "Your network or your gem server is probably having issues right now."
97
103
  end
98
104
 
105
+ # cached gem specification path, if one exists
106
+ def gemspec_cached_path spec_file_name
107
+ paths = Bundler.rubygems.spec_cache_dirs.map { |dir| File.join(dir, spec_file_name) }
108
+ paths = paths.select {|path| File.file? path }
109
+ paths.first
110
+ end
111
+
99
112
  # return the specs in the bundler format as an index
100
113
  def specs(gem_names, source)
101
114
  index = Index.new
@@ -157,14 +170,19 @@ module Bundler
157
170
  # 2. Marshal blob doesn't load properly
158
171
  # 3. One of the YAML gemspecs has the Syck::DefaultKey problem
159
172
  rescue HTTPError, MarshalError, GemspecError => e
160
- @use_api = false
161
-
162
173
  # new line now that the dots are over
163
174
  Bundler.ui.info "" unless Bundler.ui.debug?
164
175
 
165
176
  Bundler.ui.debug "Error during API request. #{e.class}: #{e.message}"
166
177
  Bundler.ui.debug e.backtrace.join(" ")
167
178
 
179
+ @current_retries ||= 0
180
+ if @current_retries < @max_retries
181
+ @current_retries += 1
182
+ retry
183
+ end
184
+
185
+ @use_api = false
168
186
  return nil
169
187
  end
170
188
 
@@ -194,21 +212,16 @@ module Bundler
194
212
  HTTP_ERRORS << Net::HTTP::Persistent::Error if defined?(Net::HTTP::Persistent)
195
213
 
196
214
  def fetch(uri, counter = 0)
197
- raise HTTPError, "Too many redirects" if counter >= REDIRECT_LIMIT
215
+ raise HTTPError, "Too many redirects" if counter >= @redirect_limit
198
216
 
199
217
  begin
200
218
  Bundler.ui.debug "Fetching from: #{uri}"
219
+ req = Net::HTTP::Get.new uri.request_uri
220
+ req.basic_auth(uri.user, uri.password) if uri.user && uri.password
201
221
  if defined?(Net::HTTP::Persistent)
202
- response = @connection.request(uri)
222
+ response = @connection.request(uri, req)
203
223
  else
204
- req = Net::HTTP::Get.new uri.request_uri
205
- req.basic_auth(uri.user, uri.password) if uri.user && uri.password
206
- if uri.host == @connection.address && uri.port == @connection.port
207
- connection = @connection
208
- else
209
- connection = Net::HTTP.new(uri.host, uri.port)
210
- end
211
- response = connection.request(req)
224
+ response = @connection.request(req)
212
225
  end
213
226
  rescue OpenSSL::SSL::SSLError
214
227
  raise CertificateFailureError.new(@public_uri)
@@ -24,7 +24,7 @@ module Bundler
24
24
 
25
25
  def initialize(base = nil, name = nil)
26
26
  Bundler.ui = UI::Shell.new
27
- @base = (base ||= Dir.pwd)
27
+ @base = (base ||= SharedHelpers.pwd)
28
28
  gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "{,*}.gemspec")]
29
29
  raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1
30
30
  @spec_path = gemspecs.first
@@ -153,7 +153,7 @@ module Bundler
153
153
  cmd << " 2>&1"
154
154
  outbuf = ''
155
155
  Bundler.ui.debug(cmd)
156
- Dir.chdir(base) {
156
+ SharedHelpers.chdir(base) {
157
157
  outbuf = `#{cmd}`
158
158
  if $? == 0
159
159
  block.call(outbuf) if block
@@ -5,5 +5,14 @@ module Bundler
5
5
  def check_executable_overwrite(filename)
6
6
  # Bundler needs to install gems regardless of binstub overwriting
7
7
  end
8
+
9
+ if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby?
10
+ def build_extensions
11
+ # Gain the lock because rubygems use Dir.chdir
12
+ SharedHelpers.chdir('.') do
13
+ super
14
+ end
15
+ end
16
+ end
8
17
  end
9
18
  end
@@ -1,5 +1,6 @@
1
1
  require 'erb'
2
2
  require 'rubygems/dependency_installer'
3
+ require 'bundler/parallel_workers'
3
4
 
4
5
  module Bundler
5
6
  class Installer < Environment
@@ -87,8 +88,14 @@ module Bundler
87
88
  # as dependencies might actually affect the installation of
88
89
  # the gem.
89
90
  Installer.post_install_messages = {}
90
- specs.each do |spec|
91
- install_gem_from_spec(spec, options[:standalone])
91
+
92
+ size = options[:jobs] || 1
93
+ size = [size, 1].max
94
+
95
+ if size > 1 && can_install_parallely?
96
+ install_in_parallel size, options[:standalone]
97
+ else
98
+ install_sequentially options[:standalone]
92
99
  end
93
100
 
94
101
  lock
@@ -102,13 +109,12 @@ module Bundler
102
109
 
103
110
  # Fetch the build settings, if there are any
104
111
  settings = Bundler.settings["build.#{spec.name}"]
112
+ message = nil
105
113
  Bundler.rubygems.with_build_args [settings] do
106
- spec.source.install(spec)
107
- Bundler.ui.debug "from #{spec.loaded_from} "
114
+ message = spec.source.install(spec)
115
+ Bundler.ui.debug " #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
108
116
  end
109
117
 
110
- # newline comes after installing, some gems say "with native extensions"
111
- Bundler.ui.info ""
112
118
  if Bundler.settings[:bin] && standalone
113
119
  generate_standalone_bundler_executable_stubs(spec)
114
120
  elsif Bundler.settings[:bin]
@@ -116,6 +122,7 @@ module Bundler
116
122
  end
117
123
 
118
124
  FileUtils.rm_rf(Bundler.tmp)
125
+ message
119
126
  rescue Exception => e
120
127
  # install hook failed
121
128
  raise e if e.is_a?(Bundler::InstallHookError) || e.is_a?(Bundler::SecurityError)
@@ -163,7 +170,7 @@ module Bundler
163
170
  next
164
171
  end
165
172
 
166
- File.open(binstub_path, 'w', 0777 & ~File.umask) do |f|
173
+ File.open(binstub_path, 'w', 0755) do |f|
167
174
  f.puts ERB.new(template, nil, '-').result(binding)
168
175
  end
169
176
  end
@@ -184,6 +191,16 @@ module Bundler
184
191
  end
185
192
 
186
193
  private
194
+ def can_install_parallely?
195
+ if Bundler.current_ruby.mri? || Bundler.rubygems.provides?(">= 2.1.0.rc")
196
+ true
197
+ else
198
+ Bundler.ui.warn "Rubygems #{Gem::VERSION} is not threadsafe, so your "\
199
+ "gems must be installed one at a time. Upgrade to Rubygems 2.1 or "\
200
+ "higher to enable parallel gem installation."
201
+ false
202
+ end
203
+ end
187
204
 
188
205
  def generate_standalone_bundler_executable_stubs(spec)
189
206
  # double-assignment to avoid warnings about variables that will be used by ERB
@@ -236,5 +253,57 @@ module Bundler
236
253
  end
237
254
  end
238
255
  end
256
+
257
+ def install_sequentially(standalone)
258
+ specs.each do |spec|
259
+ message = install_gem_from_spec spec, standalone
260
+ if message
261
+ Installer.post_install_messages[spec.name] = message
262
+ end
263
+ end
264
+ end
265
+
266
+ def install_in_parallel(size, standalone)
267
+ name2spec = {}
268
+ remains = {}
269
+ enqueued = {}
270
+ specs.each do |spec|
271
+ name2spec[spec.name] = spec
272
+ remains[spec.name] = true
273
+ end
274
+
275
+ worker_pool = ParallelWorkers.worker_pool size, lambda { |name|
276
+ spec = name2spec[name]
277
+ message = install_gem_from_spec spec, standalone
278
+ { :name => spec.name, :post_install => message }
279
+ }
280
+ specs.each do |spec|
281
+ deps = spec.dependencies.select { |dep| dep.type != :development }
282
+ if deps.empty?
283
+ worker_pool.enq spec.name
284
+ enqueued[spec.name] = true
285
+ end
286
+ end
287
+
288
+ until remains.empty?
289
+ message = worker_pool.deq
290
+ remains.delete message[:name]
291
+ if message[:post_install]
292
+ Installer.post_install_messages[message[:name]] = message[:post_install]
293
+ end
294
+ remains.keys.each do |name|
295
+ next if enqueued[name]
296
+ spec = name2spec[name]
297
+ deps = spec.dependencies.select { |dep| remains[dep.name] and dep.type != :development }
298
+ if deps.empty?
299
+ worker_pool.enq name
300
+ enqueued[name] = true
301
+ end
302
+ end
303
+ end
304
+ message
305
+ ensure
306
+ worker_pool && worker_pool.stop
307
+ end
239
308
  end
240
309
  end