[rubygems/rubygems] Improve Bundler errors when trying to install to a protected folder in macOS

### Before

```
$ GEM_HOME=/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/ bundle
Fetching gem metadata from https://rubygems.org/.
Source rubygems repository https://rubygems.org/ or installed locally is ignoring #<Bundler::StubSpecification name=sqlite3 version=1.3.13 platform=ruby> because it is missing extensions
Source rubygems repository https://rubygems.org/ or installed locally is ignoring #<Bundler::StubSpecification name=nokogiri version=1.13.8 platform=ruby> because it is missing extensions
Source rubygems repository https://rubygems.org/ or installed locally is ignoring #<Bundler::StubSpecification name=libxml-ruby version=3.2.1 platform=ruby> because it is missing extensions
Resolving dependencies...
Fetching ruby2_keywords 0.0.5

Retrying download gem from https://rubygems.org/ due to error (2/4): Bundler::GenericSystemCallError There was an error accessing `/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem`.
The underlying system error is Errno::EPERM: Operation not permitted @ rb_sysopen - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem

Retrying download gem from https://rubygems.org/ due to error (3/4): Bundler::GenericSystemCallError There was an error accessing `/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem`.
The underlying system error is Errno::EPERM: Operation not permitted @ rb_sysopen - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem

Retrying download gem from https://rubygems.org/ due to error (4/4): Bundler::GenericSystemCallError There was an error accessing `/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem`.
The underlying system error is Errno::EPERM: Operation not permitted @ rb_sysopen - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem

Bundler::GenericSystemCallError: There was an error accessing `/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem`.
The underlying system error is Errno::EPERM: Operation not permitted @ rb_sysopen - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/cache/ruby2_keywords-0.0.5.gem
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/shared_helpers.rb:119:in `rescue in filesystem_access'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/shared_helpers.rb:104:in `filesystem_access'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/rubygems_integration.rb:431:in `block in download_gem'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/retry.rb:40:in `run'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/retry.rb:30:in `attempt'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/rubygems_integration.rb:423:in `download_gem'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/source/rubygems.rb:479:in `download_gem'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/source/rubygems.rb:436:in `fetch_gem'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/source/rubygems.rb:420:in `fetch_gem_if_possible'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/source/rubygems.rb:162:in `install'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/installer/gem_installer.rb:55:in `install'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/installer/gem_installer.rb:17:in `install_from_spec'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/installer/parallel_installer.rb:133:in `do_install'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/installer/parallel_installer.rb:124:in `block in worker_pool'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/worker.rb:62:in `apply_func'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/worker.rb:57:in `block in process_queue'
  <internal:kernel>:187:in `loop'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/worker.rb:54:in `process_queue'
  /Users/deivid/code/rubygems/rubygems/bundler/lib/bundler/worker.rb:90:in `block (2 levels) in create_threads'

An error occurred while installing ruby2_keywords (0.0.5), and Bundler cannot continue.

In Gemfile:
  ruby2_keywords
```

### After

```
$ GEM_HOME=/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/ bundle
There was an error creating `/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/bundler.lock`.
The underlying system error is Errno::EPERM: Operation not permitted @ rb_sysopen - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0/bundler.lock
```

https://github.com/rubygems/rubygems/commit/345ec45f5a
This commit is contained in:
David Rodríguez 2024-10-21 19:30:50 +02:00 committed by git
parent 055ed5f592
commit f2380081df
6 changed files with 20 additions and 38 deletions

View File

@ -2,23 +2,19 @@
module Bundler
class ProcessLock
def self.lock(bundle_path = Bundler.bundle_path)
def self.lock(bundle_path = Bundler.bundle_path, &block)
lock_file_path = File.join(bundle_path, "bundler.lock")
has_lock = false
base_lock_file_path = lock_file_path.delete_suffix(".lock")
File.open(lock_file_path, "w") do |f|
f.flock(File::LOCK_EX)
has_lock = true
yield
f.flock(File::LOCK_UN)
require "fileutils" if Bundler.rubygems.provides?("< 3.5.23")
begin
SharedHelpers.filesystem_access(lock_file_path, :write) do
Gem.open_file_with_lock(base_lock_file_path, &block)
end
rescue PermissionError
block.call
end
rescue Errno::EACCES, Errno::ENOLCK, Errno::ENOTSUP, Errno::EPERM, Errno::EROFS
# In the case the user does not have access to
# create the lock file or is using NFS where
# locks are not available we skip locking.
yield
ensure
FileUtils.rm_f(lock_file_path) if has_lock
end
end
end

View File

@ -103,7 +103,9 @@ module Bundler
# @see {Bundler::PermissionError}
def filesystem_access(path, action = :write, &block)
yield(path.dup)
rescue Errno::EACCES
rescue Errno::EACCES => e
raise unless e.message.include?(path.to_s) || action == :create
raise PermissionError.new(path, action)
rescue Errno::EAGAIN
raise TemporaryResourceError.new(path, action)

View File

@ -801,6 +801,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]}
file_lock = "#{path}.lock"
open_file_with_flock(file_lock, &block)
ensure
require "fileutils"
FileUtils.rm_f file_lock
end

View File

@ -15,7 +15,7 @@ RSpec.describe Bundler::Definition do
it "raises an PermissionError with explanation" do
allow(File).to receive(:open).and_call_original
expect(File).to receive(:open).with(bundled_app_lock, "wb").
and_raise(Errno::EACCES)
and_raise(Errno::EACCES.new(bundled_app_lock.to_s))
expect { subject.lock }.
to raise_error(Bundler::PermissionError, /Gemfile\.lock/)
end

View File

@ -458,7 +458,7 @@ RSpec.describe Bundler::SharedHelpers do
end
context "system throws Errno::EACESS" do
let(:file_op_block) { proc {|_path| raise Errno::EACCES } }
let(:file_op_block) { proc {|_path| raise Errno::EACCES.new("/path") } }
it "raises a PermissionError" do
expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error(

View File

@ -21,36 +21,19 @@ RSpec.describe "process lock spec" do
expect(the_bundle).to include_gems "myrack 1.0"
end
context "when creating a lock raises Errno::ENOTSUP" do
before { allow(File).to receive(:open).and_raise(Errno::ENOTSUP) }
it "skips creating the lock file and yields" do
processed = false
Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
expect(processed).to eq true
end
end
context "when creating a lock raises Errno::EPERM" do
before { allow(File).to receive(:open).and_raise(Errno::EPERM) }
it "skips creating the lock file and yields" do
processed = false
Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
expect(processed).to eq true
it "raises a friendly error" do
expect { Bundler::ProcessLock.lock(default_bundle_path) }.to raise_error(Bundler::GenericSystemCallError)
end
end
context "when creating a lock raises Errno::EROFS" do
before { allow(File).to receive(:open).and_raise(Errno::EROFS) }
it "skips creating the lock file and yields" do
processed = false
Bundler::ProcessLock.lock(default_bundle_path) { processed = true }
expect(processed).to eq true
it "raises a friendly error" do
expect { Bundler::ProcessLock.lock(default_bundle_path) }.to raise_error(Bundler::GenericSystemCallError)
end
end
end