bundler 2.5.11 → 2.5.13

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e07468635327ec2b436d1015aadd6b09377511e9793dbd614e4d528104f6cf95
4
- data.tar.gz: 6fd4b37515fe7854b32c7cfa0a48568a36a5f2a81f0ca93be86c263fec92eabe
3
+ metadata.gz: 3d55a4e4a51daffe5907ad748a96349876c9797fdf174433a32754c3d062236b
4
+ data.tar.gz: 3650a3a40f4b01e274b4a72f5a167354600e9d404aa3b688f8bb006023877eb3
5
5
  SHA512:
6
- metadata.gz: 52cc1652e43f2568c0979188ce7a78f13e92a217fc67aa5063ce9d882739e288afc0ed43db43c18de8522e7b1460d9946a9ded85f3dd4195b9e411d6e2ef1c3f
7
- data.tar.gz: '0581fccb9f4fb784b135bd5ffd84bd9b25e05597b9194d8d01844b243c85e899477b93204ea49298d72282d0bb38692b3aefebb78e24329853ac9e85d3effa2a'
6
+ metadata.gz: 3f558a26f42b927607933af8ad62e30a9ee6350dfe8230584b8e9d57fbd414f5b4239ac119c289dbf52ca7881cd956d69255f44b4e389e87d522c168ff67178b
7
+ data.tar.gz: b50b14773bd8b8aab8d938b322c1e93e0454bef759fedd74a054dc64cdc25d85990c1a9e843e5a8ba2be15fd937ca73d64a41dce76496ad5fcb2f2bcca562953
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # 2.5.13 (June 14, 2024)
2
+
3
+ ## Bug fixes:
4
+
5
+ - Fix funding metadata not being printed in some situations [#7746](https://github.com/rubygems/rubygems/pull/7746)
6
+ - Make sure to not re-resolve when a not fully specific local platform is locked [#7751](https://github.com/rubygems/rubygems/pull/7751)
7
+ - Don't print bug report template when bin dir is not writable [#7748](https://github.com/rubygems/rubygems/pull/7748)
8
+
9
+ # 2.5.12 (June 13, 2024)
10
+
11
+ ## Enhancements:
12
+
13
+ - Keep credentials in lockfile if they are already there [#7720](https://github.com/rubygems/rubygems/pull/7720)
14
+ - Auto switch to locked bundler version even when using binstubs [#7719](https://github.com/rubygems/rubygems/pull/7719)
15
+ - Don't validate local gemspecs twice unnecessarily [#7725](https://github.com/rubygems/rubygems/pull/7725)
16
+ - Improve default gem handling by treating default gems as any other gem [#7673](https://github.com/rubygems/rubygems/pull/7673)
17
+
18
+ ## Bug fixes:
19
+
20
+ - Fix slow and incorrect resolution when adding `sorbet` to a Gemfile and the lockfile only includes "RUBY" in the platforms section [#7731](https://github.com/rubygems/rubygems/pull/7731)
21
+ - Fix duplicated config keys generated when `fallback_timeout` uri option is used [#7704](https://github.com/rubygems/rubygems/pull/7704)
22
+ - Fix `bundle exec` no longer working in truffleruby after explicit `require` of `pathname` was removed [#7703](https://github.com/rubygems/rubygems/pull/7703)
23
+ - Don't let `bundle config` report a path without a Gemfile as "local app" [#7687](https://github.com/rubygems/rubygems/pull/7687)
24
+
25
+ ## Documentation:
26
+
27
+ - Clarify BUNDLE_USER_CONFIG is a file [#7668](https://github.com/rubygems/rubygems/pull/7668)
28
+
1
29
  # 2.5.11 (May 28, 2024)
2
30
 
3
31
  ## Deprecations:
@@ -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 = "2024-05-28".freeze
8
- @git_commit_sha = "4afb2d450a".freeze
7
+ @built_at = "2024-06-14".freeze
8
+ @git_commit_sha = "5525a3d9b0".freeze
9
9
  @release = true
10
10
  # end ivars
11
11
 
data/lib/bundler/cli.rb CHANGED
@@ -65,7 +65,7 @@ module Bundler
65
65
  Bundler.reset_settings_and_root!
66
66
  end
67
67
 
68
- Bundler.self_manager.restart_with_locked_bundler_if_needed
68
+ Bundler.auto_switch
69
69
 
70
70
  Bundler.settings.set_command_option_if_given :retry, options[:retry]
71
71
 
@@ -767,13 +767,10 @@ module Bundler
767
767
 
768
768
  return unless SharedHelpers.md5_available?
769
769
 
770
- latest = Fetcher::CompactIndex.
771
- new(nil, Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org")), nil, nil).
772
- send(:compact_index_client).
773
- instance_variable_get(:@cache).
774
- dependencies("bundler").
775
- map {|d| Gem::Version.new(d.first) }.
776
- max
770
+ require_relative "vendored_uri"
771
+ remote = Source::Rubygems::Remote.new(Gem::URI("https://rubygems.org"))
772
+ cache_path = Bundler.user_cache.join("compact_index", remote.cache_slug)
773
+ latest = Bundler::CompactIndexClient.new(cache_path).latest_version("bundler")
777
774
  return unless latest
778
775
 
779
776
  current = Gem::Version.new(VERSION)
@@ -7,123 +7,89 @@ module Bundler
7
7
  class Cache
8
8
  attr_reader :directory
9
9
 
10
- def initialize(directory)
10
+ def initialize(directory, fetcher = nil)
11
11
  @directory = Pathname.new(directory).expand_path
12
- info_roots.each {|dir| mkdir(dir) }
13
- mkdir(info_etag_root)
12
+ @updater = Updater.new(fetcher) if fetcher
13
+ @mutex = Thread::Mutex.new
14
+ @endpoints = Set.new
15
+
16
+ @info_root = mkdir("info")
17
+ @special_characters_info_root = mkdir("info-special-characters")
18
+ @info_etag_root = mkdir("info-etags")
14
19
  end
15
20
 
16
21
  def names
17
- lines(names_path)
22
+ fetch("names", names_path, names_etag_path)
18
23
  end
19
24
 
20
- def names_path
21
- directory.join("names")
25
+ def versions
26
+ fetch("versions", versions_path, versions_etag_path)
22
27
  end
23
28
 
24
- def names_etag_path
25
- directory.join("names.etag")
26
- end
29
+ def info(name, remote_checksum = nil)
30
+ path = info_path(name)
27
31
 
28
- def versions
29
- versions_by_name = Hash.new {|hash, key| hash[key] = [] }
30
- info_checksums_by_name = {}
31
-
32
- lines(versions_path).each do |line|
33
- name, versions_string, info_checksum = line.split(" ", 3)
34
- info_checksums_by_name[name] = info_checksum || ""
35
- versions_string.split(",") do |version|
36
- delete = version.delete_prefix!("-")
37
- version = version.split("-", 2).unshift(name)
38
- if delete
39
- versions_by_name[name].delete(version)
40
- else
41
- versions_by_name[name] << version
42
- end
43
- end
32
+ if remote_checksum && remote_checksum != SharedHelpers.checksum_for_file(path, :MD5)
33
+ fetch("info/#{name}", path, info_etag_path(name))
34
+ else
35
+ Bundler::CompactIndexClient.debug { "update skipped info/#{name} (#{remote_checksum ? "versions index checksum is nil" : "versions index checksum matches local"})" }
36
+ read(path)
44
37
  end
45
-
46
- [versions_by_name, info_checksums_by_name]
47
- end
48
-
49
- def versions_path
50
- directory.join("versions")
51
38
  end
52
39
 
53
- def versions_etag_path
54
- directory.join("versions.etag")
40
+ def reset!
41
+ @mutex.synchronize { @endpoints.clear }
55
42
  end
56
43
 
57
- def checksums
58
- lines(versions_path).each_with_object({}) do |line, checksums|
59
- parse_version_checksum(line, checksums)
60
- end
61
- end
44
+ private
62
45
 
63
- def dependencies(name)
64
- lines(info_path(name)).map do |line|
65
- parse_gem(line)
66
- end
67
- end
46
+ def names_path = directory.join("names")
47
+ def names_etag_path = directory.join("names.etag")
48
+ def versions_path = directory.join("versions")
49
+ def versions_etag_path = directory.join("versions.etag")
68
50
 
69
51
  def info_path(name)
70
52
  name = name.to_s
53
+ # TODO: converge this into the info_root by hashing all filenames like info_etag_path
71
54
  if /[^a-z0-9_-]/.match?(name)
72
55
  name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}"
73
- info_roots.last.join(name)
56
+ @special_characters_info_root.join(name)
74
57
  else
75
- info_roots.first.join(name)
58
+ @info_root.join(name)
76
59
  end
77
60
  end
78
61
 
79
62
  def info_etag_path(name)
80
63
  name = name.to_s
81
- info_etag_root.join("#{name}-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}")
64
+ @info_etag_root.join("#{name}-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}")
82
65
  end
83
66
 
84
- private
85
-
86
- def mkdir(dir)
87
- SharedHelpers.filesystem_access(dir) do
88
- FileUtils.mkdir_p(dir)
67
+ def mkdir(name)
68
+ directory.join(name).tap do |dir|
69
+ SharedHelpers.filesystem_access(dir) do
70
+ FileUtils.mkdir_p(dir)
71
+ end
89
72
  end
90
73
  end
91
74
 
92
- def lines(path)
93
- return [] unless path.file?
94
- lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n")
95
- header = lines.index("---")
96
- header ? lines[header + 1..-1] : lines
97
- end
98
-
99
- def parse_gem(line)
100
- @dependency_parser ||= GemParser.new
101
- @dependency_parser.parse(line)
102
- end
75
+ def fetch(remote_path, path, etag_path)
76
+ if already_fetched?(remote_path)
77
+ Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
78
+ else
79
+ Bundler::CompactIndexClient.debug { "fetching #{remote_path}" }
80
+ @updater&.update(remote_path, path, etag_path)
81
+ end
103
82
 
104
- # This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects.
105
- # This method gets called at least once for every gem when parsing versions.
106
- def parse_version_checksum(line, checksums)
107
- line.freeze # allows slicing into the string to not allocate a copy of the line
108
- name_end = line.index(" ")
109
- checksum_start = line.index(" ", name_end + 1) + 1
110
- checksum_end = line.size - checksum_start
111
- # freeze name since it is used as a hash key
112
- # pre-freezing means a frozen copy isn't created
113
- name = line[0, name_end].freeze
114
- checksum = line[checksum_start, checksum_end]
115
- checksums[name] = checksum
83
+ read(path)
116
84
  end
117
85
 
118
- def info_roots
119
- [
120
- directory.join("info"),
121
- directory.join("info-special-characters"),
122
- ]
86
+ def already_fetched?(remote_path)
87
+ @mutex.synchronize { !@endpoints.add?(remote_path) }
123
88
  end
124
89
 
125
- def info_etag_root
126
- directory.join("info-etags")
90
+ def read(path)
91
+ return unless path.file?
92
+ SharedHelpers.filesystem_access(path, :read, &:read)
127
93
  end
128
94
  end
129
95
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ class CompactIndexClient
5
+ class Parser
6
+ # `compact_index` - an object responding to #names, #versions, #info(name, checksum),
7
+ # returning the file contents as a string
8
+ def initialize(compact_index)
9
+ @compact_index = compact_index
10
+ @info_checksums = nil
11
+ @versions_by_name = nil
12
+ @available = nil
13
+ @gem_parser = nil
14
+ end
15
+
16
+ def names
17
+ lines(@compact_index.names)
18
+ end
19
+
20
+ def versions
21
+ @versions_by_name ||= Hash.new {|hash, key| hash[key] = [] }
22
+ @info_checksums = {}
23
+
24
+ lines(@compact_index.versions).each do |line|
25
+ name, versions_string, checksum = line.split(" ", 3)
26
+ @info_checksums[name] = checksum || ""
27
+ versions_string.split(",") do |version|
28
+ delete = version.delete_prefix!("-")
29
+ version = version.split("-", 2).unshift(name)
30
+ if delete
31
+ @versions_by_name[name].delete(version)
32
+ else
33
+ @versions_by_name[name] << version
34
+ end
35
+ end
36
+ end
37
+
38
+ @versions_by_name
39
+ end
40
+
41
+ def info(name)
42
+ data = @compact_index.info(name, info_checksums[name])
43
+ lines(data).map {|line| gem_parser.parse(line).unshift(name) }
44
+ end
45
+
46
+ def available?
47
+ return @available unless @available.nil?
48
+ @available = !info_checksums.empty?
49
+ end
50
+
51
+ private
52
+
53
+ def info_checksums
54
+ @info_checksums ||= lines(@compact_index.versions).each_with_object({}) do |line, checksums|
55
+ parse_version_checksum(line, checksums)
56
+ end
57
+ end
58
+
59
+ def lines(data)
60
+ return [] if data.nil? || data.empty?
61
+ lines = data.split("\n")
62
+ header = lines.index("---")
63
+ header ? lines[header + 1..-1] : lines
64
+ end
65
+
66
+ def gem_parser
67
+ @gem_parser ||= GemParser.new
68
+ end
69
+
70
+ # This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects.
71
+ # This method gets called at least once for every gem when parsing versions.
72
+ def parse_version_checksum(line, checksums)
73
+ return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactor index files
74
+ return unless (checksum_start = line.index(" ", name_end + 1) + 1)
75
+ checksum_end = line.size - checksum_start
76
+
77
+ line.freeze # allows slicing into the string to not allocate a copy of the line
78
+ name = line[0, name_end]
79
+ checksum = line[checksum_start, checksum_end]
80
+ checksums[name.freeze] = checksum # freeze name since it is used as a hash key
81
+ end
82
+ end
83
+ end
84
+ end
@@ -4,6 +4,29 @@ require "pathname"
4
4
  require "set"
5
5
 
6
6
  module Bundler
7
+ # The CompactIndexClient is responsible for fetching and parsing the compact index.
8
+ #
9
+ # The compact index is a set of caching optimized files that are used to fetch gem information.
10
+ # The files are:
11
+ # - names: a list of all gem names
12
+ # - versions: a list of all gem versions
13
+ # - info/[gem]: a list of all versions of a gem
14
+ #
15
+ # The client is instantiated with:
16
+ # - `directory`: the root directory where the cache files are stored.
17
+ # - `fetcher`: (optional) an object that responds to #call(uri_path, headers) and returns an http response.
18
+ # If the `fetcher` is not provided, the client will only read cached files from disk.
19
+ #
20
+ # The client is organized into:
21
+ # - `Updater`: updates the cached files on disk using the fetcher.
22
+ # - `Cache`: calls the updater, caches files, read and return them from disk
23
+ # - `Parser`: parses the compact index file data
24
+ # - `CacheFile`: a concurrency safe file reader/writer that verifies checksums
25
+ #
26
+ # The client is intended to optimize memory usage and performance.
27
+ # It is called 100s or 1000s of times, parsing files with hundreds of thousands of lines.
28
+ # It may be called concurrently without global interpreter lock in some Rubies.
29
+ # As a result, some methods may look more complex than necessary to save memory or time.
7
30
  class CompactIndexClient
8
31
  # NOTE: MD5 is here not because we expect a server to respond with it, but
9
32
  # because we use it to generate the etag on first request during the upgrade
@@ -12,6 +35,13 @@ module Bundler
12
35
  SUPPORTED_DIGESTS = { "sha-256" => :SHA256, "md5" => :MD5 }.freeze
13
36
  DEBUG_MUTEX = Thread::Mutex.new
14
37
 
38
+ # info returns an Array of INFO Arrays. Each INFO Array has the following indices:
39
+ INFO_NAME = 0
40
+ INFO_VERSION = 1
41
+ INFO_PLATFORM = 2
42
+ INFO_DEPS = 3
43
+ INFO_REQS = 4
44
+
15
45
  def self.debug
16
46
  return unless ENV["DEBUG_COMPACT_INDEX"]
17
47
  DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
@@ -21,106 +51,47 @@ module Bundler
21
51
 
22
52
  require_relative "compact_index_client/cache"
23
53
  require_relative "compact_index_client/cache_file"
54
+ require_relative "compact_index_client/parser"
24
55
  require_relative "compact_index_client/updater"
25
56
 
26
- attr_reader :directory
27
-
28
- def initialize(directory, fetcher)
29
- @directory = Pathname.new(directory)
30
- @updater = Updater.new(fetcher)
31
- @cache = Cache.new(@directory)
32
- @endpoints = Set.new
33
- @info_checksums_by_name = {}
34
- @parsed_checksums = false
35
- @mutex = Thread::Mutex.new
36
- end
37
-
38
- def execution_mode=(block)
39
- Bundler::CompactIndexClient.debug { "execution_mode=" }
40
- @endpoints = Set.new
41
-
42
- @execution_mode = block
43
- end
44
-
45
- # @return [Lambda] A lambda that takes an array of inputs and a block, and
46
- # maps the inputs with the block in parallel.
47
- #
48
- def execution_mode
49
- @execution_mode || sequentially
50
- end
51
-
52
- def sequential_execution_mode!
53
- self.execution_mode = sequentially
54
- end
55
-
56
- def sequentially
57
- @sequentially ||= lambda do |inputs, &blk|
58
- inputs.map(&blk)
59
- end
57
+ def initialize(directory, fetcher = nil)
58
+ @cache = Cache.new(directory, fetcher)
59
+ @parser = Parser.new(@cache)
60
60
  end
61
61
 
62
62
  def names
63
- Bundler::CompactIndexClient.debug { "/names" }
64
- update("names", @cache.names_path, @cache.names_etag_path)
65
- @cache.names
63
+ Bundler::CompactIndexClient.debug { "names" }
64
+ @parser.names
66
65
  end
67
66
 
68
67
  def versions
69
- Bundler::CompactIndexClient.debug { "/versions" }
70
- update("versions", @cache.versions_path, @cache.versions_etag_path)
71
- versions, @info_checksums_by_name = @cache.versions
72
- versions
68
+ Bundler::CompactIndexClient.debug { "versions" }
69
+ @parser.versions
73
70
  end
74
71
 
75
72
  def dependencies(names)
76
73
  Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
77
- execution_mode.call(names) do |name|
78
- update_info(name)
79
- @cache.dependencies(name).map {|d| d.unshift(name) }
80
- end.flatten(1)
74
+ names.map {|name| info(name) }
81
75
  end
82
76
 
83
- def update_and_parse_checksums!
84
- Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
85
- return @info_checksums_by_name if @parsed_checksums
86
- update("versions", @cache.versions_path, @cache.versions_etag_path)
87
- @info_checksums_by_name = @cache.checksums
88
- @parsed_checksums = true
89
- end
90
-
91
- private
92
-
93
- def update(remote_path, local_path, local_etag_path)
94
- Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
95
- unless synchronize { @endpoints.add?(remote_path) }
96
- Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
97
- return
98
- end
99
- @updater.update(url(remote_path), local_path, local_etag_path)
77
+ def info(name)
78
+ Bundler::CompactIndexClient.debug { "info(#{name})" }
79
+ @parser.info(name)
100
80
  end
101
81
 
102
- def update_info(name)
103
- Bundler::CompactIndexClient.debug { "update_info(#{name})" }
104
- path = @cache.info_path(name)
105
- unless existing = @info_checksums_by_name[name]
106
- Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
107
- return
108
- end
109
- checksum = SharedHelpers.checksum_for_file(path, :MD5)
110
- if checksum == existing
111
- Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
112
- return
113
- end
114
- Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
115
- update("info/#{name}", path, @cache.info_etag_path(name))
82
+ def latest_version(name)
83
+ Bundler::CompactIndexClient.debug { "latest_version(#{name})" }
84
+ @parser.info(name).map {|d| Gem::Version.new(d[INFO_VERSION]) }.max
116
85
  end
117
86
 
118
- def url(path)
119
- path
87
+ def available?
88
+ Bundler::CompactIndexClient.debug { "available?" }
89
+ @parser.available?
120
90
  end
121
91
 
122
- def synchronize
123
- @mutex.synchronize { yield }
92
+ def reset!
93
+ Bundler::CompactIndexClient.debug { "reset!" }
94
+ @cache.reset!
124
95
  end
125
96
  end
126
97
  end
@@ -621,9 +621,16 @@ module Bundler
621
621
  end
622
622
 
623
623
  def start_resolution
624
+ @platforms |= [local_platform]
625
+
624
626
  result = SpecSet.new(resolver.start)
625
627
 
626
628
  @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version
629
+
630
+ if most_specific_ruby_locked_platform_is_not_local_platform?
631
+ @platforms.delete(result.incomplete_for_platform?(dependencies, @current_ruby_locked_platform) ? @current_ruby_locked_platform : local_platform)
632
+ end
633
+
627
634
  @platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms?
628
635
 
629
636
  result.complete_platforms!(platforms)
@@ -650,7 +657,6 @@ module Bundler
650
657
 
651
658
  def current_ruby_platform_locked?
652
659
  return false unless generic_local_platform_is_ruby?
653
- return false if Bundler.settings[:force_ruby_platform] && !@platforms.include?(Gem::Platform::RUBY)
654
660
 
655
661
  current_platform_locked?
656
662
  end
@@ -662,11 +668,16 @@ module Bundler
662
668
  end
663
669
 
664
670
  def add_current_platform
665
- return if current_ruby_platform_locked?
671
+ @current_ruby_locked_platform = most_specific_locked_platform if current_ruby_platform_locked?
672
+ return if most_specific_ruby_locked_platform_is_not_local_platform?
666
673
 
667
674
  add_platform(local_platform)
668
675
  end
669
676
 
677
+ def most_specific_ruby_locked_platform_is_not_local_platform?
678
+ @current_ruby_locked_platform && @current_ruby_locked_platform != local_platform
679
+ end
680
+
670
681
  def change_reason
671
682
  if unlocking?
672
683
  unlock_targets = if @gems_to_unlock.any?
@@ -1050,7 +1061,6 @@ module Bundler
1050
1061
  !@originally_locked_specs.incomplete_for_platform?(dependencies, platform)
1051
1062
 
1052
1063
  remove_platform(platform)
1053
- add_current_platform if platform == Gem::Platform::RUBY
1054
1064
  end
1055
1065
  end
1056
1066
 
@@ -92,6 +92,17 @@ module Bundler
92
92
  end
93
93
  end
94
94
 
95
+ # needed for `bundle fund`
96
+ def metadata
97
+ if @remote_specification
98
+ @remote_specification.metadata
99
+ elsif _local_specification
100
+ _local_specification.metadata
101
+ else
102
+ super
103
+ end
104
+ end
105
+
95
106
  def _local_specification
96
107
  return unless @loaded_from && File.exist?(local_specification_path)
97
108
  eval(File.read(local_specification_path), nil, local_specification_path).tap do |spec|
@@ -4,8 +4,6 @@ require_relative "base"
4
4
  require_relative "../worker"
5
5
 
6
6
  module Bundler
7
- autoload :CompactIndexClient, File.expand_path("../compact_index_client", __dir__)
8
-
9
7
  class Fetcher
10
8
  class CompactIndex < Base
11
9
  def self.compact_index_request(method_name)
@@ -36,15 +34,8 @@ module Bundler
36
34
 
37
35
  until remaining_gems.empty?
38
36
  log_specs { "Looking up gems #{remaining_gems.inspect}" }
39
-
40
- deps = begin
41
- parallel_compact_index_client.dependencies(remaining_gems)
42
- rescue TooManyRequestsError
43
- @bundle_worker&.stop
44
- @bundle_worker = nil # reset it. Not sure if necessary
45
- serial_compact_index_client.dependencies(remaining_gems)
46
- end
47
- next_gems = deps.flat_map {|d| d[3].flat_map(&:first) }.uniq
37
+ deps = fetch_gem_infos(remaining_gems).flatten(1)
38
+ next_gems = deps.flat_map {|d| d[CompactIndexClient::INFO_DEPS].flat_map(&:first) }.uniq
48
39
  deps.each {|dep| gem_info << dep }
49
40
  complete_gems.concat(deps.map(&:first)).uniq!
50
41
  remaining_gems = next_gems - complete_gems
@@ -61,7 +52,7 @@ module Bundler
61
52
  return nil
62
53
  end
63
54
  # Read info file checksums out of /versions, so we can know if gems are up to date
64
- compact_index_client.update_and_parse_checksums!
55
+ compact_index_client.available?
65
56
  rescue CompactIndexClient::Updater::MismatchedChecksumError => e
66
57
  Bundler.ui.debug(e.message)
67
58
  nil
@@ -81,20 +72,20 @@ module Bundler
81
72
  end
82
73
  end
83
74
 
84
- def parallel_compact_index_client
85
- compact_index_client.execution_mode = lambda do |inputs, &blk|
86
- func = lambda {|object, _index| blk.call(object) }
87
- worker = bundle_worker(func)
88
- inputs.each {|input| worker.enq(input) }
89
- inputs.map { worker.deq }
90
- end
91
-
92
- compact_index_client
75
+ def fetch_gem_infos(names)
76
+ in_parallel(names) {|name| compact_index_client.info(name) }
77
+ rescue TooManyRequestsError # rubygems.org is rate limiting us, slow down.
78
+ @bundle_worker&.stop
79
+ @bundle_worker = nil # reset it. Not sure if necessary
80
+ compact_index_client.reset!
81
+ names.map {|name| compact_index_client.info(name) }
93
82
  end
94
83
 
95
- def serial_compact_index_client
96
- compact_index_client.sequential_execution_mode!
97
- compact_index_client
84
+ def in_parallel(inputs, &blk)
85
+ func = lambda {|object, _index| blk.call(object) }
86
+ worker = bundle_worker(func)
87
+ inputs.each {|input| worker.enq(input) }
88
+ inputs.map { worker.deq }
98
89
  end
99
90
 
100
91
  def bundle_worker(func = nil)
@@ -54,7 +54,6 @@ module Bundler
54
54
  spec.source.install(
55
55
  spec,
56
56
  force: force,
57
- ensure_builtin_gems_cached: standalone,
58
57
  build_args: Array(spec_settings),
59
58
  previous_spec: previous_spec,
60
59
  )
@@ -307,7 +307,7 @@ Any \fB\.\fR characters in a host name are mapped to a double underscore (\fB__\
307
307
  .P
308
308
  This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you'll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\.
309
309
  .SH "CONFIGURE BUNDLER DIRECTORIES"
310
- Bundler's home, config, cache and plugin directories are able to be configured through environment variables\. The default location for Bundler's home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values
310
+ Bundler's home, cache and plugin directories and config file can be configured through environment variables\. The default location for Bundler's home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values
311
311
  .IP "" 4
312
312
  .nf
313
313
  BUNDLE_USER_HOME : $HOME/\.bundle
@@ -397,7 +397,7 @@ through ENV.
397
397
 
398
398
  ## CONFIGURE BUNDLER DIRECTORIES
399
399
 
400
- Bundler's home, config, cache and plugin directories are able to be configured
400
+ Bundler's home, cache and plugin directories and config file can be configured
401
401
  through environment variables. The default location for Bundler's home directory is
402
402
  `~/.bundle`, which all directories inherit from by default. The following
403
403
  outlines the available environment variables and their default values
@@ -29,7 +29,10 @@ module Bundler
29
29
  write_build_info_file
30
30
  run_post_build_hooks
31
31
 
32
- generate_bin
32
+ SharedHelpers.filesystem_access(bin_dir, :write) do
33
+ generate_bin
34
+ end
35
+
33
36
  generate_plugins
34
37
 
35
38
  write_spec
@@ -469,11 +469,25 @@ module Bundler
469
469
  end
470
470
 
471
471
  def all_specs
472
+ SharedHelpers.major_deprecation 2, "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs"
473
+
472
474
  Gem::Specification.stubs.map do |stub|
473
475
  StubSpecification.from_stub(stub)
474
476
  end
475
477
  end
476
478
 
479
+ def installed_specs
480
+ Gem::Specification.stubs.reject(&:default_gem?).map do |stub|
481
+ StubSpecification.from_stub(stub)
482
+ end
483
+ end
484
+
485
+ def default_specs
486
+ Gem::Specification.default_stubs.map do |stub|
487
+ StubSpecification.from_stub(stub)
488
+ end
489
+ end
490
+
477
491
  def find_bundler(version)
478
492
  find_name("bundler").find {|s| s.version.to_s == version }
479
493
  end
@@ -92,6 +92,7 @@ module Bundler
92
92
  def autoswitching_applies?
93
93
  ENV["BUNDLER_VERSION"].nil? &&
94
94
  Bundler.rubygems.supports_bundler_trampolining? &&
95
+ ruby_can_restart_with_same_arguments? &&
95
96
  SharedHelpers.in_bundle? &&
96
97
  lockfile_version
97
98
  end
@@ -151,6 +152,10 @@ module Bundler
151
152
  !version.to_s.end_with?(".dev")
152
153
  end
153
154
 
155
+ def ruby_can_restart_with_same_arguments?
156
+ $PROGRAM_NAME != "-e"
157
+ end
158
+
154
159
  def updating?
155
160
  "update".start_with?(ARGV.first || " ") && ARGV[1..-1].any? {|a| a.start_with?("--bundler") }
156
161
  end
@@ -103,6 +103,7 @@ module Bundler
103
103
  def initialize(root = nil)
104
104
  @root = root
105
105
  @local_config = load_config(local_config_file)
106
+ @local_root = root || Pathname.new(".bundle").expand_path
106
107
 
107
108
  @env_config = ENV.to_h
108
109
  @env_config.select! {|key, _value| key.start_with?("BUNDLE_") }
@@ -142,7 +143,7 @@ module Bundler
142
143
  end
143
144
 
144
145
  def set_local(key, value)
145
- local_config_file || raise(GemfileNotFound, "Could not locate Gemfile")
146
+ local_config_file = @local_root.join("config")
146
147
 
147
148
  set_key(key, value, @local_config, local_config_file)
148
149
  end
@@ -491,6 +492,10 @@ module Bundler
491
492
  valid_file = file.exist? && !file.size.zero?
492
493
  return {} unless valid_file
493
494
  serializer_class.load(file.read).inject({}) do |config, (k, v)|
495
+ k = k.dup
496
+ k << "/" if /https?:/i.match?(k) && !k.end_with?("/", "__#{FALLBACK_TIMEOUT_URI_OPTION.upcase}")
497
+ k.gsub!(".", "__")
498
+
494
499
  unless k.start_with?("#")
495
500
  if k.include?("-")
496
501
  Bundler.ui.warn "Your #{file} config includes `#{k}`, which contains the dash character (`-`).\n" \
@@ -518,26 +523,25 @@ module Bundler
518
523
  YAMLSerializer
519
524
  end
520
525
 
521
- PER_URI_OPTIONS = %w[
522
- fallback_timeout
523
- ].freeze
526
+ FALLBACK_TIMEOUT_URI_OPTION = "fallback_timeout"
524
527
 
525
528
  NORMALIZE_URI_OPTIONS_PATTERN =
526
529
  /
527
530
  \A
528
531
  (\w+\.)? # optional prefix key
529
532
  (https?.*?) # URI
530
- (\.#{Regexp.union(PER_URI_OPTIONS)})? # optional suffix key
533
+ (\.#{FALLBACK_TIMEOUT_URI_OPTION})? # optional suffix key
531
534
  \z
532
535
  /ix
533
536
 
534
537
  def self.key_for(key)
535
- key = normalize_uri(key).to_s if key.is_a?(String) && key.start_with?("http", "mirror.http")
536
- key = key_to_s(key).gsub(".", "__")
538
+ key = key_to_s(key)
539
+ key = normalize_uri(key) if key.start_with?("http", "mirror.http")
540
+ key = key.gsub(".", "__")
537
541
  key.gsub!("-", "___")
538
542
  key.upcase!
539
543
 
540
- key.prepend("BUNDLE_")
544
+ key.gsub(/\A([ #]*)/, '\1BUNDLE_')
541
545
  end
542
546
 
543
547
  # TODO: duplicates Rubygems#normalize_uri
data/lib/bundler/setup.rb CHANGED
@@ -5,6 +5,9 @@ require_relative "shared_helpers"
5
5
  if Bundler::SharedHelpers.in_bundle?
6
6
  require_relative "../bundler"
7
7
 
8
+ # autoswitch to locked Bundler version if available
9
+ Bundler.auto_switch
10
+
8
11
  # try to auto_install first before we get to the `Bundler.ui.silence`, so user knows what is happening
9
12
  Bundler.auto_install
10
13
 
@@ -4,14 +4,14 @@ require_relative "version"
4
4
  require_relative "rubygems_integration"
5
5
  require_relative "current_ruby"
6
6
 
7
+ autoload :Pathname, "pathname"
8
+
7
9
  module Bundler
8
10
  autoload :WINDOWS, File.expand_path("constants", __dir__)
9
11
  autoload :FREEBSD, File.expand_path("constants", __dir__)
10
12
  autoload :NULL, File.expand_path("constants", __dir__)
11
13
 
12
14
  module SharedHelpers
13
- autoload :Pathname, "pathname"
14
-
15
15
  def root
16
16
  gemfile = find_gemfile
17
17
  raise GemfileNotFound, "Could not locate Gemfile" unless gemfile
@@ -32,6 +32,20 @@ module Bundler
32
32
  @local = false
33
33
  end
34
34
 
35
+ def remote!
36
+ return if @allow_remote
37
+
38
+ @local_specs = nil
39
+ @allow_remote = true
40
+ end
41
+
42
+ def cached!
43
+ return if @allow_cached
44
+
45
+ @local_specs = nil
46
+ @allow_cached = true
47
+ end
48
+
35
49
  def self.from_lock(options)
36
50
  new(options.merge("uri" => options.delete("remote")))
37
51
  end
@@ -18,9 +18,6 @@ module Bundler
18
18
  @options = options.dup
19
19
  @glob = options["glob"] || DEFAULT_GLOB
20
20
 
21
- @allow_cached = false
22
- @allow_remote = false
23
-
24
21
  @root_path = options["root_path"] || root
25
22
 
26
23
  if options["path"]
@@ -41,16 +38,6 @@ module Bundler
41
38
  @original_path = @path
42
39
  end
43
40
 
44
- def remote!
45
- @local_specs = nil
46
- @allow_remote = true
47
- end
48
-
49
- def cached!
50
- @local_specs = nil
51
- @allow_cached = true
52
- end
53
-
54
41
  def self.from_lock(options)
55
42
  new(options.merge("path" => options.delete("remote")))
56
43
  end
@@ -10,7 +10,7 @@ module Bundler
10
10
  # Ask for X gems per API request
11
11
  API_REQUEST_SIZE = 50
12
12
 
13
- attr_accessor :remotes
13
+ attr_reader :remotes
14
14
 
15
15
  def initialize(options = {})
16
16
  @options = options
@@ -20,6 +20,7 @@ module Bundler
20
20
  @allow_cached = false
21
21
  @allow_local = options["allow_local"] || false
22
22
  @checksum_store = Checksum::Store.new
23
+ @original_remotes = nil
23
24
 
24
25
  Array(options["remotes"]).reverse_each {|r| add_remote(r) }
25
26
  end
@@ -94,10 +95,15 @@ module Bundler
94
95
  new(options)
95
96
  end
96
97
 
98
+ def remotes=(new_remotes)
99
+ @original_remotes = @remotes
100
+ @remotes = new_remotes
101
+ end
102
+
97
103
  def to_lock
98
104
  out = String.new("GEM\n")
99
- remotes.reverse_each do |remote|
100
- out << " remote: #{remove_auth remote}\n"
105
+ lockfile_remotes.reverse_each do |remote|
106
+ out << " remote: #{remote}\n"
101
107
  end
102
108
  out << " specs:\n"
103
109
  end
@@ -136,20 +142,17 @@ module Bundler
136
142
  index = @allow_remote ? remote_specs.dup : Index.new
137
143
  index.merge!(cached_specs) if @allow_cached
138
144
  index.merge!(installed_specs) if @allow_local
145
+
146
+ # complete with default specs, only if not already available in the
147
+ # index through remote, cached, or installed specs
148
+ index.use(default_specs) if @allow_local
149
+
139
150
  index
140
151
  end
141
152
  end
142
153
 
143
154
  def install(spec, options = {})
144
- force = options[:force]
145
- ensure_builtin_gems_cached = options[:ensure_builtin_gems_cached]
146
-
147
- if ensure_builtin_gems_cached && spec.default_gem? && !cached_path(spec)
148
- cached_built_in_gem(spec) unless spec.remote
149
- force = true
150
- end
151
-
152
- if installed?(spec) && !force
155
+ if (spec.default_gem? && !cached_built_in_gem(spec)) || (installed?(spec) && !options[:force])
153
156
  print_using_message "Using #{version_message(spec, options[:previous_spec])}"
154
157
  return nil # no post-install message
155
158
  end
@@ -362,7 +365,7 @@ module Bundler
362
365
 
363
366
  def installed_specs
364
367
  @installed_specs ||= Index.build do |idx|
365
- Bundler.rubygems.all_specs.reverse_each do |spec|
368
+ Bundler.rubygems.installed_specs.reverse_each do |spec|
366
369
  spec.source = self
367
370
  if Bundler.rubygems.spec_missing_extensions?(spec, false)
368
371
  Bundler.ui.debug "Source #{self} is ignoring #{spec} because it is missing extensions"
@@ -373,6 +376,15 @@ module Bundler
373
376
  end
374
377
  end
375
378
 
379
+ def default_specs
380
+ @default_specs ||= Index.build do |idx|
381
+ Bundler.rubygems.default_specs.each do |spec|
382
+ spec.source = self
383
+ idx << spec
384
+ end
385
+ end
386
+ end
387
+
376
388
  def cached_specs
377
389
  @cached_specs ||= begin
378
390
  idx = Index.new
@@ -457,6 +469,10 @@ module Bundler
457
469
 
458
470
  private
459
471
 
472
+ def lockfile_remotes
473
+ @original_remotes || credless_remotes
474
+ end
475
+
460
476
  # Checks if the requested spec exists in the global cache. If it does,
461
477
  # we copy it to the download path, and if it does not, we download it.
462
478
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  module Bundler
4
- VERSION = "2.5.11".freeze
4
+ VERSION = "2.5.13".freeze
5
5
 
6
6
  def self.bundler_major_version
7
7
  @bundler_major_version ||= VERSION.split(".").first.to_i
@@ -60,7 +60,6 @@ module Bundler
60
60
  indent, key, quote, val = match.captures
61
61
  val = strip_comment(val)
62
62
 
63
- convert_to_backward_compatible_key!(key)
64
63
  depth = indent.size / 2
65
64
  if quote.empty? && val.empty?
66
65
  new_hash = {}
@@ -92,14 +91,8 @@ module Bundler
92
91
  end
93
92
  end
94
93
 
95
- # for settings' keys
96
- def convert_to_backward_compatible_key!(key)
97
- key << "/" if /https?:/i.match?(key) && !%r{/\Z}.match?(key)
98
- key.gsub!(".", "__")
99
- end
100
-
101
94
  class << self
102
- private :dump_hash, :convert_to_backward_compatible_key!
95
+ private :dump_hash
103
96
  end
104
97
  end
105
98
  end
data/lib/bundler.rb CHANGED
@@ -42,6 +42,7 @@ module Bundler
42
42
  autoload :Checksum, File.expand_path("bundler/checksum", __dir__)
43
43
  autoload :CLI, File.expand_path("bundler/cli", __dir__)
44
44
  autoload :CIDetector, File.expand_path("bundler/ci_detector", __dir__)
45
+ autoload :CompactIndexClient, File.expand_path("bundler/compact_index_client", __dir__)
45
46
  autoload :Definition, File.expand_path("bundler/definition", __dir__)
46
47
  autoload :Dependency, File.expand_path("bundler/dependency", __dir__)
47
48
  autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__)
@@ -166,6 +167,10 @@ module Bundler
166
167
  end
167
168
  end
168
169
 
170
+ def auto_switch
171
+ self_manager.restart_with_locked_bundler_if_needed
172
+ end
173
+
169
174
  # Automatically install dependencies if Bundler.settings[:auto_install] exists.
170
175
  # This is set through config cmd `bundle config set --global auto_install 1`.
171
176
  #
@@ -356,7 +361,7 @@ module Bundler
356
361
  def settings
357
362
  @settings ||= Settings.new(app_config_path)
358
363
  rescue GemfileNotFound
359
- @settings = Settings.new(Pathname.new(".bundle").expand_path)
364
+ @settings = Settings.new
360
365
  end
361
366
 
362
367
  # @return [Hash] Environment present before Bundler was activated
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.11
4
+ version: 2.5.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Arko
@@ -22,7 +22,7 @@ authors:
22
22
  autorequire:
23
23
  bindir: exe
24
24
  cert_chain: []
25
- date: 2024-05-28 00:00:00.000000000 Z
25
+ date: 2024-06-14 00:00:00.000000000 Z
26
26
  dependencies: []
27
27
  description: Bundler manages an application's dependencies through its entire life,
28
28
  across many machines, systematically and repeatably
@@ -79,6 +79,7 @@ files:
79
79
  - lib/bundler/compact_index_client/cache.rb
80
80
  - lib/bundler/compact_index_client/cache_file.rb
81
81
  - lib/bundler/compact_index_client/gem_parser.rb
82
+ - lib/bundler/compact_index_client/parser.rb
82
83
  - lib/bundler/compact_index_client/updater.rb
83
84
  - lib/bundler/constants.rb
84
85
  - lib/bundler/current_ruby.rb
@@ -399,7 +400,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
399
400
  - !ruby/object:Gem::Version
400
401
  version: 3.2.3
401
402
  requirements: []
402
- rubygems_version: 3.5.11
403
+ rubygems_version: 3.5.13
403
404
  signing_key:
404
405
  specification_version: 4
405
406
  summary: The best way to manage your application's dependencies