diff options
-rw-r--r-- | lib/bundler/definition.rb | 85 | ||||
-rw-r--r-- | spec/bundler/runtime/setup_spec.rb | 31 | ||||
-rw-r--r-- | spec/bundler/support/hax.rb | 14 |
3 files changed, 94 insertions, 36 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 5e3dc06cf2..557315af4f 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -412,39 +412,8 @@ module Bundler raise ProductionError, "Frozen mode is set, but there's no lockfile" unless lockfile_exists? - added = [] - deleted = [] - changed = [] - - added.concat @new_platforms.map {|p| "* platform: #{p}" } - deleted.concat @removed_platforms.map {|p| "* platform: #{p}" } - - added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? - deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? - - both_sources = Hash.new {|h, k| h[k] = [] } - current_dependencies.each {|d| both_sources[d.name][0] = d } - current_locked_dependencies.each {|d| both_sources[d.name][1] = d } - - both_sources.each do |name, (dep, lock_dep)| - next if dep.nil? || lock_dep.nil? - - gemfile_source = dep.source || default_source - lock_source = lock_dep.source || default_source - next if lock_source.include?(gemfile_source) - - gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source" - lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source" - changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`" - end - - reason = resolve_needed? ? change_reason : "some dependencies were deleted from your gemfile" - msg = String.new - msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because frozen mode is set" - msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? - msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? - msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? - msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_gemfile_path} to version control.\n" unless unlocking? + msg = lockfile_changes_summary("frozen mode is set") + return unless msg unless explicit_flag suggested_command = unless Bundler.settings.locations("frozen").keys.include?(:env) @@ -454,7 +423,7 @@ module Bundler "freeze by running `#{suggested_command}`." if suggested_command end - raise ProductionError, msg if added.any? || deleted.any? || changed.any? || resolve_needed? + raise ProductionError, msg end def validate_runtime! @@ -539,6 +508,46 @@ module Bundler private + def lockfile_changes_summary(update_refused_reason) + added = [] + deleted = [] + changed = [] + + added.concat @new_platforms.map {|p| "* platform: #{p}" } + deleted.concat @removed_platforms.map {|p| "* platform: #{p}" } + + added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? + deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? + + both_sources = Hash.new {|h, k| h[k] = [] } + current_dependencies.each {|d| both_sources[d.name][0] = d } + current_locked_dependencies.each {|d| both_sources[d.name][1] = d } + + both_sources.each do |name, (dep, lock_dep)| + next if dep.nil? || lock_dep.nil? + + gemfile_source = dep.source || default_source + lock_source = lock_dep.source || default_source + next if lock_source.include?(gemfile_source) + + gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source" + lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source" + changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`" + end + + return unless added.any? || deleted.any? || changed.any? || resolve_needed? + + reason = resolve_needed? ? change_reason : "some dependencies were deleted from your gemfile" + + msg = String.new + msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because #{update_refused_reason}" + msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? + msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? + msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? + msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_gemfile_path} to version control.\n" unless unlocking? + msg + end + def install_needed? resolve_needed? || missing_specs? end @@ -599,8 +608,12 @@ module Bundler return end - SharedHelpers.filesystem_access(file) do |p| - File.open(p, "wb") {|f| f.puts(contents) } + begin + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end + rescue ReadOnlyFileSystemError + raise ProductionError, lockfile_changes_summary("file system is read-only") end end diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index 106f1edc57..5edf464827 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1656,4 +1656,35 @@ end expect(err).to be_empty expect(out).to include("Installing myrack 1.0.0") end + + context "in a read-only filesystem" do + before do + gemfile <<-G + source "https://gem.repo4" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + + PLATFORMS + x86_64-darwin-19 + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "should fail loudly if the lockfile platforms don't include the current platform" do + simulate_platform "x86_64-linux" do + ruby <<-RUBY, raise_on_error: false, env: { "BUNDLER_SPEC_READ_ONLY" => "true", "BUNDLER_FORCE_TTY" => "true" } + require "bundler/setup" + RUBY + end + + 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") + end + end end diff --git a/spec/bundler/support/hax.rb b/spec/bundler/support/hax.rb index 715af6cab6..01bad64ce7 100644 --- a/spec/bundler/support/hax.rb +++ b/spec/bundler/support/hax.rb @@ -37,4 +37,18 @@ module Gem if ENV["BUNDLER_SPEC_GEM_SOURCES"] self.sources = [ENV["BUNDLER_SPEC_GEM_SOURCES"]] end + + if ENV["BUNDLER_SPEC_READ_ONLY"] + module ReadOnly + def open(file, mode) + if file != IO::NULL && mode == "wb" + raise Errno::EROFS + else + super + end + end + end + + File.singleton_class.prepend ReadOnly + end end |