Skip to content

Commit da7d605

Browse files
Merge pull request #3655 from rubygems/disable_multisource_improvements
Fix source priority for transitive dependencies and split lockfile rubygems source sections (cherry picked from commit 4346a8b)
1 parent 145bc3e commit da7d605

26 files changed

+452
-394
lines changed

.github/workflows/ruby-core.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,17 @@ jobs:
4646
- uses: actions/checkout@v2
4747
with:
4848
path: rubygems/rubygems
49-
- name: Test RubyGems
49+
- name: Sync tools
5050
run: |
5151
ruby tool/sync_default_gems.rb rubygems
52-
make -j2 -s test-all TESTS="rubygems --no-retry"
52+
ruby tool/sync_default_gems.rb bundler
53+
working-directory: ruby/ruby
54+
- name: Test RubyGems
55+
run: make -j2 -s test-all TESTS="rubygems --no-retry"
5356
working-directory: ruby/ruby
5457
if: matrix.target == 'Rubygems'
5558
- name: Test Bundler
5659
run: |
57-
ruby tool/sync_default_gems.rb bundler
5860
git checkout lib/bundler/bundler.gemspec
5961
git add .
6062
make test-bundler-parallel

bundler/lib/bundler/definition.rb

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
106106
@locked_platforms = []
107107
end
108108

109+
@locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
110+
@disable_multisource = !Bundler.frozen_bundle? || @locked_gem_sources.none? {|s| s.remotes.size > 1 }
111+
112+
unless @disable_multisource
113+
msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \
114+
"You should regenerate your lockfile in a non frozen environment."
115+
116+
Bundler::SharedHelpers.major_deprecation 2, msg
117+
118+
@sources.allow_multisource!
119+
@locked_gem_sources.each(&:allow_multisource!)
120+
end
121+
109122
@unlock[:gems] ||= []
110123
@unlock[:sources] ||= []
111124
@unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
@@ -145,6 +158,14 @@ def gem_version_promoter
145158
end
146159
end
147160

161+
def disable_multisource?
162+
@disable_multisource
163+
end
164+
165+
def allow_multisource!
166+
@disable_multisource = false
167+
end
168+
148169
def resolve_with_cache!
149170
raise "Specs already loaded" if @specs
150171
sources.cached!
@@ -264,7 +285,7 @@ def resolve
264285
# Run a resolve against the locally available gems
265286
Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
266287
expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
267-
Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
288+
Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
268289
end
269290
end
270291
end
@@ -530,6 +551,9 @@ def find_indexed_specs(current_spec)
530551
attr_reader :sources
531552
private :sources
532553

554+
attr_reader :locked_gem_sources
555+
private :locked_gem_sources
556+
533557
def nothing_changed?
534558
!@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
535559
end
@@ -654,21 +678,20 @@ def converge_path_sources_to_gemspec_sources
654678
end
655679

656680
def converge_rubygems_sources
657-
return false if Bundler.feature_flag.disable_multisource?
681+
return false if disable_multisource?
658682

659-
changes = false
683+
return false if locked_gem_sources.empty?
660684

661-
# Get the RubyGems sources from the Gemfile.lock
662-
locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
663685
# Get the RubyGems remotes from the Gemfile
664686
actual_remotes = sources.rubygems_remotes
687+
return false if actual_remotes.empty?
688+
689+
changes = false
665690

666691
# If there is a RubyGems source in both
667-
if !locked_gem_sources.empty? && !actual_remotes.empty?
668-
locked_gem_sources.each do |locked_gem|
669-
# Merge the remotes from the Gemfile into the Gemfile.lock
670-
changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
671-
end
692+
locked_gem_sources.each do |locked_gem|
693+
# Merge the remotes from the Gemfile into the Gemfile.lock
694+
changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
672695
end
673696

674697
changes
@@ -893,30 +916,18 @@ def source_requirements
893916
# Record the specs available in each gem's source, so that those
894917
# specs will be available later when the resolver knows where to
895918
# look for that gemspec (or its dependencies)
896-
default = sources.default_source
897-
source_requirements = { :default => default }
898-
default = nil unless Bundler.feature_flag.disable_multisource?
899-
dependencies.each do |dep|
900-
next unless source = dep.source || default
901-
source_requirements[dep.name] = source
902-
end
919+
source_requirements = { :default => sources.default_source }.merge(dependency_source_requirements)
903920
metadata_dependencies.each do |dep|
904921
source_requirements[dep.name] = sources.metadata_source
905922
end
923+
source_requirements[:global] = index unless disable_multisource?
906924
source_requirements[:default_bundler] = source_requirements["bundler"] || source_requirements[:default]
907925
source_requirements["bundler"] = sources.metadata_source # needs to come last to override
908926
source_requirements
909927
end
910928

911929
def pinned_spec_names(skip = nil)
912-
pinned_names = []
913-
default = Bundler.feature_flag.disable_multisource? && sources.default_source
914-
@dependencies.each do |dep|
915-
next unless dep_source = dep.source || default
916-
next if dep_source == skip
917-
pinned_names << dep.name
918-
end
919-
pinned_names
930+
dependency_source_requirements.reject {|_, source| source == skip }.keys
920931
end
921932

922933
def requested_groups
@@ -973,5 +984,18 @@ def equivalent_rubygems_remotes?(source)
973984

974985
Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
975986
end
987+
988+
def dependency_source_requirements
989+
@dependency_source_requirements ||= begin
990+
source_requirements = {}
991+
default = disable_multisource? && sources.default_source
992+
dependencies.each do |dep|
993+
dep_source = dep.source || default
994+
next unless dep_source
995+
source_requirements[dep.name] = dep_source
996+
end
997+
source_requirements
998+
end
999+
end
9761000
end
9771001
end

bundler/lib/bundler/dsl.rb

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ def self.evaluate(gemfile, lockfile, unlock)
2424
def initialize
2525
@source = nil
2626
@sources = SourceList.new
27+
28+
@global_rubygems_sources = []
29+
2730
@git_sources = {}
2831
@dependencies = []
2932
@groups = []
@@ -45,6 +48,7 @@ def eval_gemfile(gemfile, contents = nil)
4548
@gemfiles << expanded_gemfile_path
4649
contents ||= Bundler.read_file(@gemfile.to_s)
4750
instance_eval(contents.dup.tap{|x| x.untaint if RUBY_VERSION < "2.7" }, gemfile.to_s, 1)
51+
check_primary_source_safety
4852
rescue Exception => e # rubocop:disable Lint/RescueException
4953
message = "There was an error " \
5054
"#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
@@ -164,8 +168,7 @@ def source(source, *args, &blk)
164168
elsif block_given?
165169
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
166170
else
167-
check_primary_source_safety(@sources)
168-
@sources.global_rubygems_source = source
171+
@global_rubygems_sources << source
169172
end
170173
end
171174

@@ -183,24 +186,14 @@ def git_source(name, &block)
183186
end
184187

185188
def path(path, options = {}, &blk)
186-
unless block_given?
187-
msg = "You can no longer specify a path source by itself. Instead, \n" \
188-
"either use the :path option on a gem, or specify the gems that \n" \
189-
"bundler should find in the path source by passing a block to \n" \
190-
"the path method, like: \n\n" \
191-
" path 'dir/containing/rails' do\n" \
192-
" gem 'rails'\n" \
193-
" end\n\n"
194-
195-
raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
196-
SharedHelpers.major_deprecation(2, msg.strip)
197-
end
198-
199189
source_options = normalize_hash(options).merge(
200190
"path" => Pathname.new(path),
201191
"root_path" => gemfile_root,
202192
"gemspec" => gemspecs.find {|g| g.name == options["name"] }
203193
)
194+
195+
source_options["global"] = true unless block_given?
196+
204197
source = @sources.add_path_source(source_options)
205198
with_source(source, &blk)
206199
end
@@ -279,6 +272,11 @@ def method_missing(name, *args)
279272
raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
280273
end
281274

275+
def check_primary_source_safety
276+
check_path_source_safety
277+
check_rubygems_source_safety
278+
end
279+
282280
private
283281

284282
def add_git_sources
@@ -440,25 +438,40 @@ def normalize_source(source)
440438
end
441439
end
442440

443-
def check_primary_source_safety(source_list)
444-
return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?
441+
def check_path_source_safety
442+
return if @sources.global_path_source.nil?
443+
444+
msg = "You can no longer specify a path source by itself. Instead, \n" \
445+
"either use the :path option on a gem, or specify the gems that \n" \
446+
"bundler should find in the path source by passing a block to \n" \
447+
"the path method, like: \n\n" \
448+
" path 'dir/containing/rails' do\n" \
449+
" gem 'rails'\n" \
450+
" end\n\n"
445451

446-
if Bundler.feature_flag.disable_multisource?
452+
SharedHelpers.major_deprecation(2, msg.strip)
453+
end
454+
455+
def check_rubygems_source_safety
456+
if @global_rubygems_sources.size <= 1
457+
@sources.global_rubygems_source = @global_rubygems_sources.first
458+
return
459+
end
460+
461+
@global_rubygems_sources.each do |source|
462+
@sources.add_rubygems_remote(source)
463+
end
464+
465+
if Bundler.feature_flag.bundler_3_mode?
447466
msg = "This Gemfile contains multiple primary sources. " \
448467
"Each source after the first must include a block to indicate which gems " \
449468
"should come from that source"
450-
unless Bundler.feature_flag.bundler_2_mode?
451-
msg += ". To downgrade this error to a warning, run " \
452-
"`bundle config unset disable_multisource`"
453-
end
454469
raise GemfileEvalError, msg
455470
else
456471
Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
457472
"Using `source` more than once without a block is a security risk, and " \
458473
"may result in installing unexpected gems. To resolve this warning, use " \
459-
"a block to indicate which gems should come from the secondary source. " \
460-
"To upgrade this warning to an error, run `bundle config set --local " \
461-
"disable_multisource true`."
474+
"a block to indicate which gems should come from the secondary source."
462475
end
463476
end
464477

bundler/lib/bundler/feature_flag.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def self.settings_method(name, key, &default)
3232
settings_flag(:cache_all) { bundler_3_mode? }
3333
settings_flag(:default_install_uses_path) { bundler_3_mode? }
3434
settings_flag(:deployment_means_frozen) { bundler_3_mode? }
35-
settings_flag(:disable_multisource) { bundler_3_mode? }
3635
settings_flag(:forget_cli_options) { bundler_3_mode? }
3736
settings_flag(:global_gem_cache) { bundler_3_mode? }
3837
settings_flag(:only_update_to_newer_versions) { bundler_3_mode? }

bundler/lib/bundler/inline.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def Bundler.root
5050
Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
5151
builder = Bundler::Dsl.new
5252
builder.instance_eval(&gemfile)
53+
builder.check_primary_source_safety
5354

5455
Bundler.settings.temporary(:frozen => false) do
5556
definition = builder.to_definition(nil, true)

bundler/lib/bundler/lockfile_parser.rb

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ def initialize(lockfile)
6464
@state = nil
6565
@specs = {}
6666

67-
@rubygems_aggregate = Source::Rubygems.new
68-
6967
if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
7068
raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \
7169
"Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock."
@@ -89,7 +87,6 @@ def initialize(lockfile)
8987
send("parse_#{@state}", line)
9088
end
9189
end
92-
@sources << @rubygems_aggregate unless Bundler.feature_flag.disable_multisource?
9390
@specs = @specs.values.sort_by(&:identifier)
9491
warn_for_outdated_bundler_version
9592
rescue ArgumentError => e
@@ -134,16 +131,19 @@ def parse_source(line)
134131
@sources << @current_source
135132
end
136133
when GEM
137-
if Bundler.feature_flag.disable_multisource?
134+
source_remotes = Array(@opts["remote"])
135+
136+
if source_remotes.size == 1
138137
@opts["remotes"] = @opts.delete("remote")
139138
@current_source = TYPES[@type].from_lock(@opts)
140-
@sources << @current_source
141139
else
142-
Array(@opts["remote"]).each do |url|
143-
@rubygems_aggregate.add_remote(url)
140+
source_remotes.each do |url|
141+
rubygems_aggregate.add_remote(url)
144142
end
145-
@current_source = @rubygems_aggregate
143+
@current_source = rubygems_aggregate
146144
end
145+
146+
@sources << @current_source
147147
when PLUGIN
148148
@current_source = Plugin.source_from_lock(@opts)
149149
@sources << @current_source
@@ -245,5 +245,9 @@ def parse_bundled_with(line)
245245
def parse_ruby(line)
246246
@ruby_version = line.strip
247247
end
248+
249+
def rubygems_aggregate
250+
@rubygems_aggregate ||= Source::Rubygems.new
251+
end
248252
end
249253
end

bundler/lib/bundler/man/bundle-config.1

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ Executing \fBbundle config unset \-\-local <name> <value>\fR will delete the con
5656
.P
5757
Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
5858
.
59-
.P
60-
Executing \fBbundle config set \-\-local disable_multisource true\fR upgrades the warning about the Gemfile containing multiple primary sources to an error\. Executing \fBbundle config unset disable_multisource\fR downgrades this error to a warning\.
61-
.
6259
.SH "REMEMBERING OPTIONS"
6360
Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\.
6461
.
@@ -184,9 +181,6 @@ The following is a list of all configuration keys and their purpose\. You can le
184181
\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
185182
.
186183
.IP "\(bu" 4
187-
\fBdisable_multisource\fR (\fBBUNDLE_DISABLE_MULTISOURCE\fR): When set, Gemfiles containing multiple sources will produce errors instead of warnings\. Use \fBbundle config unset disable_multisource\fR to unset\.
188-
.
189-
.IP "\(bu" 4
190184
\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\.
191185
.
192186
.IP "\(bu" 4

bundler/lib/bundler/man/bundle-config.1.ronn

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ configuration only from the local application.
4747
Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will
4848
cause it to ignore all configuration.
4949

50-
Executing `bundle config set --local disable_multisource true` upgrades the warning about
51-
the Gemfile containing multiple primary sources to an error. Executing `bundle
52-
config unset disable_multisource` downgrades this error to a warning.
53-
5450
## REMEMBERING OPTIONS
5551

5652
Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or
@@ -178,10 +174,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
178174
* `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`):
179175
Allow Bundler to use a local git override without checking if the revision
180176
present in the lockfile is present in the repository.
181-
* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`):
182-
When set, Gemfiles containing multiple sources will produce errors
183-
instead of warnings.
184-
Use `bundle config unset disable_multisource` to unset.
185177
* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
186178
Stop Bundler from accessing gems installed to RubyGems' normal location.
187179
* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):

bundler/lib/bundler/plugin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def gemfile_install(gemfile = nil, &inline)
105105
else
106106
builder.eval_gemfile(gemfile)
107107
end
108+
builder.check_primary_source_safety
108109
definition = builder.to_definition(nil, true)
109110

110111
return if definition.dependencies.empty?

0 commit comments

Comments
 (0)