bundler 1.13.0.rc.1 → 1.13.0.rc.2

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/.codeclimate.yml +1 -0
  3. data/.rubocop.yml +8 -0
  4. data/.rubocop_todo.yml +21 -21
  5. data/.travis.yml +5 -1
  6. data/CHANGELOG.md +33 -1
  7. data/DEVELOPMENT.md +1 -1
  8. data/Rakefile +21 -12
  9. data/bin/rake +1 -1
  10. data/bin/rspec +1 -1
  11. data/bin/rubocop +2 -2
  12. data/bundler.gemspec +2 -2
  13. data/exe/bundler +1 -19
  14. data/lib/bundler.rb +43 -34
  15. data/lib/bundler/cli.rb +54 -5
  16. data/lib/bundler/cli/binstubs.rb +3 -2
  17. data/lib/bundler/cli/console.rb +3 -0
  18. data/lib/bundler/cli/doctor.rb +95 -0
  19. data/lib/bundler/cli/exec.rb +18 -2
  20. data/lib/bundler/cli/gem.rb +1 -1
  21. data/lib/bundler/cli/inject.rb +25 -7
  22. data/lib/bundler/cli/install.rb +23 -2
  23. data/lib/bundler/cli/lock.rb +14 -2
  24. data/lib/bundler/cli/update.rb +9 -0
  25. data/lib/bundler/definition.rb +86 -17
  26. data/lib/bundler/deployment.rb +6 -0
  27. data/lib/bundler/dsl.rb +67 -22
  28. data/lib/bundler/env.rb +1 -1
  29. data/lib/bundler/environment_preserver.rb +1 -1
  30. data/lib/bundler/errors.rb +11 -1
  31. data/lib/bundler/fetcher.rb +3 -2
  32. data/lib/bundler/fetcher/base.rb +10 -0
  33. data/lib/bundler/fetcher/compact_index.rb +27 -9
  34. data/lib/bundler/fetcher/dependency.rb +1 -12
  35. data/lib/bundler/fetcher/downloader.rb +1 -1
  36. data/lib/bundler/friendly_errors.rb +4 -2
  37. data/lib/bundler/gem_helper.rb +2 -2
  38. data/lib/bundler/gem_version_promoter.rb +175 -0
  39. data/lib/bundler/graph.rb +4 -25
  40. data/lib/bundler/index.rb +9 -1
  41. data/lib/bundler/injector.rb +12 -5
  42. data/lib/bundler/inline.rb +2 -2
  43. data/lib/bundler/installer.rb +23 -8
  44. data/lib/bundler/installer/gem_installer.rb +13 -15
  45. data/lib/bundler/installer/parallel_installer.rb +121 -99
  46. data/lib/bundler/lazy_specification.rb +8 -2
  47. data/lib/bundler/lockfile_parser.rb +20 -12
  48. data/lib/bundler/mirror.rb +2 -2
  49. data/lib/bundler/plugin.rb +153 -31
  50. data/lib/bundler/plugin/api.rb +29 -5
  51. data/lib/bundler/plugin/api/source.rb +293 -0
  52. data/lib/bundler/plugin/dsl.rb +25 -1
  53. data/lib/bundler/plugin/index.rb +80 -13
  54. data/lib/bundler/plugin/installer.rb +6 -10
  55. data/lib/bundler/plugin/source_list.rb +4 -0
  56. data/lib/bundler/postit_trampoline.rb +57 -40
  57. data/lib/bundler/resolver.rb +24 -12
  58. data/lib/bundler/retry.rb +2 -1
  59. data/lib/bundler/ruby_version.rb +4 -2
  60. data/lib/bundler/rubygems_ext.rb +10 -3
  61. data/lib/bundler/rubygems_gem_installer.rb +6 -0
  62. data/lib/bundler/rubygems_integration.rb +101 -66
  63. data/lib/bundler/runtime.rb +25 -2
  64. data/lib/bundler/settings.rb +30 -11
  65. data/lib/bundler/setup.rb +6 -3
  66. data/lib/bundler/shared_helpers.rb +11 -5
  67. data/lib/bundler/source/gemspec.rb +4 -0
  68. data/lib/bundler/source/git.rb +9 -6
  69. data/lib/bundler/source/git/git_proxy.rb +27 -3
  70. data/lib/bundler/source/path.rb +4 -26
  71. data/lib/bundler/source/path/installer.rb +39 -11
  72. data/lib/bundler/source/rubygems.rb +1 -1
  73. data/lib/bundler/source_list.rb +28 -8
  74. data/lib/bundler/spec_set.rb +1 -1
  75. data/lib/bundler/templates/Executable.standalone +4 -2
  76. data/lib/bundler/templates/Gemfile +0 -1
  77. data/lib/bundler/ui/shell.rb +11 -3
  78. data/lib/bundler/ui/silent.rb +1 -3
  79. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +1 -2
  80. data/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb +16 -2
  81. data/lib/bundler/version.rb +1 -1
  82. data/lib/bundler/yaml_serializer.rb +34 -11
  83. data/man/bundle-binstubs.ronn +29 -0
  84. data/man/bundle-config.ronn +32 -0
  85. data/man/bundle-install.ronn +6 -41
  86. data/man/bundle-package.ronn +1 -1
  87. data/man/bundle.ronn +4 -3
  88. data/man/gemfile.5.ronn +1 -1
  89. metadata +13 -9
  90. data/lib/bundler/environment.rb +0 -42
@@ -36,6 +36,16 @@ module Bundler
36
36
  def api_fetcher?
37
37
  false
38
38
  end
39
+
40
+ private
41
+
42
+ def log_specs(debug_msg)
43
+ if Bundler.ui.debug?
44
+ Bundler.ui.debug debug_msg
45
+ else
46
+ Bundler.ui.info ".", false
47
+ end
48
+ end
39
49
  end
40
50
  end
41
51
  end
@@ -36,7 +36,7 @@ module Bundler
36
36
  remaining_gems = gem_names.dup
37
37
 
38
38
  until remaining_gems.empty?
39
- Bundler.ui.debug "Looking up gems #{remaining_gems.inspect}"
39
+ log_specs "Looking up gems #{remaining_gems.inspect}"
40
40
 
41
41
  deps = compact_index_client.dependencies(remaining_gems)
42
42
  next_gems = deps.map {|d| d[3].map(&:first).flatten(1) }.flatten(1).uniq
@@ -44,6 +44,8 @@ module Bundler
44
44
  complete_gems.concat(deps.map(&:first)).uniq!
45
45
  remaining_gems = next_gems - complete_gems
46
46
  end
47
+ @bundle_worker.stop if @bundle_worker
48
+ @bundle_worker = nil # reset it. Not sure if necessary
47
49
 
48
50
  gem_info
49
51
  end
@@ -76,28 +78,44 @@ module Bundler
76
78
  private
77
79
 
78
80
  def compact_index_client
79
- @compact_index_client ||= begin
80
- compact_fetcher = lambda do |path, headers|
81
- downloader.fetch(fetch_uri + path, headers)
82
- end
83
-
81
+ @compact_index_client ||=
84
82
  SharedHelpers.filesystem_access(cache_path) do
85
83
  CompactIndexClient.new(cache_path, compact_fetcher)
86
84
  end.tap do |client|
87
85
  client.in_parallel = lambda do |inputs, &blk|
88
86
  func = lambda {|object, _index| blk.call(object) }
89
- worker_name = "Compact Index (#{display_uri.host})"
90
- worker = Bundler::Worker.new(25, worker_name, func)
87
+ worker = bundle_worker(func)
91
88
  inputs.each {|input| worker.enq(input) }
92
- inputs.map { worker.deq }.tap { worker.stop }
89
+ inputs.map { worker.deq }
93
90
  end
94
91
  end
92
+ end
93
+
94
+ def bundle_worker(func = nil)
95
+ @bundle_worker ||= begin
96
+ worker_name = "Compact Index (#{display_uri.host})"
97
+ Bundler::Worker.new(25, worker_name, func)
98
+ end
99
+ @bundle_worker.tap do |worker|
100
+ worker.instance_variable_set(:@func, func) if func
95
101
  end
96
102
  end
97
103
 
98
104
  def cache_path
99
105
  Bundler.user_cache.join("compact_index", remote.cache_slug)
100
106
  end
107
+
108
+ def compact_fetcher
109
+ lambda do |path, headers|
110
+ begin
111
+ downloader.fetch(fetch_uri + path, headers)
112
+ rescue NetworkDownError => e
113
+ raise unless Bundler.settings[:allow_offline_install] && headers["If-None-Match"]
114
+ Bundler.ui.warn "Using the cached data for the new index because of a network error: #{e}"
115
+ Net::HTTPNotModified.new(nil, nil, nil)
116
+ end
117
+ end
118
+ end
101
119
  end
102
120
  end
103
121
  end
@@ -23,7 +23,7 @@ module Bundler
23
23
  def specs(gem_names, full_dependency_list = [], last_spec_list = [])
24
24
  query_list = gem_names.uniq - full_dependency_list
25
25
 
26
- log_specs(query_list)
26
+ log_specs "Query List: #{query_list.inspect}"
27
27
 
28
28
  return last_spec_list if query_list.empty?
29
29
 
@@ -76,17 +76,6 @@ module Bundler
76
76
  uri.query = "gems=#{CGI.escape(gem_names.join(","))}" if gem_names.any?
77
77
  uri
78
78
  end
79
-
80
- private
81
-
82
- def log_specs(query_list)
83
- # only display the message on the first run
84
- if Bundler.ui.debug?
85
- Bundler.ui.debug "Query List: #{query_list.inspect}"
86
- else
87
- Bundler.ui.info ".", false
88
- end
89
- end
90
79
  end
91
80
  end
92
81
  end
@@ -14,7 +14,7 @@ module Bundler
14
14
  raise HTTPError, "Too many redirects" if counter >= redirect_limit
15
15
 
16
16
  response = request(uri, options)
17
- Bundler.ui.debug("HTTP #{response.code} #{response.message}")
17
+ Bundler.ui.debug("HTTP #{response.code} #{response.message} #{uri}")
18
18
 
19
19
  case response
20
20
  when Net::HTTPSuccess, Net::HTTPNotModified
@@ -12,7 +12,7 @@ module Bundler
12
12
  when YamlSyntaxError
13
13
  Bundler.ui.error error.message
14
14
  Bundler.ui.trace error.orig_exception
15
- when Dsl::DSLError
15
+ when Dsl::DSLError, GemspecError
16
16
  Bundler.ui.error error.message
17
17
  when GemRequireError
18
18
  Bundler.ui.error error.message
@@ -89,8 +89,10 @@ module Bundler
89
89
  end
90
90
 
91
91
  def issues_url(exception)
92
+ message = exception.message.lines.first.tr(":", " ").chomp
93
+ message = message.split("-").first if exception.is_a?(Errno)
92
94
  "https://github.com/bundler/bundler/search?q=" \
93
- "#{CGI.escape(exception.message.lines.first.chomp)}&type=Issues"
95
+ "#{CGI.escape(message)}&type=Issues"
94
96
  end
95
97
  end
96
98
 
@@ -140,7 +140,7 @@ module Bundler
140
140
  end
141
141
 
142
142
  def tag_version
143
- sh "git tag -a -m \"Version #{version}\" #{version_tag}"
143
+ sh "git tag -m \"Version #{version}\" #{version_tag}"
144
144
  Bundler.ui.confirm "Tagged #{version_tag}."
145
145
  yield if block_given?
146
146
  rescue
@@ -182,7 +182,7 @@ module Bundler
182
182
  end
183
183
 
184
184
  def gem_push?
185
- ! %w(n no nil false off 0).include?(ENV["gem_push"].to_s.downcase)
185
+ !%w(n no nil false off 0).include?(ENV["gem_push"].to_s.downcase)
186
186
  end
187
187
  end
188
188
  end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+ module Bundler
3
+ # This class contains all of the logic for determining the next version of a
4
+ # Gem to update to based on the requested level (patch, minor, major).
5
+ # Primarily designed to work with Resolver which will provide it the list of
6
+ # available dependency versions as found in its index, before returning it to
7
+ # to the resolution engine to select the best version.
8
+ class GemVersionPromoter
9
+ attr_reader :level, :locked_specs, :unlock_gems
10
+
11
+ # By default, strict is false, meaning every available version of a gem
12
+ # is returned from sort_versions. The order gives preference to the
13
+ # requested level (:patch, :minor, :major) but in complicated requirement
14
+ # cases some gems will by necessity by promoted past the requested level,
15
+ # or even reverted to older versions.
16
+ #
17
+ # If strict is set to true, the results from sort_versions will be
18
+ # truncated, eliminating any version outside the current level scope.
19
+ # This can lead to unexpected outcomes or even VersionConflict exceptions
20
+ # that report a version of a gem not existing for versions that indeed do
21
+ # existing in the referenced source.
22
+ attr_accessor :strict
23
+
24
+ # Given a list of locked_specs and a list of gems to unlock creates a
25
+ # GemVersionPromoter instance.
26
+ #
27
+ # @param locked_specs [SpecSet] All current locked specs. Unlike Definition
28
+ # where this list is empty if all gems are being updated, this should
29
+ # always be populated for all gems so this class can properly function.
30
+ # @param unlock_gems [String] List of gem names being unlocked. If empty,
31
+ # all gems will be considered unlocked.
32
+ # @return [GemVersionPromoter]
33
+ def initialize(locked_specs = SpecSet.new([]), unlock_gems = [])
34
+ @level = :major
35
+ @strict = false
36
+ @locked_specs = locked_specs
37
+ @unlock_gems = unlock_gems
38
+ @sort_versions = {}
39
+ end
40
+
41
+ # @param value [Symbol] One of three Symbols: :major, :minor or :patch.
42
+ def level=(value)
43
+ v = case value
44
+ when String, Symbol
45
+ value.to_sym
46
+ end
47
+
48
+ raise ArgumentError, "Unexpected level #{v}. Must be :major, :minor or :patch" unless [:major, :minor, :patch].include?(v)
49
+ @level = v
50
+ end
51
+
52
+ # Given a Dependency and an Array of SpecGroups of available versions for a
53
+ # gem, this method will return the Array of SpecGroups sorted (and possibly
54
+ # truncated if strict is true) in an order to give preference to the current
55
+ # level (:major, :minor or :patch) when resolution is deciding what versions
56
+ # best resolve all dependencies in the bundle.
57
+ # @param dep [Dependency] The Dependency of the gem.
58
+ # @param spec_groups [SpecGroup] An array of SpecGroups for the same gem
59
+ # named in the @dep param.
60
+ # @return [SpecGroup] A new instance of the SpecGroup Array sorted and
61
+ # possibly filtered.
62
+ def sort_versions(dep, spec_groups)
63
+ before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if ENV["DEBUG_RESOLVER"]
64
+
65
+ @sort_versions[dep] ||= begin
66
+ gem_name = dep.name
67
+
68
+ # An Array per version returned, different entries for different platforms.
69
+ # We only need the version here so it's ok to hard code this to the first instance.
70
+ locked_spec = locked_specs[gem_name].first
71
+
72
+ if strict
73
+ filter_dep_specs(spec_groups, locked_spec)
74
+ else
75
+ sort_dep_specs(spec_groups, locked_spec)
76
+ end.tap do |specs|
77
+ if ENV["DEBUG_RESOLVER"]
78
+ STDERR.puts before_result
79
+ STDERR.puts " after sort_versions: #{debug_format_result(dep, specs).inspect}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ # @return [bool] Convenience method for testing value of level variable.
86
+ def major?
87
+ level == :major
88
+ end
89
+
90
+ # @return [bool] Convenience method for testing value of level variable.
91
+ def minor?
92
+ level == :minor
93
+ end
94
+
95
+ private
96
+
97
+ def filter_dep_specs(spec_groups, locked_spec)
98
+ res = spec_groups.select do |spec_group|
99
+ if locked_spec && !major?
100
+ gsv = spec_group.version
101
+ lsv = locked_spec.version
102
+
103
+ must_match = minor? ? [0] : [0, 1]
104
+
105
+ matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] }
106
+ (matches.uniq == [true]) ? (gsv >= lsv) : false
107
+ else
108
+ true
109
+ end
110
+ end
111
+
112
+ sort_dep_specs(res, locked_spec)
113
+ end
114
+
115
+ def sort_dep_specs(spec_groups, locked_spec)
116
+ return spec_groups unless locked_spec
117
+ @gem_name = locked_spec.name
118
+ @locked_version = locked_spec.version
119
+
120
+ result = spec_groups.sort do |a, b|
121
+ @a_ver = a.version
122
+ @b_ver = b.version
123
+ if major?
124
+ @a_ver <=> @b_ver
125
+ elsif either_version_older_than_locked
126
+ @a_ver <=> @b_ver
127
+ elsif segments_do_not_match(:major)
128
+ @b_ver <=> @a_ver
129
+ elsif !minor? && segments_do_not_match(:minor)
130
+ @b_ver <=> @a_ver
131
+ else
132
+ @a_ver <=> @b_ver
133
+ end
134
+ end
135
+ post_sort(result)
136
+ end
137
+
138
+ def either_version_older_than_locked
139
+ @a_ver < @locked_version || @b_ver < @locked_version
140
+ end
141
+
142
+ def segments_do_not_match(level)
143
+ index = [:major, :minor].index(level)
144
+ @a_ver.segments[index] != @b_ver.segments[index]
145
+ end
146
+
147
+ def unlocking_gem?
148
+ unlock_gems.empty? || unlock_gems.include?(@gem_name)
149
+ end
150
+
151
+ # Specific version moves can't always reliably be done during sorting
152
+ # as not all elements are compared against each other.
153
+ def post_sort(result)
154
+ # default :major behavior in Bundler does not do this
155
+ return result if major?
156
+ if unlocking_gem?
157
+ result
158
+ else
159
+ move_version_to_end(result, @locked_version)
160
+ end
161
+ end
162
+
163
+ def move_version_to_end(result, version)
164
+ move, keep = result.partition {|s| s.version.to_s == version.to_s }
165
+ keep.concat(move)
166
+ end
167
+
168
+ def debug_format_result(dep, spec_groups)
169
+ a = [dep.to_s,
170
+ spec_groups.map {|sg| [sg.version, sg.dependencies_for_activated_platforms.map {|dp| [dp.name, dp.requirement.to_s] }] }]
171
+ last_map = a.last.map {|sg_data| [sg_data.first.version, sg_data.last.map {|aa| aa.join(" ") }] }
172
+ [a.first, last_map, level, strict ? :strict : :not_strict]
173
+ end
174
+ end
175
+ end
@@ -17,7 +17,6 @@ module Bundler
17
17
  @node_options = {}
18
18
  @edge_options = {}
19
19
 
20
- _patching_gem_dependency_class
21
20
  _populate_relations
22
21
  end
23
22
 
@@ -36,10 +35,7 @@ module Bundler
36
35
 
37
36
  tmp = Set.new
38
37
  parent_dependencies.each do |dependency|
39
- # if the dependency is a prerelease, allow to_spec to be non-nil
40
- dependency.prerelease = true
41
-
42
- child_dependencies = dependency.to_spec.runtime_dependencies.to_set
38
+ child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set
43
39
  @relations[dependency.name] += child_dependencies.map(&:name).to_set
44
40
  tmp += child_dependencies
45
41
 
@@ -74,7 +70,7 @@ module Bundler
74
70
  when :node
75
71
  if symbol_or_string_or_dependency.is_a?(Gem::Dependency)
76
72
  label = symbol_or_string_or_dependency.name.dup
77
- label << "\n#{symbol_or_string_or_dependency.to_spec.version}" if @show_version
73
+ label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version
78
74
  else
79
75
  label = symbol_or_string_or_dependency.to_s
80
76
  end
@@ -90,25 +86,8 @@ module Bundler
90
86
  label.nil? ? {} : { :label => label }
91
87
  end
92
88
 
93
- def _patching_gem_dependency_class
94
- # method borrow from rubygems/dependency.rb
95
- # redefinition of matching_specs will also redefine to_spec and to_specs
96
- Gem::Dependency.class_eval do
97
- def matching_specs(platform_only = false)
98
- matches = Bundler.load.specs.select do |spec|
99
- name == spec.name &&
100
- requirement.satisfied_by?(spec.version)
101
- end
102
-
103
- if platform_only
104
- matches.select! do |spec|
105
- Gem::Platform.match spec.platform
106
- end
107
- end
108
-
109
- matches = matches.sort_by(&:sort_obj) # HACK: shouldn't be needed
110
- end
111
- end
89
+ def spec_for_dependency(dependency)
90
+ @env.requested_specs.find {|s| s.name == dependency.name }
112
91
  end
113
92
 
114
93
  class GraphVizClient
@@ -67,12 +67,20 @@ module Bundler
67
67
  end
68
68
  end
69
69
 
70
- results.sort_by do |s|
70
+ sort_specs(results)
71
+ end
72
+
73
+ def self.sort_specs(specs)
74
+ specs.sort_by do |s|
71
75
  platform_string = s.platform.to_s
72
76
  [s.version, platform_string == RUBY ? NULL : platform_string]
73
77
  end
74
78
  end
75
79
 
80
+ def sort_specs(specs)
81
+ self.class.sort_specs(specs)
82
+ end
83
+
76
84
  def local_search(query, base = nil)
77
85
  case query
78
86
  when Gem::Specification, RemoteSpecification, LazySpecification, EndpointSpecification then search_by_spec(query)
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  module Bundler
3
3
  class Injector
4
- def self.inject(new_deps)
5
- injector = new(new_deps)
4
+ def self.inject(new_deps, options = {})
5
+ injector = new(new_deps, options)
6
6
  injector.inject(Bundler.default_gemfile, Bundler.default_lockfile)
7
7
  end
8
8
 
9
- def initialize(new_deps)
9
+ def initialize(new_deps, options = {})
10
10
  @new_deps = new_deps
11
+ @options = options
11
12
  end
12
13
 
13
14
  def inject(gemfile_path, lockfile_path)
@@ -48,14 +49,20 @@ module Bundler
48
49
 
49
50
  def new_gem_lines
50
51
  @new_deps.map do |d|
51
- %(gem '#{d.name}', '#{d.requirement}')
52
+ name = "'#{d.name}'"
53
+ requirement = ", '#{d.requirement}'"
54
+ group = ", :group => #{d.groups.inspect}" if d.groups != Array(:default)
55
+ source = ", :source => '#{d.source}'" unless d.source.nil?
56
+ %(gem #{name}#{requirement}#{group}#{source})
52
57
  end.join("\n")
53
58
  end
54
59
 
55
60
  def append_to(gemfile_path)
56
61
  gemfile_path.open("a") do |f|
57
62
  f.puts
58
- f.puts "# Added at #{Time.now} by #{`whoami`.chomp}:"
63
+ if @options["timestamp"] || @options["timestamp"].nil?
64
+ f.puts "# Added at #{Time.now} by #{`whoami`.chomp}:"
65
+ end
59
66
  f.puts new_gem_lines
60
67
  end
61
68
  end