bundler 4.0.9 → 4.0.18
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 +4 -4
- data/CHANGELOG.md +147 -0
- data/bundler.gemspec +2 -2
- data/lib/bundler/build_metadata.rb +1 -1
- data/lib/bundler/cli/add.rb +3 -0
- data/lib/bundler/cli/check.rb +1 -3
- data/lib/bundler/cli/common.rb +6 -0
- data/lib/bundler/cli/config.rb +8 -3
- data/lib/bundler/cli/gem.rb +2 -3
- data/lib/bundler/cli/install.rb +3 -0
- data/lib/bundler/cli/issue.rb +2 -2
- data/lib/bundler/cli/lock.rb +3 -0
- data/lib/bundler/cli/open.rb +12 -2
- data/lib/bundler/cli/outdated.rb +42 -2
- data/lib/bundler/cli/update.rb +2 -0
- data/lib/bundler/cli.rb +18 -12
- data/lib/bundler/compact_index_client/cache.rb +11 -0
- data/lib/bundler/compact_index_client/cache_file.rb +1 -1
- data/lib/bundler/compact_index_client/parser.rb +4 -1
- data/lib/bundler/definition.rb +51 -12
- data/lib/bundler/dsl.rb +10 -3
- data/lib/bundler/endpoint_specification.rb +17 -2
- data/lib/bundler/fetcher/base.rb +3 -0
- data/lib/bundler/fetcher/compact_index.rb +13 -0
- data/lib/bundler/fetcher/gem_remote_fetcher.rb +2 -0
- data/lib/bundler/fetcher.rb +12 -2
- data/lib/bundler/injector.rb +1 -2
- data/lib/bundler/installer/parallel_installer.rb +68 -23
- data/lib/bundler/installer.rb +12 -1
- data/lib/bundler/lockfile_generator.rb +39 -1
- data/lib/bundler/lockfile_parser.rb +23 -1
- data/lib/bundler/man/bundle-add.1 +6 -3
- data/lib/bundler/man/bundle-add.1.ronn +7 -2
- data/lib/bundler/man/bundle-binstubs.1 +1 -1
- data/lib/bundler/man/bundle-cache.1 +4 -1
- data/lib/bundler/man/bundle-cache.1.ronn +6 -0
- data/lib/bundler/man/bundle-check.1 +1 -1
- data/lib/bundler/man/bundle-clean.1 +1 -1
- data/lib/bundler/man/bundle-config.1 +121 -157
- data/lib/bundler/man/bundle-config.1.ronn +33 -1
- data/lib/bundler/man/bundle-console.1 +3 -3
- data/lib/bundler/man/bundle-console.1.ronn +2 -2
- data/lib/bundler/man/bundle-doctor.1 +1 -1
- data/lib/bundler/man/bundle-env.1 +1 -1
- data/lib/bundler/man/bundle-exec.1 +1 -1
- data/lib/bundler/man/bundle-fund.1 +1 -1
- data/lib/bundler/man/bundle-gem.1 +1 -1
- data/lib/bundler/man/bundle-help.1 +1 -1
- data/lib/bundler/man/bundle-info.1 +1 -1
- data/lib/bundler/man/bundle-init.1 +2 -2
- data/lib/bundler/man/bundle-init.1.ronn +1 -1
- data/lib/bundler/man/bundle-install.1 +5 -2
- data/lib/bundler/man/bundle-install.1.ronn +9 -1
- data/lib/bundler/man/bundle-issue.1 +1 -1
- data/lib/bundler/man/bundle-licenses.1 +1 -1
- data/lib/bundler/man/bundle-list.1 +1 -1
- data/lib/bundler/man/bundle-lock.1 +5 -2
- data/lib/bundler/man/bundle-lock.1.ronn +6 -0
- data/lib/bundler/man/bundle-open.1 +1 -1
- data/lib/bundler/man/bundle-outdated.1 +17 -14
- data/lib/bundler/man/bundle-outdated.1.ronn +19 -12
- data/lib/bundler/man/bundle-platform.1 +1 -1
- data/lib/bundler/man/bundle-plugin.1 +2 -2
- data/lib/bundler/man/bundle-plugin.1.ronn +1 -1
- data/lib/bundler/man/bundle-pristine.1 +1 -1
- data/lib/bundler/man/bundle-remove.1 +1 -1
- data/lib/bundler/man/bundle-show.1 +1 -1
- data/lib/bundler/man/bundle-update.1 +5 -2
- data/lib/bundler/man/bundle-update.1.ronn +8 -0
- data/lib/bundler/man/bundle-version.1 +1 -1
- data/lib/bundler/man/bundle.1 +23 -2
- data/lib/bundler/man/bundle.1.ronn +22 -1
- data/lib/bundler/man/gemfile.5 +13 -2
- data/lib/bundler/man/gemfile.5.ronn +20 -1
- data/lib/bundler/mirror.rb +7 -7
- data/lib/bundler/plugin/unloaded_source.rb +25 -0
- data/lib/bundler/plugin.rb +12 -6
- data/lib/bundler/remote_specification.rb +1 -1
- data/lib/bundler/resolver/base.rb +9 -0
- data/lib/bundler/resolver/strategy.rb +6 -3
- data/lib/bundler/resolver.rb +80 -1
- data/lib/bundler/rubygems_ext.rb +22 -0
- data/lib/bundler/rubygems_gem_installer.rb +35 -2
- data/lib/bundler/runtime.rb +11 -11
- data/lib/bundler/self_manager.rb +6 -1
- data/lib/bundler/settings/validator.rb +7 -0
- data/lib/bundler/settings.rb +2 -0
- data/lib/bundler/shared_helpers.rb +6 -1
- data/lib/bundler/source/git/git_proxy.rb +13 -2
- data/lib/bundler/source/git.rb +1 -1
- data/lib/bundler/source/metadata.rb +4 -0
- data/lib/bundler/source/path.rb +3 -2
- data/lib/bundler/source/rubygems/remote.rb +12 -2
- data/lib/bundler/source/rubygems.rb +81 -13
- data/lib/bundler/source_list.rb +44 -3
- data/lib/bundler/spec_set.rb +2 -2
- data/lib/bundler/stub_specification.rb +1 -0
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +10 -1
- data/lib/bundler/vendored_tsort.rb +19 -2
- data/lib/bundler/version.rb +1 -1
- data/lib/bundler.rb +11 -1
- metadata +5 -6
- data/lib/bundler/vendor/tsort/LICENSE.txt +0 -22
- data/lib/bundler/vendor/tsort/lib/tsort.rb +0 -455
data/lib/bundler/definition.rb
CHANGED
|
@@ -230,6 +230,16 @@ module Bundler
|
|
|
230
230
|
sources.prefer_local!
|
|
231
231
|
end
|
|
232
232
|
|
|
233
|
+
# Releases memory only needed during resolution, such as remote spec
|
|
234
|
+
# indexes and resolver state. Only safe to call once resolution is
|
|
235
|
+
# complete and the result has been materialized, since any further
|
|
236
|
+
# resolution will need to refetch remote specs.
|
|
237
|
+
def release_resolution_memory!
|
|
238
|
+
@resolver = nil
|
|
239
|
+
@resolution_base = nil
|
|
240
|
+
sources.release_resolution_memory!
|
|
241
|
+
end
|
|
242
|
+
|
|
233
243
|
# For given dependency list returns a SpecSet with Gemspec of all the required
|
|
234
244
|
# dependencies.
|
|
235
245
|
# 1. The method first resolves the dependencies specified in Gemfile
|
|
@@ -387,10 +397,6 @@ module Bundler
|
|
|
387
397
|
|
|
388
398
|
contents = to_lock
|
|
389
399
|
|
|
390
|
-
# Convert to \r\n if the existing lock has them
|
|
391
|
-
# i.e., Windows with `git config core.autocrlf=true`
|
|
392
|
-
contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n")
|
|
393
|
-
|
|
394
400
|
if @locked_bundler_version
|
|
395
401
|
locked_major = @locked_bundler_version.segments.first
|
|
396
402
|
current_major = bundler_version_to_lock.segments.first
|
|
@@ -411,6 +417,14 @@ module Bundler
|
|
|
411
417
|
return
|
|
412
418
|
end
|
|
413
419
|
|
|
420
|
+
# Convert to \r\n if the existing lock has them, i.e., Windows with
|
|
421
|
+
# `git config core.autocrlf=true`. Detect from the bytes on disk because
|
|
422
|
+
# reading in text mode strips carriage returns on Windows, which would
|
|
423
|
+
# otherwise defeat this check and rewrite a `\r\n` lockfile with `\n`.
|
|
424
|
+
if File.exist?(file) && SharedHelpers.filesystem_access(file, :read) {|p| File.binread(p).include?("\r\n") }
|
|
425
|
+
contents.gsub!(/\n/, "\r\n")
|
|
426
|
+
end
|
|
427
|
+
|
|
414
428
|
begin
|
|
415
429
|
SharedHelpers.filesystem_access(file) do |p|
|
|
416
430
|
File.open(p, "wb") {|f| f.puts(contents) }
|
|
@@ -688,9 +702,10 @@ module Bundler
|
|
|
688
702
|
"available locally before rerunning Bundler."
|
|
689
703
|
else
|
|
690
704
|
"Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \
|
|
691
|
-
"no longer be found in that source. That means the author of #{locked_gem} has removed it
|
|
692
|
-
"You'll need to update your bundle to a version other
|
|
693
|
-
"removed
|
|
705
|
+
"no longer be found in that source. That means either the author of #{locked_gem} has removed it, " \
|
|
706
|
+
"or you no longer have access to that source. You'll need to update your bundle to a version other " \
|
|
707
|
+
"than #{locked_gem} that hasn't been removed, or check your credentials and access rights for " \
|
|
708
|
+
"#{locked_gem.source}, in order to install."
|
|
694
709
|
end
|
|
695
710
|
|
|
696
711
|
raise GemNotFound, message
|
|
@@ -783,7 +798,25 @@ module Bundler
|
|
|
783
798
|
end
|
|
784
799
|
|
|
785
800
|
def precompute_source_requirements_for_indirect_dependencies?
|
|
786
|
-
sources.non_global_rubygems_sources.all?(&:dependency_api_available?)
|
|
801
|
+
if sources.non_global_rubygems_sources.all?(&:dependency_api_available?)
|
|
802
|
+
true
|
|
803
|
+
else
|
|
804
|
+
non_dependency_api_warning
|
|
805
|
+
false
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
def non_dependency_api_warning
|
|
810
|
+
non_api_sources = sources.non_global_rubygems_sources.reject(&:dependency_api_available?)
|
|
811
|
+
non_api_source_names = non_api_sources.map {|d| " * #{d}" }.join("\n")
|
|
812
|
+
|
|
813
|
+
msg = String.new
|
|
814
|
+
msg << "Your Gemfile contains scoped sources that don't implement a dependency API, namely:\n\n"
|
|
815
|
+
msg << non_api_source_names
|
|
816
|
+
msg << "\n\nUsing the above gem servers may result in installing unexpected gems. " \
|
|
817
|
+
"To resolve this warning, make sure you use gem servers that implement dependency APIs, " \
|
|
818
|
+
"such as gemstash or geminabox gem servers."
|
|
819
|
+
Bundler.ui.warn msg
|
|
787
820
|
end
|
|
788
821
|
|
|
789
822
|
def current_platform_locked?
|
|
@@ -988,6 +1021,8 @@ module Bundler
|
|
|
988
1021
|
end
|
|
989
1022
|
end
|
|
990
1023
|
|
|
1024
|
+
sources.metadata_source.checksum_store.merge!(@locked_gems.metadata_source.checksum_store) if @locked_gems
|
|
1025
|
+
|
|
991
1026
|
changes
|
|
992
1027
|
end
|
|
993
1028
|
|
|
@@ -1157,16 +1192,20 @@ module Bundler
|
|
|
1157
1192
|
def find_source_requirements
|
|
1158
1193
|
preload_git_sources
|
|
1159
1194
|
|
|
1195
|
+
# Only safe to exclude when locked_requirements (merged below) backfills the gap.
|
|
1196
|
+
nothing_changed = nothing_changed?
|
|
1197
|
+
excluded = nothing_changed ? excluded_git_sources : []
|
|
1198
|
+
|
|
1160
1199
|
# Record the specs available in each gem's source, so that those
|
|
1161
1200
|
# specs will be available later when the resolver knows where to
|
|
1162
1201
|
# look for that gemspec (or its dependencies)
|
|
1163
1202
|
source_requirements = if precompute_source_requirements_for_indirect_dependencies?
|
|
1164
|
-
all_requirements = source_map.all_requirements(
|
|
1203
|
+
all_requirements = source_map.all_requirements(excluded)
|
|
1165
1204
|
{ default: default_source }.merge(all_requirements)
|
|
1166
1205
|
else
|
|
1167
|
-
{ default: Source::RubygemsAggregate.new(sources, source_map,
|
|
1206
|
+
{ default: Source::RubygemsAggregate.new(sources, source_map, excluded) }.merge(source_map.direct_requirements)
|
|
1168
1207
|
end
|
|
1169
|
-
source_requirements.merge!(source_map.locked_requirements) if nothing_changed
|
|
1208
|
+
source_requirements.merge!(source_map.locked_requirements) if nothing_changed
|
|
1170
1209
|
metadata_dependencies.each do |dep|
|
|
1171
1210
|
source_requirements[dep.name] = sources.metadata_source
|
|
1172
1211
|
end
|
|
@@ -1259,7 +1298,7 @@ module Bundler
|
|
|
1259
1298
|
|
|
1260
1299
|
def new_resolution_base(last_resolve:, unlock:)
|
|
1261
1300
|
new_resolution_platforms = @current_platform_missing ? @new_platforms + [Bundler.local_platform] : @new_platforms
|
|
1262
|
-
Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms)
|
|
1301
|
+
Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms, explicit_unlocks: @explicit_unlocks)
|
|
1263
1302
|
end
|
|
1264
1303
|
|
|
1265
1304
|
def new_resolver(base)
|
data/lib/bundler/dsl.rb
CHANGED
|
@@ -116,6 +116,10 @@ module Bundler
|
|
|
116
116
|
options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
|
117
117
|
options = normalize_hash(options)
|
|
118
118
|
source = normalize_source(source)
|
|
119
|
+
cooldown = options["cooldown"]
|
|
120
|
+
if cooldown && !(cooldown.is_a?(Integer) && cooldown >= 0)
|
|
121
|
+
raise InvalidOption, "Expected `cooldown` to be a non-negative integer, got #{cooldown.inspect}"
|
|
122
|
+
end
|
|
119
123
|
|
|
120
124
|
if options.key?("type")
|
|
121
125
|
options["type"] = options["type"].to_s
|
|
@@ -130,9 +134,9 @@ module Bundler
|
|
|
130
134
|
source_opts = options.merge("uri" => source)
|
|
131
135
|
with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
|
|
132
136
|
elsif block_given?
|
|
133
|
-
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
|
|
137
|
+
with_source(@sources.add_rubygems_source("remotes" => source, "cooldown" => cooldown), &blk)
|
|
134
138
|
else
|
|
135
|
-
@sources.add_global_rubygems_remote(source)
|
|
139
|
+
@sources.add_global_rubygems_remote(source, cooldown: cooldown)
|
|
136
140
|
end
|
|
137
141
|
end
|
|
138
142
|
|
|
@@ -598,8 +602,11 @@ module Bundler
|
|
|
598
602
|
|
|
599
603
|
trace_line = backtrace.find {|l| l.include?(dsl_path) } || trace_line
|
|
600
604
|
return m unless trace_line
|
|
601
|
-
|
|
605
|
+
# Match the line number right before `:in` or the end of the line so a
|
|
606
|
+
# Windows drive letter like `C:` does not get mistaken for the number.
|
|
607
|
+
line_number = trace_line[/:(\d+)(?::in\b|\z)/, 1]
|
|
602
608
|
return m unless line_number
|
|
609
|
+
line_number = line_number.to_i - 1
|
|
603
610
|
|
|
604
611
|
lines = contents.lines.to_a
|
|
605
612
|
indent = " # "
|
|
@@ -5,7 +5,7 @@ module Bundler
|
|
|
5
5
|
class EndpointSpecification < Gem::Specification
|
|
6
6
|
include MatchRemoteMetadata
|
|
7
7
|
|
|
8
|
-
attr_reader :name, :version, :platform, :checksum
|
|
8
|
+
attr_reader :name, :version, :platform, :checksum, :created_at
|
|
9
9
|
attr_writer :dependencies
|
|
10
10
|
attr_accessor :remote, :locked_platform
|
|
11
11
|
|
|
@@ -145,6 +145,7 @@ module Bundler
|
|
|
145
145
|
unless data
|
|
146
146
|
@required_ruby_version = nil
|
|
147
147
|
@required_rubygems_version = nil
|
|
148
|
+
@created_at = nil
|
|
148
149
|
return
|
|
149
150
|
end
|
|
150
151
|
|
|
@@ -152,8 +153,13 @@ module Bundler
|
|
|
152
153
|
next unless v
|
|
153
154
|
case k.to_s
|
|
154
155
|
when "checksum"
|
|
156
|
+
# Some registries send empty checksum values, treat them as if the
|
|
157
|
+
# checksum was not included at all
|
|
158
|
+
checksum = v.last
|
|
159
|
+
next unless checksum
|
|
160
|
+
|
|
155
161
|
begin
|
|
156
|
-
@checksum = Checksum.from_api(
|
|
162
|
+
@checksum = Checksum.from_api(checksum, @spec_fetcher.uri)
|
|
157
163
|
rescue ArgumentError => e
|
|
158
164
|
raise ArgumentError, "Invalid checksum for #{full_name}: #{e.message}"
|
|
159
165
|
end
|
|
@@ -161,6 +167,15 @@ module Bundler
|
|
|
161
167
|
@required_rubygems_version = Gem::Requirement.new(v)
|
|
162
168
|
when "ruby"
|
|
163
169
|
@required_ruby_version = Gem::Requirement.new(v)
|
|
170
|
+
when "created_at"
|
|
171
|
+
value = v.is_a?(Array) ? v.last : v
|
|
172
|
+
if value.is_a?(String)
|
|
173
|
+
@created_at = begin
|
|
174
|
+
Time.new(value)
|
|
175
|
+
rescue ArgumentError
|
|
176
|
+
nil
|
|
177
|
+
end
|
|
178
|
+
end
|
|
164
179
|
end
|
|
165
180
|
end
|
|
166
181
|
rescue StandardError => e
|
data/lib/bundler/fetcher/base.rb
CHANGED
|
@@ -63,6 +63,13 @@ module Bundler
|
|
|
63
63
|
true
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
# The client holds the parsed checksums of all info files in the
|
|
67
|
+
# index. Dropping it is always safe because it is rebuilt from the
|
|
68
|
+
# local cache on demand.
|
|
69
|
+
def release_resolution_memory!
|
|
70
|
+
@compact_index_client = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
66
73
|
private
|
|
67
74
|
|
|
68
75
|
def compact_index_client
|
|
@@ -73,6 +80,12 @@ module Bundler
|
|
|
73
80
|
end
|
|
74
81
|
|
|
75
82
|
def fetch_gem_infos(names)
|
|
83
|
+
# Create the client and update the versions file on this thread.
|
|
84
|
+
# Otherwise the workers race to lazily create the client and update
|
|
85
|
+
# the versions file concurrently, e.g. when the client was released
|
|
86
|
+
# after resolution and is being rebuilt for `bundle cache`.
|
|
87
|
+
compact_index_client.available?
|
|
88
|
+
|
|
76
89
|
in_parallel(names) {|name| compact_index_client.info(name) }
|
|
77
90
|
rescue TooManyRequestsError # rubygems.org is rate limiting us, slow down.
|
|
78
91
|
@bundle_worker&.stop
|
data/lib/bundler/fetcher.rb
CHANGED
|
@@ -215,7 +215,13 @@ module Bundler
|
|
|
215
215
|
agent << " #{ruby.engine}/#{ruby.versions_string(engine_version)}"
|
|
216
216
|
end
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
# Some settings keys embed a URI (e.g. `mirror.<uri>`) whose userinfo can
|
|
219
|
+
# carry credentials. Strip the userinfo before advertising the setting
|
|
220
|
+
# names so we don't leak secrets to every gem source we talk to.
|
|
221
|
+
options = Bundler.settings.all.map do |key|
|
|
222
|
+
key.include?("://") ? key.gsub(%r{//[^/@]+@}, "//") : key
|
|
223
|
+
end
|
|
224
|
+
agent << " options/#{options.join(",")}"
|
|
219
225
|
|
|
220
226
|
agent << " ci/#{cis.join(",")}" if cis.any?
|
|
221
227
|
|
|
@@ -243,6 +249,10 @@ module Bundler
|
|
|
243
249
|
fetchers.first.api_fetcher?
|
|
244
250
|
end
|
|
245
251
|
|
|
252
|
+
def release_resolution_memory!
|
|
253
|
+
@fetchers&.each(&:release_resolution_memory!)
|
|
254
|
+
end
|
|
255
|
+
|
|
246
256
|
def gem_remote_fetcher
|
|
247
257
|
@gem_remote_fetcher ||= begin
|
|
248
258
|
require_relative "fetcher/gem_remote_fetcher"
|
|
@@ -314,7 +324,7 @@ module Bundler
|
|
|
314
324
|
if ssl_client_cert
|
|
315
325
|
pem = File.read(ssl_client_cert)
|
|
316
326
|
con.cert = OpenSSL::X509::Certificate.new(pem)
|
|
317
|
-
con.key = OpenSSL::PKey
|
|
327
|
+
con.key = OpenSSL::PKey.read(pem)
|
|
318
328
|
end
|
|
319
329
|
|
|
320
330
|
con.read_timeout = Fetcher.api_timeout
|
data/lib/bundler/injector.rb
CHANGED
|
@@ -80,11 +80,10 @@ module Bundler
|
|
|
80
80
|
def conservative_version(spec)
|
|
81
81
|
version = spec.version
|
|
82
82
|
return ">= 0" if version.nil?
|
|
83
|
-
segments = version.segments
|
|
84
83
|
seg_end_index = version >= Gem::Version.new("1.0") ? 1 : 2
|
|
85
84
|
|
|
86
85
|
prerelease_suffix = version.to_s.delete_prefix(version.release.to_s) if version.prerelease?
|
|
87
|
-
"#{version_prefix}#{segments[0..seg_end_index].join(".")}#{prerelease_suffix}"
|
|
86
|
+
"#{version_prefix}#{version.segments[0..seg_end_index].join(".")}#{prerelease_suffix}"
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
def version_prefix
|
|
@@ -6,7 +6,7 @@ require_relative "gem_installer"
|
|
|
6
6
|
module Bundler
|
|
7
7
|
class ParallelInstaller
|
|
8
8
|
class SpecInstallation
|
|
9
|
-
attr_accessor :spec, :name, :full_name, :post_install_message, :state, :error
|
|
9
|
+
attr_accessor :spec, :name, :full_name, :post_install_message, :state, :error, :dependencies
|
|
10
10
|
def initialize(spec)
|
|
11
11
|
@spec = spec
|
|
12
12
|
@name = spec.name
|
|
@@ -46,25 +46,11 @@ module Bundler
|
|
|
46
46
|
!post_install_message.empty?
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
dep.type == :development || dep.name == @name
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Checks installed dependencies against spec's dependencies to make
|
|
54
|
-
# sure needed dependencies have been installed.
|
|
49
|
+
# Recursively checks that all dependencies (direct and transitive) have been installed.
|
|
55
50
|
def dependencies_installed?(installed_specs)
|
|
56
|
-
dependencies.all?
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Represents only the non-development dependencies, the ones that are
|
|
60
|
-
# itself and are in the total list.
|
|
61
|
-
def dependencies
|
|
62
|
-
@dependencies ||= all_dependencies.reject {|dep| ignorable_dependency? dep }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Represents all dependencies
|
|
66
|
-
def all_dependencies
|
|
67
|
-
@spec.dependencies
|
|
51
|
+
dependencies.all? do |dep|
|
|
52
|
+
installed_specs.include?(dep.name) && dep.dependencies_installed?(installed_specs)
|
|
53
|
+
end
|
|
68
54
|
end
|
|
69
55
|
|
|
70
56
|
def to_s
|
|
@@ -85,6 +71,12 @@ module Bundler
|
|
|
85
71
|
@force = force
|
|
86
72
|
@local = local
|
|
87
73
|
@specs = all_specs.map {|s| SpecInstallation.new(s) }
|
|
74
|
+
specs_by_name = @specs.to_h {|s| [s.name, s] }
|
|
75
|
+
@specs.each do |spec_install|
|
|
76
|
+
spec_install.dependencies = spec_install.spec.dependencies.filter_map do |dep|
|
|
77
|
+
specs_by_name[dep.name] unless dep.type == :development || dep.name == spec_install.name
|
|
78
|
+
end
|
|
79
|
+
end
|
|
88
80
|
@specs.each do |spec_install|
|
|
89
81
|
spec_install.state = :installed if skip.include?(spec_install.name)
|
|
90
82
|
end if skip
|
|
@@ -118,10 +110,55 @@ module Bundler
|
|
|
118
110
|
end
|
|
119
111
|
|
|
120
112
|
def install_with_worker
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
with_jobserver do
|
|
114
|
+
installed_specs = {}
|
|
115
|
+
enqueue_specs(installed_specs)
|
|
123
116
|
|
|
124
|
-
|
|
117
|
+
process_specs(installed_specs) until finished_installing?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def with_jobserver
|
|
122
|
+
# The jobserver hands tokens to child `make` processes through MAKEFLAGS
|
|
123
|
+
# using the GNU make `--jobserver-auth` protocol. nmake, the default make
|
|
124
|
+
# on mswin, instead reads MAKEFLAGS as bare option letters and aborts
|
|
125
|
+
# every native extension build with `fatal error U1065: invalid option
|
|
126
|
+
# '-'`. Skip the jobserver when nmake is in use. Other Windows toolchains
|
|
127
|
+
# such as mingw use GNU make and keep working through the inherited pipe.
|
|
128
|
+
return yield if nmake? || bsd_make?
|
|
129
|
+
|
|
130
|
+
begin
|
|
131
|
+
r, w = IO.pipe
|
|
132
|
+
r.close_on_exec = false
|
|
133
|
+
w.close_on_exec = false
|
|
134
|
+
w.write("*" * @size)
|
|
135
|
+
|
|
136
|
+
old_makeflags = ENV["MAKEFLAGS"]
|
|
137
|
+
ENV["MAKEFLAGS"] = [old_makeflags, "--jobserver-auth=#{r.fileno},#{w.fileno}"].compact.join(" ")
|
|
138
|
+
|
|
139
|
+
yield
|
|
140
|
+
ensure
|
|
141
|
+
# Restore MAKEFLAGS before closing the pipe so a close failure can't
|
|
142
|
+
# leave the process with descriptors that point at a closed pipe.
|
|
143
|
+
old_makeflags ? ENV["MAKEFLAGS"] = old_makeflags : ENV.delete("MAKEFLAGS")
|
|
144
|
+
|
|
145
|
+
r&.close
|
|
146
|
+
w&.close
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Mirror how RubyGems' extension builder picks the make program so the
|
|
151
|
+
# jobserver is only set up when a GNU-compatible make will consume it.
|
|
152
|
+
def nmake?
|
|
153
|
+
make = ENV["MAKE"] || ENV["make"]
|
|
154
|
+
make ||= "nmake" if RUBY_PLATFORM.include?("mswin")
|
|
155
|
+
/\bnmake/i.match?(make.to_s)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def bsd_make?
|
|
159
|
+
return false unless Gem.freebsd_platform?
|
|
160
|
+
make = ENV["MAKE"] || ENV["make"] || "make"
|
|
161
|
+
!/\bgmake/i.match?(make)
|
|
125
162
|
end
|
|
126
163
|
|
|
127
164
|
def install_serially
|
|
@@ -218,7 +255,15 @@ module Bundler
|
|
|
218
255
|
|
|
219
256
|
def require_tree_for_spec(spec)
|
|
220
257
|
tree = @spec_set.what_required(spec)
|
|
221
|
-
|
|
258
|
+
gemfile_name = begin
|
|
259
|
+
File.basename(SharedHelpers.default_gemfile)
|
|
260
|
+
rescue GemfileNotFound
|
|
261
|
+
# This runs while reporting an install error. When no Gemfile can be
|
|
262
|
+
# located (e.g. Bundler used as a library), raising here would mask
|
|
263
|
+
# the original error, so fall back to a generic header instead.
|
|
264
|
+
"Gemfile"
|
|
265
|
+
end
|
|
266
|
+
t = String.new("In #{gemfile_name}:\n")
|
|
222
267
|
tree.each_with_index do |s, depth|
|
|
223
268
|
t << " " * depth.succ << s.name
|
|
224
269
|
unless tree.last == s
|
data/lib/bundler/installer.rb
CHANGED
|
@@ -63,6 +63,11 @@ module Bundler
|
|
|
63
63
|
Bundler.create_bundle_path
|
|
64
64
|
|
|
65
65
|
ProcessLock.lock do
|
|
66
|
+
# Invalidate any stale gem specification cache from before we acquired the lock.
|
|
67
|
+
# Another process may have installed gems while we were waiting.
|
|
68
|
+
Gem::Specification.reset
|
|
69
|
+
@definition.sources.clear_cache
|
|
70
|
+
|
|
66
71
|
@definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
|
|
67
72
|
|
|
68
73
|
if @definition.dependencies.empty?
|
|
@@ -190,7 +195,13 @@ module Bundler
|
|
|
190
195
|
force = options[:force]
|
|
191
196
|
local = options[:local] || options[:"prefer-local"]
|
|
192
197
|
jobs = Bundler.settings.installation_parallelization
|
|
193
|
-
|
|
198
|
+
specs = @definition.specs
|
|
199
|
+
# Installing default gems may need the remote index again to cache
|
|
200
|
+
# their .gem files, so keep resolution memory around in that case.
|
|
201
|
+
# The bundler spec itself is excluded because it comes from the
|
|
202
|
+
# metadata source and never goes through that path.
|
|
203
|
+
@definition.release_resolution_memory! if specs.none? {|s| s.default_gem? && s.source.is_a?(Source::Rubygems) }
|
|
204
|
+
spec_installations = ParallelInstaller.call(self, specs, jobs, standalone, force, local: local)
|
|
194
205
|
spec_installations.each do |installation|
|
|
195
206
|
post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message?
|
|
196
207
|
end
|
|
@@ -71,7 +71,8 @@ module Bundler
|
|
|
71
71
|
checksums = definition.resolve.map do |spec|
|
|
72
72
|
spec.source.checksum_store.to_lock(spec)
|
|
73
73
|
end
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
add_section("CHECKSUMS", checksums + bundler_checksum)
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
def add_locked_ruby_version
|
|
@@ -100,5 +101,42 @@ module Bundler
|
|
|
100
101
|
raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile"
|
|
101
102
|
end
|
|
102
103
|
end
|
|
104
|
+
|
|
105
|
+
def bundler_checksum
|
|
106
|
+
# `.dev` versions and `SKIP_BUNDLER_CHECKSUM` are deliberate opt-outs (used
|
|
107
|
+
# by Bundler/RubyGems' own development and release tasks): never record a
|
|
108
|
+
# checksum for Bundler itself in those cases.
|
|
109
|
+
return [] if Bundler.gem_version.to_s.end_with?(".dev") || ENV["SKIP_BUNDLER_CHECKSUM"]
|
|
110
|
+
|
|
111
|
+
bundler_spec = definition.sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last
|
|
112
|
+
|
|
113
|
+
# Record a fresh checksum from the locally cached gem when it's available.
|
|
114
|
+
# When it isn't (e.g. a fresh checkout/CI that never downloaded the bundler
|
|
115
|
+
# gem), fall back to whatever checksum is already locked rather than
|
|
116
|
+
# dropping it, so the entry stays consistent across environments.
|
|
117
|
+
if File.exist?(bundler_spec.cache_file)
|
|
118
|
+
require "rubygems/package"
|
|
119
|
+
|
|
120
|
+
package = Gem::Package.new(bundler_spec.cache_file)
|
|
121
|
+
definition.sources.metadata_source.checksum_store.register(bundler_spec, Checksum.from_gem_package(package))
|
|
122
|
+
elsif bundled_with_changing?
|
|
123
|
+
# We can't compute a fresh checksum (the bundler gem isn't cached) and the
|
|
124
|
+
# BUNDLED WITH version is changing. Keeping the previously locked checksum
|
|
125
|
+
# would leave a `bundler (<old version>) sha256=...` entry that no longer
|
|
126
|
+
# matches the new BUNDLED WITH version, so drop it instead.
|
|
127
|
+
return []
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
return [] if definition.sources.metadata_source.checksum_store.missing?(bundler_spec)
|
|
131
|
+
|
|
132
|
+
[definition.sources.metadata_source.checksum_store.to_lock(bundler_spec)]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def bundled_with_changing?
|
|
136
|
+
locked_gems = definition.locked_gems
|
|
137
|
+
return false unless locked_gems
|
|
138
|
+
|
|
139
|
+
locked_gems.bundler_version != definition.bundler_version_to_lock
|
|
140
|
+
end
|
|
103
141
|
end
|
|
104
142
|
end
|
|
@@ -28,6 +28,7 @@ module Bundler
|
|
|
28
28
|
|
|
29
29
|
attr_reader(
|
|
30
30
|
:sources,
|
|
31
|
+
:metadata_source,
|
|
31
32
|
:dependencies,
|
|
32
33
|
:specs,
|
|
33
34
|
:platforms,
|
|
@@ -97,6 +98,7 @@ module Bundler
|
|
|
97
98
|
def initialize(lockfile, strict: false)
|
|
98
99
|
@platforms = []
|
|
99
100
|
@sources = []
|
|
101
|
+
@metadata_source = Source::Metadata.new
|
|
100
102
|
@dependencies = {}
|
|
101
103
|
@parse_method = nil
|
|
102
104
|
@specs = {}
|
|
@@ -113,6 +115,17 @@ module Bundler
|
|
|
113
115
|
"Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock."
|
|
114
116
|
end
|
|
115
117
|
|
|
118
|
+
@valid = lockfile.strip.empty? ||
|
|
119
|
+
lockfile.split(/(?:\r?\n)+/).any? {|l| KNOWN_SECTIONS.include?(l) }
|
|
120
|
+
|
|
121
|
+
unless @valid
|
|
122
|
+
SharedHelpers.feature_deprecated!(
|
|
123
|
+
"Your #{@lockfile_path} does not appear to be a valid lockfile. " \
|
|
124
|
+
"Run `rm #{@lockfile_path}` and then `bundle install` to generate a new lockfile. " \
|
|
125
|
+
"This will raise a LockfileError in a future version of Bundler."
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
116
129
|
lockfile.split(/((?:\r?\n)+)/) do |line|
|
|
117
130
|
# split alternates between the line and the following whitespace
|
|
118
131
|
next @pos.advance!(line) if line.match?(/^\s*$/)
|
|
@@ -162,6 +175,10 @@ module Bundler
|
|
|
162
175
|
bundler_version.nil? || bundler_version < Gem::Version.new("1.16.2")
|
|
163
176
|
end
|
|
164
177
|
|
|
178
|
+
def valid?
|
|
179
|
+
@valid
|
|
180
|
+
end
|
|
181
|
+
|
|
165
182
|
private
|
|
166
183
|
|
|
167
184
|
TYPES = {
|
|
@@ -252,7 +269,12 @@ module Bundler
|
|
|
252
269
|
version = Gem::Version.new(version)
|
|
253
270
|
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
|
|
254
271
|
full_name = Gem::NameTuple.new(name, version, platform).full_name
|
|
255
|
-
|
|
272
|
+
spec = @specs[full_name]
|
|
273
|
+
|
|
274
|
+
if name == "bundler"
|
|
275
|
+
spec ||= LazySpecification.new(name, version, platform, @metadata_source)
|
|
276
|
+
end
|
|
277
|
+
return unless spec
|
|
256
278
|
|
|
257
279
|
if checksums
|
|
258
280
|
checksums.split(",") do |lock_checksum|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
.\" generated with Ronn-NG/v0.10.1
|
|
2
2
|
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
|
3
|
-
.TH "BUNDLE\-ADD" "1" "
|
|
3
|
+
.TH "BUNDLE\-ADD" "1" "April 2026" ""
|
|
4
4
|
.SH "NAME"
|
|
5
5
|
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
|
|
6
6
|
.SH "SYNOPSIS"
|
|
7
|
-
\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT|\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-quiet] [\-\-skip\-install] [\-\-strict|\-\-optimistic]
|
|
7
|
+
\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT|\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-cooldown=NUMBER] [\-\-quiet] [\-\-skip\-install] [\-\-strict|\-\-optimistic]
|
|
8
8
|
.SH "DESCRIPTION"
|
|
9
9
|
Adds the named gem to the [\fBGemfile(5)\fR][Gemfile(5)] and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
|
|
10
10
|
.SH "OPTIONS"
|
|
@@ -50,6 +50,9 @@ Adds optimistic declaration of version\.
|
|
|
50
50
|
.TP
|
|
51
51
|
\fB\-\-strict\fR
|
|
52
52
|
Adds strict declaration of version\.
|
|
53
|
+
.TP
|
|
54
|
+
\fB\-\-cooldown=<number>\fR
|
|
55
|
+
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run\. See \fBcooldown\fR in bundle\-config(1) for precedence rules\.
|
|
53
56
|
.SH "EXAMPLES"
|
|
54
57
|
.IP "1." 4
|
|
55
58
|
You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\.
|
|
@@ -73,4 +76,4 @@ You can assign the gem to more than one group\.
|
|
|
73
76
|
\fBbundle add rails \-\-group "development, test"\fR
|
|
74
77
|
.IP "" 0
|
|
75
78
|
.SH "SEE ALSO"
|
|
76
|
-
Gemfile(5) \fIhttps://
|
|
79
|
+
Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR, bundle\-remove(1) \fIbundle\-remove\.1\.html\fR
|
|
@@ -5,7 +5,7 @@ bundle-add(1) -- Add gem to the Gemfile and run bundle install
|
|
|
5
5
|
|
|
6
6
|
`bundle add` <GEM_NAME> [--group=GROUP] [--version=VERSION] [--source=SOURCE]
|
|
7
7
|
[--path=PATH] [--git=GIT|--github=GITHUB] [--branch=BRANCH] [--ref=REF]
|
|
8
|
-
[--quiet] [--skip-install] [--strict|--optimistic]
|
|
8
|
+
[--cooldown=NUMBER] [--quiet] [--skip-install] [--strict|--optimistic]
|
|
9
9
|
|
|
10
10
|
## DESCRIPTION
|
|
11
11
|
|
|
@@ -56,6 +56,11 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
|
|
|
56
56
|
* `--strict`:
|
|
57
57
|
Adds strict declaration of version.
|
|
58
58
|
|
|
59
|
+
* `--cooldown=<number>`:
|
|
60
|
+
Only consider gem versions published at least <number> days ago when
|
|
61
|
+
resolving. Pass `0` to disable cooldown for this run. See `cooldown`
|
|
62
|
+
in bundle-config(1) for precedence rules.
|
|
63
|
+
|
|
59
64
|
## EXAMPLES
|
|
60
65
|
|
|
61
66
|
1. You can add the `rails` gem to the Gemfile without any version restriction.
|
|
@@ -83,5 +88,5 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
|
|
|
83
88
|
|
|
84
89
|
## SEE ALSO
|
|
85
90
|
|
|
86
|
-
[Gemfile(5)](https://
|
|
91
|
+
[Gemfile(5)](https://guides.rubygems.org/gemfile/),
|
|
87
92
|
[bundle-remove(1)](bundle-remove.1.html)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.\" generated with Ronn-NG/v0.10.1
|
|
2
2
|
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
|
3
|
-
.TH "BUNDLE\-BINSTUBS" "1" "
|
|
3
|
+
.TH "BUNDLE\-BINSTUBS" "1" "April 2026" ""
|
|
4
4
|
.SH "NAME"
|
|
5
5
|
\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
|
|
6
6
|
.SH "SYNOPSIS"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.\" generated with Ronn-NG/v0.10.1
|
|
2
2
|
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
|
3
|
-
.TH "BUNDLE\-CACHE" "1" "
|
|
3
|
+
.TH "BUNDLE\-CACHE" "1" "April 2026" ""
|
|
4
4
|
.SH "NAME"
|
|
5
5
|
\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
|
|
6
6
|
.SH "SYNOPSIS"
|
|
@@ -17,6 +17,9 @@ Include gems for all platforms present in the lockfile, not only the current one
|
|
|
17
17
|
\fB\-\-cache\-path=CACHE\-PATH\fR
|
|
18
18
|
Specify a different cache path than the default (vendor/cache)\.
|
|
19
19
|
.TP
|
|
20
|
+
\fB\-\-cooldown=<number>\fR
|
|
21
|
+
Only consider gem versions published at least \fInumber\fR days ago when resolving before caching\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. See \fBcooldown\fR in bundle\-config(1)\.
|
|
22
|
+
.TP
|
|
20
23
|
\fB\-\-gemfile=GEMFILE\fR
|
|
21
24
|
Use the specified gemfile instead of Gemfile\.
|
|
22
25
|
.TP
|
|
@@ -21,6 +21,12 @@ use the gems in the cache in preference to the ones on `rubygems.org`.
|
|
|
21
21
|
* `--cache-path=CACHE-PATH`:
|
|
22
22
|
Specify a different cache path than the default (vendor/cache).
|
|
23
23
|
|
|
24
|
+
* `--cooldown=<number>`:
|
|
25
|
+
Only consider gem versions published at least <number> days ago when
|
|
26
|
+
resolving before caching. Pass `0` to disable cooldown for this run,
|
|
27
|
+
overriding any per-source or global configuration. See `cooldown` in
|
|
28
|
+
bundle-config(1).
|
|
29
|
+
|
|
24
30
|
* `--gemfile=GEMFILE`:
|
|
25
31
|
Use the specified gemfile instead of Gemfile.
|
|
26
32
|
|