Skip to content

Commit 3e78a2f

Browse files
deivid-rodriguezhsbt
authored andcommitted
[rubygems/rubygems] Improve error message when on read-only filesystems
If we fail to write the lockfile, give a better error. ruby/rubygems@81a08d6eda
1 parent 0d62037 commit 3e78a2f

File tree

3 files changed

+94
-36
lines changed

3 files changed

+94
-36
lines changed

lib/bundler/definition.rb

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -412,39 +412,8 @@ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
412412

413413
raise ProductionError, "Frozen mode is set, but there's no lockfile" unless lockfile_exists?
414414

415-
added = []
416-
deleted = []
417-
changed = []
418-
419-
added.concat @new_platforms.map {|p| "* platform: #{p}" }
420-
deleted.concat @removed_platforms.map {|p| "* platform: #{p}" }
421-
422-
added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
423-
deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any?
424-
425-
both_sources = Hash.new {|h, k| h[k] = [] }
426-
current_dependencies.each {|d| both_sources[d.name][0] = d }
427-
current_locked_dependencies.each {|d| both_sources[d.name][1] = d }
428-
429-
both_sources.each do |name, (dep, lock_dep)|
430-
next if dep.nil? || lock_dep.nil?
431-
432-
gemfile_source = dep.source || default_source
433-
lock_source = lock_dep.source || default_source
434-
next if lock_source.include?(gemfile_source)
435-
436-
gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source"
437-
lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source"
438-
changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`"
439-
end
440-
441-
reason = resolve_needed? ? change_reason : "some dependencies were deleted from your gemfile"
442-
msg = String.new
443-
msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because frozen mode is set"
444-
msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
445-
msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
446-
msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
447-
msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_gemfile_path} to version control.\n" unless unlocking?
415+
msg = lockfile_changes_summary("frozen mode is set")
416+
return unless msg
448417

449418
unless explicit_flag
450419
suggested_command = unless Bundler.settings.locations("frozen").keys.include?(:env)
@@ -454,7 +423,7 @@ def ensure_equivalent_gemfile_and_lockfile(explicit_flag = false)
454423
"freeze by running `#{suggested_command}`." if suggested_command
455424
end
456425

457-
raise ProductionError, msg if added.any? || deleted.any? || changed.any? || resolve_needed?
426+
raise ProductionError, msg
458427
end
459428

460429
def validate_runtime!
@@ -539,6 +508,46 @@ def add_checksums
539508

540509
private
541510

511+
def lockfile_changes_summary(update_refused_reason)
512+
added = []
513+
deleted = []
514+
changed = []
515+
516+
added.concat @new_platforms.map {|p| "* platform: #{p}" }
517+
deleted.concat @removed_platforms.map {|p| "* platform: #{p}" }
518+
519+
added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
520+
deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any?
521+
522+
both_sources = Hash.new {|h, k| h[k] = [] }
523+
current_dependencies.each {|d| both_sources[d.name][0] = d }
524+
current_locked_dependencies.each {|d| both_sources[d.name][1] = d }
525+
526+
both_sources.each do |name, (dep, lock_dep)|
527+
next if dep.nil? || lock_dep.nil?
528+
529+
gemfile_source = dep.source || default_source
530+
lock_source = lock_dep.source || default_source
531+
next if lock_source.include?(gemfile_source)
532+
533+
gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source"
534+
lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source"
535+
changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`"
536+
end
537+
538+
return unless added.any? || deleted.any? || changed.any? || resolve_needed?
539+
540+
reason = resolve_needed? ? change_reason : "some dependencies were deleted from your gemfile"
541+
542+
msg = String.new
543+
msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because #{update_refused_reason}"
544+
msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any?
545+
msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any?
546+
msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any?
547+
msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_gemfile_path} to version control.\n" unless unlocking?
548+
msg
549+
end
550+
542551
def install_needed?
543552
resolve_needed? || missing_specs?
544553
end
@@ -599,8 +608,12 @@ def write_lock(file, preserve_unknown_sections)
599608
return
600609
end
601610

602-
SharedHelpers.filesystem_access(file) do |p|
603-
File.open(p, "wb") {|f| f.puts(contents) }
611+
begin
612+
SharedHelpers.filesystem_access(file) do |p|
613+
File.open(p, "wb") {|f| f.puts(contents) }
614+
end
615+
rescue ReadOnlyFileSystemError
616+
raise ProductionError, lockfile_changes_summary("file system is read-only")
604617
end
605618
end
606619

spec/bundler/runtime/setup_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,4 +1656,35 @@ def require(path)
16561656
expect(err).to be_empty
16571657
expect(out).to include("Installing myrack 1.0.0")
16581658
end
1659+
1660+
context "in a read-only filesystem" do
1661+
before do
1662+
gemfile <<-G
1663+
source "https://gem.repo4"
1664+
G
1665+
1666+
lockfile <<-L
1667+
GEM
1668+
remote: https://gem.repo4/
1669+
1670+
PLATFORMS
1671+
x86_64-darwin-19
1672+
1673+
DEPENDENCIES
1674+
1675+
BUNDLED WITH
1676+
#{Bundler::VERSION}
1677+
L
1678+
end
1679+
1680+
it "should fail loudly if the lockfile platforms don't include the current platform" do
1681+
simulate_platform "x86_64-linux" do
1682+
ruby <<-RUBY, raise_on_error: false, env: { "BUNDLER_SPEC_READ_ONLY" => "true", "BUNDLER_FORCE_TTY" => "true" }
1683+
require "bundler/setup"
1684+
RUBY
1685+
end
1686+
1687+
expect(err).to include("Your lockfile does not include the current platform, but the lockfile can't be updated because file system is read-only")
1688+
end
1689+
end
16591690
end

spec/bundler/support/hax.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,18 @@ class Platform
3737
if ENV["BUNDLER_SPEC_GEM_SOURCES"]
3838
self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]]
3939
end
40+
41+
if ENV["BUNDLER_SPEC_READ_ONLY"]
42+
module ReadOnly
43+
def open(file, mode)
44+
if file != IO::NULL && mode == "wb"
45+
raise Errno::EROFS
46+
else
47+
super
48+
end
49+
end
50+
end
51+
52+
File.singleton_class.prepend ReadOnly
53+
end
4054
end

0 commit comments

Comments
 (0)