mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

Replaces our serialization of child process errors via Marshal with JSON, preventing unintentional or malicious code execution outside of the build sandbox. Additionally, adds tests for the new behavior.
210 lines
6.2 KiB
Ruby
210 lines
6.2 KiB
Ruby
# This script is loaded by formula_installer as a separate instance.
|
|
# Thrown exceptions are propagated back to the parent process over a pipe
|
|
|
|
old_trap = trap("INT") { exit! 130 }
|
|
|
|
require "global"
|
|
require "build_options"
|
|
require "cxxstdlib"
|
|
require "keg"
|
|
require "extend/ENV"
|
|
require "debrew"
|
|
require "fcntl"
|
|
require "socket"
|
|
|
|
class Build
|
|
attr_reader :formula, :deps, :reqs
|
|
|
|
def initialize(formula, options)
|
|
@formula = formula
|
|
@formula.build = BuildOptions.new(options, formula.options)
|
|
|
|
if ARGV.ignore_deps?
|
|
@deps = []
|
|
@reqs = []
|
|
else
|
|
@deps = expand_deps
|
|
@reqs = expand_reqs
|
|
end
|
|
end
|
|
|
|
def post_superenv_hacks
|
|
# Only allow Homebrew-approved directories into the PATH, unless
|
|
# a formula opts-in to allowing the user's path.
|
|
return unless formula.env.userpaths? || reqs.any? { |rq| rq.env.userpaths? }
|
|
ENV.userpaths!
|
|
end
|
|
|
|
def effective_build_options_for(dependent)
|
|
args = dependent.build.used_options
|
|
args |= Tab.for_formula(dependent).used_options
|
|
BuildOptions.new(args, dependent.options)
|
|
end
|
|
|
|
def expand_reqs
|
|
formula.recursive_requirements do |dependent, req|
|
|
build = effective_build_options_for(dependent)
|
|
if req.prune_from_option?(build)
|
|
Requirement.prune
|
|
elsif req.prune_if_build_and_not_dependent?(dependent, formula)
|
|
Requirement.prune
|
|
end
|
|
end
|
|
end
|
|
|
|
def expand_deps
|
|
formula.recursive_dependencies do |dependent, dep|
|
|
build = effective_build_options_for(dependent)
|
|
if dep.prune_from_option?(build)
|
|
Dependency.prune
|
|
elsif dep.prune_if_build_and_not_dependent?(dependent, formula)
|
|
Dependency.prune
|
|
elsif dep.build?
|
|
Dependency.keep_but_prune_recursive_deps
|
|
end
|
|
end
|
|
end
|
|
|
|
def install
|
|
formula_deps = deps.map(&:to_formula)
|
|
keg_only_deps = formula_deps.select(&:keg_only?)
|
|
run_time_deps = deps.reject(&:build?).map(&:to_formula)
|
|
|
|
formula_deps.each do |dep|
|
|
fixopt(dep) unless dep.opt_prefix.directory?
|
|
end
|
|
|
|
ENV.activate_extensions!
|
|
|
|
if superenv?
|
|
ENV.keg_only_deps = keg_only_deps
|
|
ENV.deps = formula_deps
|
|
ENV.run_time_deps = run_time_deps
|
|
ENV.x11 = reqs.any? { |rq| rq.is_a?(X11Requirement) }
|
|
ENV.setup_build_environment(formula)
|
|
post_superenv_hacks
|
|
reqs.each(&:modify_build_environment)
|
|
deps.each(&:modify_build_environment)
|
|
else
|
|
ENV.setup_build_environment(formula)
|
|
reqs.each(&:modify_build_environment)
|
|
deps.each(&:modify_build_environment)
|
|
|
|
keg_only_deps.each do |dep|
|
|
ENV.prepend_path "PATH", dep.opt_bin.to_s
|
|
ENV.prepend_path "PKG_CONFIG_PATH", "#{dep.opt_lib}/pkgconfig"
|
|
ENV.prepend_path "PKG_CONFIG_PATH", "#{dep.opt_share}/pkgconfig"
|
|
ENV.prepend_path "ACLOCAL_PATH", "#{dep.opt_share}/aclocal"
|
|
ENV.prepend_path "CMAKE_PREFIX_PATH", dep.opt_prefix.to_s
|
|
ENV.prepend "LDFLAGS", "-L#{dep.opt_lib}" if dep.opt_lib.directory?
|
|
ENV.prepend "CPPFLAGS", "-I#{dep.opt_include}" if dep.opt_include.directory?
|
|
end
|
|
end
|
|
|
|
new_env = {
|
|
"TMPDIR" => HOMEBREW_TEMP,
|
|
"TEMP" => HOMEBREW_TEMP,
|
|
"TMP" => HOMEBREW_TEMP,
|
|
}
|
|
|
|
with_env(new_env) do
|
|
formula.extend(Debrew::Formula) if ARGV.debug?
|
|
|
|
formula.brew do |_formula, staging|
|
|
# For head builds, HOMEBREW_FORMULA_PREFIX should include the commit,
|
|
# which is not known until after the formula has been staged.
|
|
ENV["HOMEBREW_FORMULA_PREFIX"] = formula.prefix
|
|
|
|
staging.retain! if ARGV.keep_tmp?
|
|
formula.patch
|
|
|
|
if ARGV.git?
|
|
system "git", "init"
|
|
system "git", "add", "-A"
|
|
end
|
|
if ARGV.interactive?
|
|
ohai "Entering interactive mode"
|
|
puts "Type `exit' to return and finalize the installation"
|
|
puts "Install to this prefix: #{formula.prefix}"
|
|
|
|
if ARGV.git?
|
|
puts "This directory is now a git repo. Make your changes and then use:"
|
|
puts " git diff | pbcopy"
|
|
puts "to copy the diff to the clipboard."
|
|
end
|
|
|
|
interactive_shell(formula)
|
|
else
|
|
formula.prefix.mkpath
|
|
|
|
(formula.logs/"00.options.out").write \
|
|
"#{formula.full_name} #{formula.build.used_options.sort.join(" ")}".strip
|
|
formula.install
|
|
|
|
stdlibs = detect_stdlibs(ENV.compiler)
|
|
tab = Tab.create(formula, ENV.compiler, stdlibs.first)
|
|
tab.write
|
|
|
|
# Find and link metafiles
|
|
formula.prefix.install_metafiles formula.buildpath
|
|
formula.prefix.install_metafiles formula.libexec if formula.libexec.exist?
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def detect_stdlibs(compiler)
|
|
keg = Keg.new(formula.prefix)
|
|
CxxStdlib.check_compatibility(formula, deps, keg, compiler)
|
|
|
|
# The stdlib recorded in the install receipt is used during dependency
|
|
# compatibility checks, so we only care about the stdlib that libraries
|
|
# link against.
|
|
keg.detect_cxx_stdlibs(skip_executables: true)
|
|
end
|
|
|
|
def fixopt(f)
|
|
path = if f.linked_keg.directory? && f.linked_keg.symlink?
|
|
f.linked_keg.resolved_path
|
|
elsif f.prefix.directory?
|
|
f.prefix
|
|
elsif (kids = f.rack.children).size == 1 && kids.first.directory?
|
|
kids.first
|
|
else
|
|
raise
|
|
end
|
|
Keg.new(path).optlink
|
|
rescue
|
|
raise "#{f.opt_prefix} not present or broken\nPlease reinstall #{f.full_name}. Sorry :("
|
|
end
|
|
end
|
|
|
|
begin
|
|
error_pipe = UNIXSocket.open(ENV["HOMEBREW_ERROR_PIPE"], &:recv_io)
|
|
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
|
|
|
trap("INT", old_trap)
|
|
|
|
formula = ARGV.formulae.first
|
|
options = Options.create(ARGV.flags_only)
|
|
build = Build.new(formula, options)
|
|
build.install
|
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
error_hash = JSON.parse e.to_json
|
|
|
|
# Special case: need to recreate BuildErrors in full
|
|
# for proper analytics reporting and error messages.
|
|
# BuildErrors are specific to build processses and not other
|
|
# children, which is why we create the necessary state here
|
|
# and not in Utils.safe_fork.
|
|
if error_hash["json_class"] == "BuildError"
|
|
error_hash["cmd"] = e.cmd
|
|
error_hash["args"] = e.args
|
|
error_hash["env"] = e.env
|
|
end
|
|
|
|
error_pipe.puts error_hash.to_json
|
|
error_pipe.close
|
|
exit! 1
|
|
end
|