Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

commands/exec: allow customising environment. #1573

Merged
merged 1 commit into from
Jan 23, 2025
Merged
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
31 changes: 30 additions & 1 deletion lib/bundle/commands/exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ def run(*args, global: false, file: nil)

Formulary.factory(entry.name)
end
ENV.keg_only_deps = ENV.deps.select(&:keg_only?)

# Allow setting all dependencies to be keg-only
# (i.e. should be explicitly in HOMEBREW_*PATHs ahead of HOMEBREW_PREFIX)
ENV.keg_only_deps = if ENV["HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS"].present?
ENV.delete("HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS")
ENV.deps
else
ENV.deps.select(&:keg_only?)
end
ENV.setup_build_environment

# Enable compiler flag filtering
Expand All @@ -58,6 +66,27 @@ def run(*args, global: false, file: nil)
# Ensure the Ruby path we saved goes before anything else, if the command was in the PATH
ENV.prepend_path "PATH", command_path if command_path.present?

# Replace the formula versions from the environment variables
formula_versions = {}
ENV.each do |key, value|
match = key.match(/^HOMEBREW_BUNDLE_EXEC_FORMULA_VERSION_(.+)$/)
next if match.blank?

formula_name = match[1]
next if formula_name.blank?

ENV.delete(key)
formula_versions[formula_name.downcase] = value
end
formula_versions.each do |formula_name, formula_version|
ENV.each do |key, value|
opt = %r{/opt/#{formula_name}([/:$])}
next unless value.match(opt)

ENV[key] = value.gsub(opt, "/Cellar/#{formula_name}/#{formula_version}\\1")
end
end

exec(*args)
end
end
Expand Down
96 changes: 47 additions & 49 deletions spec/bundle/commands/exec_command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,47 @@
end

context "when a Brewfile is found" do
it "does not raise an error" do
allow(described_class).to receive(:exec).and_return(nil)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'openssl'")
let(:brewfile_contents) { "brew 'openssl'" }

expect { described_class.run("bundle", "install") }.not_to raise_error
before do
allow_any_instance_of(Pathname).to receive(:read)
.and_return(brewfile_contents)
end

it "is able to run without bundle arguments" do
allow(described_class).to receive(:exec).with("bundle", "install").and_return(nil)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'openssl'")
context "with valid command setup" do
before do
allow(described_class).to receive(:exec).and_return(nil)
end

expect { described_class.run("bundle", "install") }.not_to raise_error
end
it "does not raise an error" do
expect { described_class.run("bundle", "install") }.not_to raise_error
end

it "raises an exception if called without a command" do
allow(described_class).to receive(:exec).and_return(nil)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'openssl'")
it "does not raise an error when HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS is set" do
ENV["HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS"] = "1"
expect { described_class.run("bundle", "install") }.not_to raise_error
end

expect { described_class.run }.to raise_error(RuntimeError)
it "uses the formula version from the environment variable" do
openssl_version = "1.1.1"
ENV["PATH"] = "/opt/homebrew/opt/openssl/bin"
ENV["HOMEBREW_BUNDLE_EXEC_FORMULA_VERSION_OPENSSL"] = openssl_version
described_class.run("bundle", "install")
expect(ENV.fetch("PATH")).to include("/Cellar/openssl/1.1.1/bin")
end

it "is able to run without bundle arguments" do
allow(described_class).to receive(:exec).with("bundle", "install").and_return(nil)
expect { described_class.run("bundle", "install") }.not_to raise_error
end

it "raises an exception if called without a command" do
expect { described_class.run }.to raise_error(RuntimeError)
end
end

it "raises if called with a command that's not on the PATH" do
allow(described_class).to receive_messages(exec: nil, which: nil)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'openssl'")

expect { described_class.run("bundle", "install") }.to raise_error(RuntimeError)
end

Expand All @@ -47,49 +59,35 @@
expect(described_class).to receive(:which).and_return(Pathname("/usr/local/bin/bundle"))
allow(ENV).to receive(:prepend_path).with(any_args).and_call_original
expect(ENV).to receive(:prepend_path).with("PATH", "/usr/local/bin").once.and_call_original
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'openssl'")
described_class.run("bundle", "install")
end

describe "when running a command which exists but is not on the PATH" do
it "does not raise if the command is a relative path with current directory indicator" do
allow(described_class).to receive(:exec).with("./configure").and_return(nil)
expect(described_class).not_to receive(:which)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'zlib'")

expect { described_class.run("./configure") }.not_to raise_error
let(:brewfile_contents) { "brew 'zlib'" }

shared_examples "allows command execution" do |command|
it "does not raise" do
allow(described_class).to receive(:exec).with(command).and_return(nil)
expect(described_class).not_to receive(:which)
expect { described_class.run(command) }.not_to raise_error
end
end

it "does not raise if the command is a relative path without current directory indicator" do
allow(described_class).to receive(:exec).with("bin/install").and_return(nil)
expect(described_class).not_to receive(:which)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'zlib'")

expect { described_class.run("bin/install") }.not_to raise_error
end

it "does not raise if the command is an absolute path" do
allow(described_class).to receive(:exec).with("/Users/admin/Downloads/command").and_return(nil)
expect(described_class).not_to receive(:which)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'zlib'")

expect { described_class.run("/Users/admin/Downloads/command") }.not_to raise_error
end
it_behaves_like "allows command execution", "./configure"
it_behaves_like "allows command execution", "bin/install"
it_behaves_like "allows command execution", "/Users/admin/Downloads/command"
end

describe "when the Brewfile contains rbenv" do
before { ENV["HOMEBREW_RBENV_ROOT"] = rbenv_root.to_s }

let(:rbenv_root) { Pathname.new("/tmp/.rbenv") }
let(:brewfile_contents) { "brew 'rbenv'" }

before do
ENV["HOMEBREW_RBENV_ROOT"] = rbenv_root.to_s
end

it "prepends the path of the rbenv shims to PATH before running" do
allow(described_class).to receive(:exec).with("/usr/bin/true").and_return(0)
allow_any_instance_of(Pathname).to receive(:read)
.and_return("brew 'rbenv'")
allow(ENV).to receive(:fetch).with(any_args).and_call_original
allow(ENV).to receive(:prepend_path).with(any_args).once.and_call_original

Expand Down
Loading