Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion lib/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def auto_install
rescue GemNotFound, GitError
ui.info "Automatically installing missing gems."
reset!
CLI::Install.new({}).run
auto_install_missing_gems
reset!
end
end
Expand Down Expand Up @@ -605,6 +605,44 @@ def self_manager

private

# When possible we do the install in a subprocess because to install gems
# we need to require some default gems like `openssl` (for HTTPS remotes)
# which may later conflict with the Gemfile requirements. `bundler/inline`
# re-resolves when that happens. We can't: the `Bundler.setup` that follows
# must activate what the lockfile says.
def auto_install_missing_gems
do_install = -> { CLI::Install.new({}).run }

if Process.respond_to?(:fork)
[$stdout, $stderr].each(&:flush) # don't let the fork inherit buffered output

_, status = Process.waitpid2(Process.fork do
exit_status = 1

begin
# Errors here never reach the parent's handler. Report them in the
# child, and let the parent exit with the status. Required inside the
# fork so the CLI's vendored Thor stays out of the parent.
require_relative "bundler/friendly_errors"

with_friendly_errors(&do_install)
exit_status = 0
rescue SystemExit => e
exit_status = e.status
ensure
# Skip `at_exit` handlers, they belong to the booting program.
# `exit!` doesn't flush, so flush by hand.
[$stdout, $stderr].each(&:flush)
exit!(exit_status)
end
end)

exit(status.exitstatus || status.to_i) unless status.success?
else
do_install.call
end
end

def unbundle_env(env)
if env.key?("BUNDLER_ORIG_MANPATH")
env["MANPATH"] = env["BUNDLER_ORIG_MANPATH"]
Expand Down
30 changes: 30 additions & 0 deletions spec/bundler/bundler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,36 @@
end
end

describe "#auto_install" do
let(:install) { double("install") }

before do
skip "requires Process.fork" unless Process.respond_to?(:fork)

definition = double("definition")
allow(definition).to receive(:specs).and_raise(Bundler::GemNotFound)
allow(Bundler).to receive(:definition).and_return(definition)
allow(Bundler::CLI::Install).to receive(:new).with({}).and_return(install)
end

it "installs in a subprocess, so that gems activated to install don't conflict with the Gemfile" do
installer_pid = tmp("auto_install_pid")
allow(install).to receive(:run) { File.write(installer_pid, Process.pid) }

Bundler.settings.temporary(auto_install: true) { Bundler.auto_install }

expect(installer_pid.read.to_i).not_to eq(Process.pid)
end

it "exits with the status code of a failed install" do
allow(install).to receive(:run).and_raise(Bundler::InstallError)

expect do
Bundler.settings.temporary(auto_install: true) { Bundler.auto_install }
end.to raise_error(SystemExit) {|error| expect(error.status).to eq(5) }
end
end

describe "#mkdir_p" do
it "creates a folder at the given path" do
install_gemfile <<-G
Expand Down
23 changes: 23 additions & 0 deletions spec/runtime/setup_gems_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,29 @@ def require(path)
expect(out).to include("Installing myrack 1.0.0")
end

it "performs an automatic bundle install of a default gem locked to another version" do
build_repo4 do
build_gem "psych", "999"
build_gem "myrack", "1.0.0"
end

gemfile <<-G
source "https://gem.repo4"
gem "psych"
gem "myrack"
G

bundle_config "auto_install 1"

ruby <<-RUBY, artifice: "compact_index"
require 'bundler/setup'
puts Gem.loaded_specs["psych"].version
RUBY
expect(err).to be_empty
expect(out).to include("Installing psych 999")
expect(out).to include("999")
end

context "in a read-only filesystem" do
before do
gemfile <<-G
Expand Down