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,18 @@
1
+ require 'thread'
2
+
3
+ require "bundler/parallel_workers/worker"
4
+
5
+ module Bundler
6
+ module ParallelWorkers
7
+ autoload :UnixWorker, "bundler/parallel_workers/unix_worker"
8
+ autoload :ThreadWorker, "bundler/parallel_workers/thread_worker"
9
+
10
+ def self.worker_pool(size, job)
11
+ if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby?
12
+ ThreadWorker.new(size, job)
13
+ else
14
+ UnixWorker.new(size, job)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module Bundler
2
+ module ParallelWorkers
3
+ class ThreadWorker < Worker
4
+
5
+ private
6
+
7
+ # On platforms where fork is not available
8
+ # use Threads for parallely downloading gems
9
+ #
10
+ # @param size [Integer] Size of thread worker pool
11
+ # @param func [Proc] Job to be run inside thread worker pool
12
+ def prepare_workers(size, func)
13
+ @threads = size.times.map do |i|
14
+ Thread.start do
15
+ Thread.current.abort_on_exception = true
16
+ loop do
17
+ obj = @request_queue.deq
18
+ break if obj.equal? POISON
19
+ @response_queue.enq func.call(obj)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,88 @@
1
+ module Bundler
2
+ module ParallelWorkers
3
+ # UnixWorker is used only on platforms where fork is available. The way
4
+ # this code works is, it forks a preconfigured number of workers and then
5
+ # It starts preconfigured number of threads that write to the connected pipe.
6
+ class UnixWorker < Worker
7
+
8
+ class JobHandler < Struct.new(:pid, :io_r, :io_w)
9
+ def work(obj)
10
+ Marshal.dump obj, io_w
11
+ Marshal.load io_r
12
+ rescue IOError
13
+ nil
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Start forked workers for downloading gems. This version of worker
20
+ # is only used on platforms where fork is available.
21
+ #
22
+ # @param size [Integer] Size of worker pool
23
+ # @param func [Proc] Job that should be executed in the worker
24
+ def prepare_workers(size, func)
25
+ @workers = size.times.map do
26
+ child_read, parent_write = IO.pipe
27
+ parent_read, child_write = IO.pipe
28
+
29
+ pid = Process.fork do
30
+ begin
31
+ parent_read.close
32
+ parent_write.close
33
+
34
+ while !child_read.eof?
35
+ obj = Marshal.load child_read
36
+ Marshal.dump func.call(obj), child_write
37
+ end
38
+ rescue Exception => e
39
+ begin
40
+ Marshal.dump WrappedException.new(e), child_write
41
+ rescue Errno::EPIPE
42
+ nil
43
+ end
44
+ ensure
45
+ child_read.close
46
+ child_write.close
47
+ end
48
+ end
49
+
50
+ child_read.close
51
+ child_write.close
52
+ JobHandler.new pid, parent_read, parent_write
53
+ end
54
+ end
55
+
56
+ # Start the threads whose job is basically to wait for incoming messages
57
+ # on request queue and write that message to the connected pipe. Also retrieve
58
+ # messages from child worker via connected pipe and write the message to response queue
59
+ #
60
+ # @param size [Integer] Number of threads to be started
61
+ def prepare_threads(size)
62
+ @threads = size.times.map do |i|
63
+ Thread.start do
64
+ worker = @workers[i]
65
+ Thread.current.abort_on_exception = true
66
+ loop do
67
+ obj = @request_queue.deq
68
+ break if obj.equal? POISON
69
+ @response_queue.enq worker.work(obj)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ # Kill the forked workers by sending SIGINT to them
76
+ def stop_workers
77
+ @workers.each do |worker|
78
+ worker.io_r.close
79
+ worker.io_w.close
80
+ Process.kill :INT, worker.pid
81
+ end
82
+ @workers.each do |worker|
83
+ Process.waitpid worker.pid
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,68 @@
1
+ module Bundler
2
+ module ParallelWorkers
3
+ class Worker
4
+ POISON = Object.new
5
+
6
+ class WrappedException < StandardError
7
+ attr_reader :exception
8
+ def initialize(exn)
9
+ @exception = exn
10
+ end
11
+ end
12
+
13
+ # Creates a worker pool of specified size
14
+ #
15
+ # @param size [Integer] Size of pool
16
+ # @param func [Proc] job to run in inside the worker pool
17
+ def initialize(size, func)
18
+ @request_queue = Queue.new
19
+ @response_queue = Queue.new
20
+ prepare_workers size, func
21
+ prepare_threads size
22
+ end
23
+
24
+ # Enque a request to be executed in the worker pool
25
+ #
26
+ # @param obj [String] mostly it is name of spec that should be downloaded
27
+ def enq(obj)
28
+ @request_queue.enq obj
29
+ end
30
+
31
+ # Retrieves results of job function being executed in worker pool
32
+ def deq
33
+ result = @response_queue.deq
34
+ if WrappedException === result
35
+ raise result.exception
36
+ end
37
+ result
38
+ end
39
+
40
+ # Stop the forked workers and started threads
41
+ def stop
42
+ stop_workers
43
+ stop_threads
44
+ end
45
+
46
+ private
47
+ # Stop the worker threads by sending a poison object down the request queue
48
+ # so as worker threads after retrieving it, shut themselves down
49
+ def stop_threads
50
+ @threads.each do
51
+ @request_queue.enq POISON
52
+ end
53
+ @threads.each do |thread|
54
+ thread.join
55
+ end
56
+ end
57
+
58
+ # To be overridden by child classes
59
+ def prepare_threads(size)
60
+ end
61
+
62
+ # To be overridden by child classes
63
+ def stop_workers
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'bundler/safe_catch'
2
3
  # This is the latest iteration of the gem dependency resolving algorithm. As of now,
3
4
  # it can resolve (as a success or failure) any set of gem dependencies we throw at it
4
5
  # in a reasonable amount of time. The most iterations I've seen it take is about 150.
@@ -21,6 +22,9 @@ end
21
22
 
22
23
  module Bundler
23
24
  class Resolver
25
+ include SafeCatch
26
+ extend SafeCatch
27
+
24
28
  ALL = Bundler::Dependency::PLATFORM_MAP.values.uniq.freeze
25
29
 
26
30
  class SpecGroup < Array
@@ -125,7 +129,7 @@ module Bundler
125
129
  Bundler.ui.info "Resolving dependencies...", false
126
130
  base = SpecSet.new(base) unless base.is_a?(SpecSet)
127
131
  resolver = new(index, source_requirements, base)
128
- result = catch(:success) do
132
+ result = safe_catch(:success) do
129
133
  resolver.start(requirements)
130
134
  raise resolver.version_conflict
131
135
  nil
@@ -168,10 +172,10 @@ module Bundler
168
172
  resolve(reqs, activated)
169
173
  end
170
174
 
171
- def resolve(reqs, activated)
175
+ def resolve(reqs, activated, depth = 0)
172
176
  # If the requirements are empty, then we are in a success state. Aka, all
173
177
  # gem dependencies have been resolved.
174
- throw :success, successify(activated) if reqs.empty?
178
+ safe_throw :success, successify(activated) if reqs.empty?
175
179
 
176
180
  indicate_progress
177
181
 
@@ -197,6 +201,8 @@ module Bundler
197
201
  # Pull off the first requirement so that we can resolve it
198
202
  current = reqs.shift
199
203
 
204
+ $stderr.puts "#{' ' * depth}#{current}" if ENV['DEBUG_RESOLVER_TREE']
205
+
200
206
  debug { "Attempting:\n #{current}"}
201
207
 
202
208
  # Check if the gem has already been activated, if it has, we will make sure
@@ -228,7 +234,7 @@ module Bundler
228
234
  @gems_size[dep] ||= gems_size(dep)
229
235
  end
230
236
 
231
- resolve(reqs, activated)
237
+ resolve(reqs, activated, depth + 1)
232
238
  else
233
239
  debug { " * [FAIL] Already activated" }
234
240
  @errors[existing.name] = [existing, current]
@@ -248,7 +254,7 @@ module Bundler
248
254
  if parent && parent.name != 'bundler'
249
255
  debug { " -> Jumping to: #{parent.name}" }
250
256
  required_by = existing.respond_to?(:required_by) && existing.required_by.last
251
- throw parent.name, required_by && required_by.name
257
+ safe_throw parent.name, required_by && required_by.name
252
258
  else
253
259
  # The original set of dependencies conflict with the base set of specs
254
260
  # passed to the resolver. This is by definition an impossible resolve.
@@ -301,7 +307,7 @@ module Bundler
301
307
  end
302
308
 
303
309
  matching_versions.reverse_each do |spec_group|
304
- conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup)
310
+ conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup, depth)
305
311
  conflicts << conflict if conflict
306
312
  end
307
313
 
@@ -315,7 +321,7 @@ module Bundler
315
321
  # Choose the closest pivot in the stack that will affect the conflict
316
322
  errorpivot = (@stack & [req_name, current.required_by.last.name]).last
317
323
  debug { " -> Jumping to: #{errorpivot}" }
318
- throw errorpivot, req_name
324
+ safe_throw errorpivot, req_name
319
325
  end
320
326
  end
321
327
  end
@@ -330,14 +336,14 @@ module Bundler
330
336
  @stack.reverse_each do |savepoint|
331
337
  if conflicts.include?(savepoint)
332
338
  debug { " -> Jumping to: #{savepoint}" }
333
- throw savepoint
339
+ safe_throw savepoint
334
340
  end
335
341
  end
336
342
  end
337
343
  end
338
344
  end
339
345
 
340
- def resolve_requirement(spec_group, requirement, reqs, activated)
346
+ def resolve_requirement(spec_group, requirement, reqs, activated, depth)
341
347
  # We are going to try activating the spec. We need to keep track of stack of
342
348
  # requirements that got us to the point of activating this gem.
343
349
  spec_group.required_by.replace requirement.required_by
@@ -366,9 +372,9 @@ module Bundler
366
372
  # jump back to this point and try another version of the gem.
367
373
  length = @stack.length
368
374
  @stack << requirement.name
369
- retval = catch(requirement.name) do
375
+ retval = safe_catch(requirement.name) do
370
376
  # try to resolve the next option
371
- resolve(reqs, activated)
377
+ resolve(reqs, activated, depth)
372
378
  end
373
379
 
374
380
  # clear the search cache since the catch means we couldn't meet the
@@ -51,8 +51,8 @@ module Gem
51
51
  end
52
52
 
53
53
  def git_version
54
- if loaded_from && File.exist?(File.join(full_gem_path, ".git"))
55
- sha = Dir.chdir(full_gem_path){ `git rev-parse HEAD`.strip }
54
+ if @loaded_from && File.exist?(File.join(full_gem_path, ".git"))
55
+ sha = Bundler::SharedHelpers.chdir(full_gem_path){ `git rev-parse HEAD`.strip }
56
56
  " #{sha[0..6]}"
57
57
  end
58
58
  end
@@ -6,6 +6,22 @@ require 'rubygems/config_file'
6
6
  module Bundler
7
7
  class RubygemsIntegration
8
8
 
9
+ def self.version
10
+ @version ||= Gem::Version.new(Gem::VERSION)
11
+ end
12
+
13
+ def self.provides?(req_str)
14
+ Gem::Requirement.new(req_str).satisfied_by?(version)
15
+ end
16
+
17
+ def version
18
+ self.class.version
19
+ end
20
+
21
+ def provides?(req_str)
22
+ self.class.provides?(req_str)
23
+ end
24
+
9
25
  def build_args
10
26
  Gem::Command.build_args
11
27
  end
@@ -79,6 +95,14 @@ module Bundler
79
95
  Gem.path
80
96
  end
81
97
 
98
+ def spec_cache_dirs
99
+ @spec_cache_dirs ||= begin
100
+ dirs = gem_path.map {|dir| File.join(dir, 'specifications')}
101
+ dirs << Gem.spec_cache_dir if Gem.respond_to?(:spec_cache_dir) # Not in Rubygems 2.0.3 or earlier
102
+ dirs.uniq.select {|dir| File.directory? dir}
103
+ end
104
+ end
105
+
82
106
  def marshal_spec_dir
83
107
  Gem::MARSHAL_SPEC_DIR
84
108
  end
@@ -153,13 +177,13 @@ module Bundler
153
177
  end
154
178
  end
155
179
 
156
- def build(spec, skip_validation = false)
180
+ def build(spec)
157
181
  require 'rubygems/builder'
158
182
  Gem::Builder.new(spec).build
159
183
  end
160
184
 
161
185
  def build_gem(gem_dir, spec)
162
- Dir.chdir(gem_dir) { build(spec) }
186
+ SharedHelpers.chdir(gem_dir) { build(spec) }
163
187
  end
164
188
 
165
189
  def download_gem(spec, uri, path)
@@ -170,7 +194,7 @@ module Bundler
170
194
  @security_policies ||= begin
171
195
  require 'rubygems/security'
172
196
  Gem::Security::Policies
173
- rescue LoadError, NameError
197
+ rescue LoadError
174
198
  {}
175
199
  end
176
200
  end
@@ -398,7 +422,7 @@ module Bundler
398
422
  end
399
423
  end
400
424
 
401
- # Rubygems 1.8.5-1.8.19
425
+ # Rubygems ~> 1.8.5
402
426
  class Modern < RubygemsIntegration
403
427
  def stub_rubygems(specs)
404
428
  Gem::Specification.all = specs
@@ -431,24 +455,14 @@ module Bundler
431
455
  end
432
456
  end
433
457
 
434
- # Rubygems 1.8.20+
435
- class MoreModern < Modern
436
- # Rubygems 1.8.20 and adds the skip_validation parameter, so that's
437
- # when we start passing it through.
438
- def build(spec, skip_validation = false)
439
- require 'rubygems/builder'
440
- Gem::Builder.new(spec).build(skip_validation)
441
- end
442
- end
443
-
444
458
  # Rubygems 2.0
445
459
  class Future < RubygemsIntegration
446
460
  def stub_rubygems(specs)
447
461
  Gem::Specification.all = specs
448
462
 
449
- Gem.post_reset do
463
+ Gem.post_reset {
450
464
  Gem::Specification.all = specs
451
- end
465
+ }
452
466
  end
453
467
 
454
468
  def all_specs
@@ -491,26 +505,24 @@ module Bundler
491
505
  return p
492
506
  end
493
507
 
494
- def build(spec, skip_validation = false)
508
+ def build(spec)
495
509
  require 'rubygems/package'
496
- Gem::Package.build(spec, skip_validation)
510
+ Gem::Package.build(spec)
497
511
  end
498
512
 
499
513
  end
500
514
 
501
515
  end
502
516
 
503
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.99.99')
517
+ if RubygemsIntegration.provides?(">= 1.99.99")
504
518
  @rubygems = RubygemsIntegration::Future.new
505
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.20')
506
- @rubygems = RubygemsIntegration::MoreModern.new
507
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.5')
519
+ elsif RubygemsIntegration.provides?('>= 1.8.5')
508
520
  @rubygems = RubygemsIntegration::Modern.new
509
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
521
+ elsif RubygemsIntegration.provides?('>= 1.8.0')
510
522
  @rubygems = RubygemsIntegration::AlmostModern.new
511
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.7.0')
523
+ elsif RubygemsIntegration.provides?('>= 1.7.0')
512
524
  @rubygems = RubygemsIntegration::Transitional.new
513
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.4.0')
525
+ elsif RubygemsIntegration.provides?('>= 1.4.0')
514
526
  @rubygems = RubygemsIntegration::Legacy.new
515
527
  else # Rubygems 1.3.6 and 1.3.7
516
528
  @rubygems = RubygemsIntegration::Ancient.new