rubygems-update 3.2.0 → 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/{History.txt → CHANGELOG.md} +482 -457
  3. data/Manifest.txt +31 -27
  4. data/POLICIES.md +4 -3
  5. data/Rakefile +23 -18
  6. data/bundler/CHANGELOG.md +67 -20
  7. data/bundler/bundler.gemspec +1 -1
  8. data/bundler/lib/bundler.rb +8 -2
  9. data/bundler/lib/bundler/build_metadata.rb +2 -2
  10. data/bundler/lib/bundler/cli.rb +3 -6
  11. data/bundler/lib/bundler/cli/gem.rb +2 -0
  12. data/bundler/lib/bundler/cli/install.rb +14 -5
  13. data/bundler/lib/bundler/cli/outdated.rb +2 -2
  14. data/bundler/lib/bundler/cli/update.rb +1 -1
  15. data/bundler/lib/bundler/compact_index_client/cache.rb +5 -13
  16. data/bundler/lib/bundler/compact_index_client/gem_parser.rb +28 -0
  17. data/bundler/lib/bundler/compact_index_client/updater.rb +0 -8
  18. data/bundler/lib/bundler/definition.rb +24 -21
  19. data/bundler/lib/bundler/dependency.rb +3 -1
  20. data/bundler/lib/bundler/gem_helper.rb +3 -3
  21. data/bundler/lib/bundler/gem_helpers.rb +30 -24
  22. data/bundler/lib/bundler/lazy_specification.rb +16 -3
  23. data/bundler/{man → lib/bundler/man}/bundle-add.1 +0 -0
  24. data/bundler/{man → lib/bundler/man}/bundle-binstubs.1 +0 -0
  25. data/bundler/{man → lib/bundler/man}/bundle-cache.1 +0 -0
  26. data/bundler/{man → lib/bundler/man}/bundle-check.1 +0 -0
  27. data/bundler/{man → lib/bundler/man}/bundle-clean.1 +0 -0
  28. data/bundler/{man → lib/bundler/man}/bundle-config.1 +0 -0
  29. data/bundler/{man → lib/bundler/man}/bundle-doctor.1 +0 -0
  30. data/bundler/{man → lib/bundler/man}/bundle-exec.1 +0 -0
  31. data/bundler/{man → lib/bundler/man}/bundle-gem.1 +0 -0
  32. data/bundler/{man → lib/bundler/man}/bundle-info.1 +0 -0
  33. data/bundler/{man → lib/bundler/man}/bundle-init.1 +0 -0
  34. data/bundler/{man → lib/bundler/man}/bundle-inject.1 +0 -0
  35. data/bundler/{man → lib/bundler/man}/bundle-install.1 +0 -0
  36. data/bundler/{man → lib/bundler/man}/bundle-list.1 +0 -0
  37. data/bundler/{man → lib/bundler/man}/bundle-lock.1 +0 -0
  38. data/bundler/{man → lib/bundler/man}/bundle-open.1 +0 -0
  39. data/bundler/{man → lib/bundler/man}/bundle-outdated.1 +0 -0
  40. data/bundler/{man → lib/bundler/man}/bundle-platform.1 +0 -0
  41. data/bundler/{man → lib/bundler/man}/bundle-pristine.1 +0 -0
  42. data/bundler/{man → lib/bundler/man}/bundle-remove.1 +0 -0
  43. data/bundler/{man → lib/bundler/man}/bundle-show.1 +0 -0
  44. data/bundler/{man → lib/bundler/man}/bundle-update.1 +0 -0
  45. data/bundler/{man → lib/bundler/man}/bundle-viz.1 +0 -0
  46. data/bundler/{man → lib/bundler/man}/bundle.1 +0 -0
  47. data/bundler/{man → lib/bundler/man}/gemfile.5 +0 -0
  48. data/bundler/{man → lib/bundler/man}/index.txt +0 -0
  49. data/bundler/lib/bundler/resolver.rb +29 -27
  50. data/bundler/lib/bundler/resolver/spec_group.rb +19 -25
  51. data/bundler/lib/bundler/rubygems_integration.rb +0 -6
  52. data/bundler/lib/bundler/source/git.rb +18 -16
  53. data/bundler/lib/bundler/source/git/git_proxy.rb +54 -49
  54. data/bundler/lib/bundler/source/path/installer.rb +2 -0
  55. data/bundler/lib/bundler/source/rubygems.rb +10 -1
  56. data/bundler/lib/bundler/spec_set.rb +5 -8
  57. data/bundler/lib/bundler/stub_specification.rb +0 -2
  58. data/bundler/lib/bundler/templates/newgem/Gemfile.tt +1 -1
  59. data/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt +1 -1
  60. data/bundler/lib/bundler/templates/newgem/rubocop.yml.tt +3 -0
  61. data/bundler/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +8 -1
  62. data/bundler/lib/bundler/version.rb +1 -1
  63. data/lib/rubygems.rb +1 -1
  64. data/lib/rubygems/commands/owner_command.rb +0 -1
  65. data/lib/rubygems/commands/push_command.rb +0 -1
  66. data/lib/rubygems/commands/setup_command.rb +16 -62
  67. data/lib/rubygems/commands/yank_command.rb +0 -1
  68. data/lib/rubygems/dependency_installer.rb +1 -0
  69. data/lib/rubygems/ext/builder.rb +3 -3
  70. data/lib/rubygems/ext/cmake_builder.rb +1 -2
  71. data/lib/rubygems/ext/configure_builder.rb +1 -2
  72. data/lib/rubygems/ext/rake_builder.rb +1 -1
  73. data/lib/rubygems/gemcutter_utilities.rb +22 -17
  74. data/lib/rubygems/installer.rb +0 -23
  75. data/lib/rubygems/remote_fetcher.rb +4 -2
  76. data/lib/rubygems/request_set.rb +2 -13
  77. data/lib/rubygems/resolver.rb +6 -1
  78. data/lib/rubygems/resolver/api_set.rb +29 -20
  79. data/lib/rubygems/resolver/api_set/gem_parser.rb +20 -0
  80. data/lib/rubygems/resolver/api_specification.rb +4 -3
  81. data/lib/rubygems/resolver/best_set.rb +2 -2
  82. data/lib/rubygems/resolver/index_specification.rb +18 -0
  83. data/lib/rubygems/resolver/installer_set.rb +57 -7
  84. data/lib/rubygems/resolver/spec_specification.rb +14 -0
  85. data/lib/rubygems/resolver/specification.rb +12 -0
  86. data/lib/rubygems/server.rb +6 -1
  87. data/lib/rubygems/source.rb +11 -6
  88. data/lib/rubygems/specification.rb +18 -14
  89. data/lib/rubygems/test_case.rb +17 -4
  90. data/lib/rubygems/test_utilities.rb +6 -5
  91. data/rubygems-update.gemspec +2 -2
  92. data/test/rubygems/data/null-required-rubygems-version.gemspec.rz +0 -0
  93. data/test/rubygems/test_gem_commands_install_command.rb +131 -0
  94. data/test/rubygems/test_gem_commands_push_command.rb +41 -2
  95. data/test/rubygems/test_gem_commands_setup_command.rb +21 -37
  96. data/test/rubygems/test_gem_dependency_installer.rb +27 -47
  97. data/test/rubygems/test_gem_ext_builder.rb +6 -6
  98. data/test/rubygems/test_gem_ext_cmake_builder.rb +2 -4
  99. data/test/rubygems/test_gem_ext_configure_builder.rb +2 -2
  100. data/test/rubygems/test_gem_ext_rake_builder.rb +25 -0
  101. data/test/rubygems/test_gem_installer.rb +6 -60
  102. data/test/rubygems/test_gem_remote_fetcher.rb +1 -1
  103. data/test/rubygems/test_gem_resolver_api_set.rb +54 -51
  104. data/test/rubygems/test_gem_resolver_api_specification.rb +3 -3
  105. data/test/rubygems/test_gem_resolver_best_set.rb +26 -3
  106. data/test/rubygems/test_gem_source.rb +2 -2
  107. data/test/rubygems/test_gem_source_subpath_problem.rb +2 -2
  108. data/test/rubygems/test_gem_specification.rb +9 -3
  109. data/test/test_changelog_generator.rb +17 -0
  110. metadata +38 -58
@@ -4,8 +4,8 @@ module Bundler
4
4
  # Represents metadata from when the Bundler gem was built.
5
5
  module BuildMetadata
6
6
  # begin ivars
7
- @built_at = "2020-12-10".freeze
8
- @git_commit_sha = "d3628e5019".freeze
7
+ @built_at = "2021-01-11".freeze
8
+ @git_commit_sha = "ba867aed7f".freeze
9
9
  @release = true
10
10
  # end ivars
11
11
 
@@ -57,7 +57,7 @@ module Bundler
57
57
  custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile]
58
58
  if custom_gemfile && !custom_gemfile.empty?
59
59
  Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
60
- Bundler.reset_settings!
60
+ Bundler.reset_settings_and_root!
61
61
  end
62
62
 
63
63
  Bundler.settings.set_command_option_if_given :retry, options[:retry]
@@ -122,9 +122,7 @@ module Bundler
122
122
  else command = "bundle-#{cli}"
123
123
  end
124
124
 
125
- man_path = File.expand_path("../../../man", __FILE__)
126
- # man files are located under ruby's mandir with the default gems of bundler
127
- man_path = RbConfig::CONFIG["mandir"] unless File.directory?(man_path)
125
+ man_path = File.expand_path("man", __dir__)
128
126
  man_pages = Hash[Dir.glob(File.join(man_path, "**", "*")).grep(/.*\.\d*\Z/).collect do |f|
129
127
  [File.basename(f, ".*"), f]
130
128
  end]
@@ -134,8 +132,7 @@ module Bundler
134
132
  if Bundler.which("man") && man_path !~ %r{^file:/.+!/META-INF/jruby.home/.+}
135
133
  Kernel.exec "man #{man_page}"
136
134
  else
137
- fallback_man_path = File.expand_path("../man", __FILE__)
138
- puts File.read("#{fallback_man_path}/#{File.basename(man_page)}.ronn")
135
+ puts File.read("#{man_path}/#{File.basename(man_page)}.ronn")
139
136
  end
140
137
  elsif command_path = Bundler.which("bundler-#{cli}")
141
138
  Kernel.exec(command_path, "--help")
@@ -59,6 +59,7 @@ module Bundler
59
59
  :exe => options[:exe],
60
60
  :bundler_version => bundler_dependency_version,
61
61
  :github_username => github_username.empty? ? "[USERNAME]" : github_username,
62
+ :required_ruby_version => Gem.ruby_version < Gem::Version.new("2.4.a") ? "2.3.0" : "2.4.0",
62
63
  }
63
64
  ensure_safe_gem_name(name, constant_array)
64
65
 
@@ -147,6 +148,7 @@ module Bundler
147
148
  "For more information, see the RuboCop docs (https://docs.rubocop.org/en/stable/) " \
148
149
  "and the Ruby Style Guides (https://github.com/rubocop-hq/ruby-style-guide).")
149
150
  config[:rubocop] = true
151
+ config[:rubocop_version] = Gem.ruby_version < Gem::Version.new("2.4.a") ? "0.81.0" : "1.7"
150
152
  Bundler.ui.info "RuboCop enabled in config"
151
153
  templates.merge!("rubocop.yml.tt" => ".rubocop.yml")
152
154
  end
@@ -152,18 +152,27 @@ module Bundler
152
152
 
153
153
  check_for_group_conflicts_in_cli_options
154
154
 
155
+ Bundler.settings.set_command_option :with, nil if options[:with] == []
156
+ Bundler.settings.set_command_option :without, nil if options[:without] == []
157
+
155
158
  with = options.fetch(:with, [])
156
159
  with |= Bundler.settings[:with].map(&:to_s)
157
160
  with -= options[:without] if options[:without]
158
- with = nil if options[:with] == []
159
161
 
160
162
  without = options.fetch(:without, [])
161
163
  without |= Bundler.settings[:without].map(&:to_s)
162
164
  without -= options[:with] if options[:with]
163
- without = nil if options[:without] == []
164
165
 
165
- Bundler.settings.set_command_option :without, without
166
- Bundler.settings.set_command_option :with, with
166
+ options[:with] = with
167
+ options[:without] = without
168
+
169
+ unless Bundler.settings[:without] == options[:without] && Bundler.settings[:with] == options[:with]
170
+ # need to nil them out first to get around validation for backwards compatibility
171
+ Bundler.settings.set_command_option :without, nil
172
+ Bundler.settings.set_command_option :with, nil
173
+ Bundler.settings.set_command_option :without, options[:without] - options[:with]
174
+ Bundler.settings.set_command_option :with, options[:with]
175
+ end
167
176
  end
168
177
 
169
178
  def normalize_settings
@@ -190,7 +199,7 @@ module Bundler
190
199
 
191
200
  Bundler.settings.set_command_option_if_given :clean, options["clean"]
192
201
 
193
- normalize_groups if options[:without] || options[:with]
202
+ normalize_groups
194
203
 
195
204
  options[:force] = options[:redownload]
196
205
  end
@@ -76,6 +76,8 @@ module Bundler
76
76
  next unless gems.empty? || gems.include?(current_spec.name)
77
77
 
78
78
  active_spec = retrieve_active_spec(definition, current_spec)
79
+ next unless active_spec
80
+
79
81
  next unless filter_options_patch.empty? || update_present_via_semver_portions(current_spec, active_spec, options)
80
82
 
81
83
  gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
@@ -229,8 +231,6 @@ module Bundler
229
231
  end
230
232
 
231
233
  def update_present_via_semver_portions(current_spec, active_spec, options)
232
- return false if active_spec.nil?
233
-
234
234
  current_major = current_spec.version.segments.first
235
235
  active_major = active_spec.version.segments.first
236
236
 
@@ -82,7 +82,7 @@ module Bundler
82
82
  locked_spec = locked_info[:spec]
83
83
  new_spec = Bundler.definition.specs[name].first
84
84
  unless new_spec
85
- if Bundler.rubygems.platforms.none? {|p| locked_spec.match_platform(p) }
85
+ unless locked_spec.match_platform(Bundler.local_platform)
86
86
  Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one"
87
87
  end
88
88
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "gem_parser"
4
+
3
5
  module Bundler
4
6
  class CompactIndexClient
5
7
  class Cache
@@ -92,19 +94,9 @@ module Bundler
92
94
  header ? lines[header + 1..-1] : lines
93
95
  end
94
96
 
95
- def parse_gem(string)
96
- version_and_platform, rest = string.split(" ", 2)
97
- version, platform = version_and_platform.split("-", 2)
98
- dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
99
- dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
100
- requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
101
- [version, platform, dependencies, requirements]
102
- end
103
-
104
- def parse_dependency(string)
105
- dependency = string.split(":")
106
- dependency[-1] = dependency[-1].split("&") if dependency.size > 1
107
- dependency
97
+ def parse_gem(line)
98
+ @dependency_parser ||= GemParser.new
99
+ @dependency_parser.parse(line)
108
100
  end
109
101
 
110
102
  def info_roots
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ class CompactIndexClient
5
+ if defined?(Gem::Resolver::APISet::GemParser)
6
+ GemParser = Gem::Resolver::APISet::GemParser
7
+ else
8
+ class GemParser
9
+ def parse(line)
10
+ version_and_platform, rest = line.split(" ", 2)
11
+ version, platform = version_and_platform.split("-", 2)
12
+ dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
13
+ dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
14
+ requirements = requirements ? requirements.map {|d| parse_dependency(d) } : []
15
+ [version, platform, dependencies, requirements]
16
+ end
17
+
18
+ private
19
+
20
+ def parse_dependency(string)
21
+ dependency = string.split(":")
22
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
23
+ dependency
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../vendored_fileutils"
4
- require "stringio"
5
- require "zlib"
6
4
 
7
5
  module Bundler
8
6
  class CompactIndexClient
@@ -45,18 +43,12 @@ module Bundler
45
43
  else
46
44
  "bytes=#{local_temp_path.size}-"
47
45
  end
48
- else
49
- # Fastly ignores Range when Accept-Encoding: gzip is set
50
- headers["Accept-Encoding"] = "gzip"
51
46
  end
52
47
 
53
48
  response = @fetcher.call(remote_path, headers)
54
49
  return nil if response.is_a?(Net::HTTPNotModified)
55
50
 
56
51
  content = response.body
57
- if response["Content-Encoding"] == "gzip"
58
- content = Zlib::GzipReader.new(StringIO.new(content)).read
59
- end
60
52
 
61
53
  SharedHelpers.filesystem_access(local_temp_path) do
62
54
  if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero?
@@ -118,7 +118,7 @@ module Bundler
118
118
  end
119
119
  @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
120
120
 
121
- add_current_platform unless Bundler.frozen_bundle?
121
+ add_current_platform unless current_ruby_platform_locked? || Bundler.frozen_bundle?
122
122
 
123
123
  converge_path_sources_to_gemspec_sources
124
124
  @path_changes = converge_paths
@@ -157,7 +157,7 @@ module Bundler
157
157
  end
158
158
 
159
159
  def resolve_remotely!
160
- raise "Specs already loaded" if @specs
160
+ return if @specs
161
161
  @remote = true
162
162
  sources.remote!
163
163
  specs
@@ -269,6 +269,7 @@ module Bundler
269
269
  else
270
270
  # Run a resolve against the locally available gems
271
271
  Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
272
+ expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
272
273
  last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
273
274
  end
274
275
 
@@ -505,15 +506,11 @@ module Bundler
505
506
  end
506
507
 
507
508
  def validate_platforms!
508
- return if @platforms.any? do |bundle_platform|
509
- Bundler.rubygems.platforms.any? do |local_platform|
510
- MatchPlatform.platforms_match?(bundle_platform, local_platform)
511
- end
512
- end
509
+ return if current_platform_locked?
513
510
 
514
511
  raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
515
- "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
516
- "there's no compatible match between those two lists."
512
+ "but your local platform is #{Bundler.local_platform}. " \
513
+ "Add the current platform to the lockfile with `bundle lock --add-platform #{Bundler.local_platform}` and try again."
517
514
  end
518
515
 
519
516
  def add_platform(platform)
@@ -526,6 +523,12 @@ module Bundler
526
523
  raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
527
524
  end
528
525
 
526
+ def most_specific_locked_platform
527
+ @platforms.min_by do |bundle_platform|
528
+ platform_specificity_match(bundle_platform, local_platform)
529
+ end
530
+ end
531
+
529
532
  def find_resolved_spec(current_spec)
530
533
  specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
531
534
  end
@@ -547,12 +550,20 @@ module Bundler
547
550
 
548
551
  private
549
552
 
550
- def add_current_platform
551
- current_platforms.each {|platform| add_platform(platform) }
553
+ def current_ruby_platform_locked?
554
+ return false unless generic_local_platform == Gem::Platform::RUBY
555
+
556
+ current_platform_locked?
552
557
  end
553
558
 
554
- def current_platforms
555
- [local_platform, generic_local_platform].uniq
559
+ def current_platform_locked?
560
+ @platforms.any? do |bundle_platform|
561
+ MatchPlatform.platforms_match?(bundle_platform, Bundler.local_platform)
562
+ end
563
+ end
564
+
565
+ def add_current_platform
566
+ add_platform(local_platform)
556
567
  end
557
568
 
558
569
  def change_reason
@@ -847,14 +858,6 @@ module Bundler
847
858
  @locked_specs[dep].any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
848
859
  end
849
860
 
850
- # This list of dependencies is only used in #resolve, so it's OK to add
851
- # the metadata dependencies here
852
- def expanded_dependencies
853
- @expanded_dependencies ||= begin
854
- expand_dependencies(dependencies + metadata_dependencies, @remote)
855
- end
856
- end
857
-
858
861
  def metadata_dependencies
859
862
  @metadata_dependencies ||= begin
860
863
  ruby_versions = ruby_version_requirements(@ruby_version)
@@ -96,9 +96,11 @@ module Bundler
96
96
  def gem_platforms(valid_platforms)
97
97
  return valid_platforms if @platforms.empty?
98
98
 
99
+ valid_generic_platforms = valid_platforms.map {|p| [p, GemHelpers.generic(p)] }.to_h
99
100
  @gem_platforms ||= expanded_platforms.compact.uniq
100
101
 
101
- valid_platforms & @gem_platforms
102
+ filtered_generic_platforms = valid_generic_platforms.values & @gem_platforms
103
+ valid_generic_platforms.select {|_, v| filtered_generic_platforms.include?(v) }.keys
102
104
  end
103
105
 
104
106
  def expanded_platforms
@@ -117,13 +117,13 @@ module Bundler
117
117
  def git_push(remote = nil)
118
118
  remote ||= default_remote
119
119
  perform_git_push remote
120
- perform_git_push "#{remote} #{version_tag}"
120
+ perform_git_push "#{remote} refs/tags/#{version_tag}"
121
121
  Bundler.ui.confirm "Pushed git commits and release tag."
122
122
  end
123
123
 
124
124
  def default_remote
125
- current_branch = sh(%w[git rev-parse --abbrev-ref HEAD]).strip
126
- return "origin" if current_branch.empty?
125
+ # We can replace this with `git branch --show-current` once we drop support for git < 2.22.0
126
+ current_branch = sh(%w[git rev-parse --abbrev-ref HEAD]).gsub(%r{\Aheads/}, "").strip
127
127
 
128
128
  remote_for_branch = sh(%W[git config --get branch.#{current_branch}.remote]).strip
129
129
  return "origin" if remote_for_branch.empty?
@@ -35,41 +35,33 @@ module Bundler
35
35
 
36
36
  def platform_specificity_match(spec_platform, user_platform)
37
37
  spec_platform = Gem::Platform.new(spec_platform)
38
- return PlatformMatch::EXACT_MATCH if spec_platform == user_platform
39
- return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
40
-
41
- PlatformMatch.new(
42
- PlatformMatch.os_match(spec_platform, user_platform),
43
- PlatformMatch.cpu_match(spec_platform, user_platform),
44
- PlatformMatch.platform_version_match(spec_platform, user_platform)
45
- )
38
+
39
+ PlatformMatch.specificity_score(spec_platform, user_platform)
46
40
  end
47
41
  module_function :platform_specificity_match
48
42
 
49
43
  def select_best_platform_match(specs, platform)
50
- specs.select {|spec| spec.match_platform(platform) }.
51
- min_by {|spec| platform_specificity_match(spec.platform, platform) }
44
+ matching = specs.select {|spec| spec.match_platform(platform) }
45
+ exact = matching.select {|spec| spec.platform == platform }
46
+ return exact if exact.any?
47
+
48
+ sorted_matching = matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) }
49
+ exemplary_spec = sorted_matching.first
50
+
51
+ sorted_matching.take_while{|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) }
52
52
  end
53
53
  module_function :select_best_platform_match
54
54
 
55
- PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match)
56
55
  class PlatformMatch
57
- def <=>(other)
58
- return nil unless other.is_a?(PlatformMatch)
56
+ def self.specificity_score(spec_platform, user_platform)
57
+ return -1 if spec_platform == user_platform
58
+ return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
59
59
 
60
- m = os_match <=> other.os_match
61
- return m unless m.zero?
62
-
63
- m = cpu_match <=> other.cpu_match
64
- return m unless m.zero?
65
-
66
- m = platform_version_match <=> other.platform_version_match
67
- m
60
+ os_match(spec_platform, user_platform) +
61
+ cpu_match(spec_platform, user_platform) * 10 +
62
+ platform_version_match(spec_platform, user_platform) * 100
68
63
  end
69
64
 
70
- EXACT_MATCH = new(-1, -1, -1).freeze
71
- WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze
72
-
73
65
  def self.os_match(spec_platform, user_platform)
74
66
  if spec_platform.os == user_platform.os
75
67
  0
@@ -100,5 +92,19 @@ module Bundler
100
92
  end
101
93
  end
102
94
  end
95
+
96
+ def same_specificity(platform, spec, exemplary_spec)
97
+ platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform)
98
+ end
99
+ module_function :same_specificity
100
+
101
+ def same_deps(spec, exemplary_spec)
102
+ same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort
103
+ return same_runtime_deps unless spec.is_a?(Gem::Specification) && exemplary_spec.is_a?(Gem::Specification)
104
+
105
+ same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version
106
+ same_runtime_deps && same_metadata_deps
107
+ end
108
+ module_function :same_deps
103
109
  end
104
110
  end
@@ -4,7 +4,7 @@ require_relative "match_platform"
4
4
 
5
5
  module Bundler
6
6
  class LazySpecification
7
- Identifier = Struct.new(:name, :version, :source, :platform, :dependencies)
7
+ Identifier = Struct.new(:name, :version, :platform)
8
8
  class Identifier
9
9
  include Comparable
10
10
  def <=>(other)
@@ -82,7 +82,7 @@ module Bundler
82
82
  search_object = if source.is_a?(Source::Path)
83
83
  Dependency.new(name, version)
84
84
  else
85
- self
85
+ ruby_platform_materializes_to_ruby_platform? ? self : Dependency.new(name, version)
86
86
  end
87
87
  platform_object = Gem::Platform.new(platform)
88
88
  candidates = source.specs.search(search_object)
@@ -108,7 +108,7 @@ module Bundler
108
108
  end
109
109
 
110
110
  def identifier
111
- @__identifier ||= Identifier.new(name, version, source, platform, dependencies)
111
+ @__identifier ||= Identifier.new(name, version, platform)
112
112
  end
113
113
 
114
114
  def git_version
@@ -129,5 +129,18 @@ module Bundler
129
129
 
130
130
  @specification.send(method, *args, &blk)
131
131
  end
132
+
133
+ #
134
+ # For backwards compatibility with existing lockfiles, if the most specific
135
+ # locked platform is RUBY, we keep the previous behaviour of resolving the
136
+ # best platform variant at materiliazation time. For previous bundler
137
+ # versions (before 2.2.0) this was always the case (except when the lockfile
138
+ # only included non-ruby platforms), but we're also keeping this behaviour
139
+ # on newer bundlers unless users generate the lockfile from scratch or
140
+ # explicitly add a more specific platform.
141
+ #
142
+ def ruby_platform_materializes_to_ruby_platform?
143
+ !Bundler.most_specific_locked_platform?(Gem::Platform::RUBY)
144
+ end
132
145
  end
133
146
  end