Skip to content

Commit

Permalink
Merge pull request #1573 from Homebrew/exec_env
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeMcQuaid authored Jan 23, 2025
2 parents d5825e0 + 3368bc3 commit 5dfc20b
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 50 deletions.
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

0 comments on commit 5dfc20b

Please sign in to comment.