brew/Library/Homebrew/formula_installer.rb

1135 lines
34 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require "cxxstdlib"
require "formula"
require "keg"
require "tab"
2016-04-25 17:57:51 +01:00
require "utils/bottles"
require "caveats"
require "cleaner"
require "formula_cellar_checks"
require "install_renamed"
require "debrew"
require "sandbox"
require "development_tools"
require "cache_store"
require "linkage_checker"
2018-06-11 20:45:06 -04:00
require "install"
require "messages"
require "cask/cask_loader"
require "cmd/install"
require "find"
class FormulaInstaller
include FormulaCellarChecks
extend Predicable
def self.mode_attr_accessor(*names)
attr_accessor(*names)
private(*names)
names.each do |name|
predicate = "#{name}?"
2016-09-20 22:24:31 +02:00
define_method(predicate) do
send(name) ? true : false
end
private(predicate)
end
end
attr_reader :formula
2020-07-07 11:29:33 +01:00
attr_accessor :options, :build_bottle, :installed_as_dependency, :installed_on_request, :link_keg
mode_attr_accessor :show_summary_heading, :show_header
mode_attr_accessor :build_from_source, :force_bottle, :include_test
2014-11-03 21:34:41 -06:00
mode_attr_accessor :ignore_deps, :only_deps, :interactive, :git
2020-02-05 19:43:59 +00:00
mode_attr_accessor :verbose, :debug, :quiet
def initialize(formula)
@formula = formula
@link_keg = !formula.keg_only?
@show_header = false
@ignore_deps = false
@only_deps = false
@build_from_source = Homebrew.args.build_from_source?
@build_bottle = false
@force_bottle = Homebrew.args.force_bottle?
@include_test = Homebrew.args.include_test?
2014-03-13 10:11:00 -05:00
@interactive = false
2014-11-03 21:34:41 -06:00
@git = false
@verbose = Homebrew.args.verbose?
2020-02-05 19:43:59 +00:00
@quiet = Homebrew.args.quiet?
@debug = Homebrew.args.debug?
@installed_as_dependency = false
@installed_on_request = true
@options = Options.new
@requirement_messages = []
2013-12-12 15:42:31 -06:00
@poured_bottle = false
@pour_failed = false
@start_time = nil
end
def self.attempted
@attempted ||= Set.new
end
def self.clear_attempted
@attempted = Set.new
end
def self.installed
@installed ||= Set.new
end
def self.clear_installed
@installed = Set.new
end
# When no build tools are available and build flags are passed through ARGV,
# it's necessary to interrupt the user before any sort of installation
# can proceed. Only invoked when the user has no developer tools.
def self.prevent_build_flags
build_flags = Homebrew.args.collect_build_args
return if build_flags.empty?
all_bottled = Homebrew.args.formulae.all?(&:bottled?)
raise BuildFlagsError.new(build_flags, bottled: all_bottled)
end
def build_bottle?
2016-09-20 22:24:31 +02:00
return false unless @build_bottle
2018-09-17 02:45:00 +02:00
2016-09-20 22:24:31 +02:00
!formula.bottle_disabled?
end
def pour_bottle?(install_bottle_options = { warn: false })
return false if @pour_failed
return false if !formula.bottled? && !formula.local_bottle_path
return true if force_bottle?
return false if build_from_source? || build_bottle? || interactive?
return false if Homebrew.args.cc
return false unless options.empty?
return false if formula.bottle_disabled?
2018-09-17 02:45:00 +02:00
unless formula.pour_bottle?
if install_bottle_options[:warn] && formula.pour_bottle_check_unsatisfied_reason
2017-10-15 02:28:32 +02:00
opoo <<~EOS
Building #{formula.full_name} from source:
#{formula.pour_bottle_check_unsatisfied_reason}
EOS
end
return false
end
bottle = formula.bottle_specification
unless bottle.compatible_cellar?
if install_bottle_options[:warn]
2017-10-15 02:28:32 +02:00
opoo <<~EOS
Building #{formula.full_name} from source:
The bottle needs a #{bottle.cellar} Cellar (yours is #{HOMEBREW_CELLAR}).
EOS
end
return false
end
true
end
def install_bottle_for?(dep, build)
return pour_bottle? if dep == formula
return false if Homebrew.args.build_formula_from_source?(dep)
return false unless dep.bottle && dep.pour_bottle?
return false unless build.used_options.empty?
return false unless dep.bottle.compatible_cellar?
2018-09-17 02:45:00 +02:00
true
end
def prelude
Tab.clear_cache
verify_deps_exist unless ignore_deps?
forbidden_license_check(formula)
check_install_sanity
end
def verify_deps_exist
begin
compute_dependencies
rescue TapFormulaUnavailableError => e
2016-09-23 22:02:23 +02:00
raise if e.tap.installed?
e.tap.install
retry
end
rescue FormulaUnavailableError => e
e.dependent = formula.full_name
raise
end
def check_install_sanity
raise FormulaInstallationAlreadyAttemptedError, formula if self.class.attempted.include?(formula)
2020-04-01 13:42:52 +01:00
if formula.deprecated?
opoo "#{formula.full_name} has been deprecated!"
elsif formula.disabled?
odie "#{formula.full_name} has been disabled!"
end
return if ignore_deps?
2016-09-23 22:02:23 +02:00
recursive_deps = formula.recursive_dependencies
recursive_formulae = recursive_deps.map(&:to_formula)
recursive_dependencies = []
recursive_formulae.each do |dep|
dep_recursive_dependencies = dep.recursive_dependencies.map(&:to_s)
if dep_recursive_dependencies.include?(formula.name)
recursive_dependencies << "#{formula.full_name} depends on #{dep.full_name}"
recursive_dependencies << "#{dep.full_name} depends on #{formula.full_name}"
end
end
unless recursive_dependencies.empty?
2017-10-15 02:28:32 +02:00
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} contains a recursive dependency on itself:
#{recursive_dependencies.join("\n ")}
EOS
end
if recursive_formulae.flat_map(&:recursive_dependencies)
.map(&:to_s)
.include?(formula.name)
2017-10-15 02:28:32 +02:00
raise CannotInstallFormulaError, <<~EOS
#{formula.full_name} contains a recursive dependency on itself!
EOS
end
2016-09-23 22:02:23 +02:00
pinned_unsatisfied_deps = recursive_deps.select do |dep|
dep.to_formula.pinned? && !dep.satisfied?(inherited_options_for(dep))
end
2016-09-23 22:02:23 +02:00
return if pinned_unsatisfied_deps.empty?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
raise CannotInstallFormulaError,
2019-04-30 08:44:35 +01:00
"You must `brew unpin #{pinned_unsatisfied_deps * " "}` as installing " \
"#{formula.full_name} requires the latest version of pinned dependencies"
end
def build_bottle_preinstall
@etc_var_dirs ||= [HOMEBREW_PREFIX/"etc", HOMEBREW_PREFIX/"var"]
@etc_var_preinstall = Find.find(*@etc_var_dirs.select(&:directory?)).to_a
end
def build_bottle_postinstall
@etc_var_postinstall = Find.find(*@etc_var_dirs.select(&:directory?)).to_a
(@etc_var_postinstall - @etc_var_preinstall).each do |file|
Pathname.new(file).cp_path_sub(HOMEBREW_PREFIX, formula.bottle_prefix)
end
end
def install
lock
start_time = Time.now
if !formula.bottle_unneeded? && !pour_bottle? && DevelopmentTools.installed?
Homebrew::Install.perform_build_from_source_checks
end
# not in initialize so upgrade can unlink the active keg before calling this
# function but after instantiating this class so that it can avoid having to
# relink the active keg if possible (because it is slow).
if formula.linked_keg.directory?
2017-10-15 02:28:32 +02:00
message = <<~EOS
#{formula.name} #{formula.linked_version} is already installed
EOS
if formula.outdated? && !formula.head?
message += <<~EOS
To upgrade to #{formula.pkg_version}, run `brew upgrade #{formula.full_name}`.
EOS
elsif only_deps?
message = nil
else
# some other version is already installed *and* linked
message += <<~EOS
2019-04-08 12:47:15 -04:00
To install #{formula.pkg_version}, first run `brew unlink #{formula.name}`.
EOS
end
raise CannotInstallFormulaError, message if message
end
# Warn if a more recent version of this formula is available in the tap.
begin
if formula.pkg_version < (v = Formulary.factory(formula.full_name).pkg_version)
opoo "#{formula.full_name} #{v} is available and more recent than version #{formula.pkg_version}."
end
rescue FormulaUnavailableError
nil
end
check_conflicts
raise BuildToolsError, [formula] if !pour_bottle? && !formula.bottle_unneeded? && !DevelopmentTools.installed?
unless ignore_deps?
deps = compute_dependencies
check_dependencies_bottled(deps) if pour_bottle? && !DevelopmentTools.installed?
install_dependencies(deps)
end
return if only_deps?
if build_bottle? && (arch = Homebrew.args.bottle_arch) && !Hardware::CPU.optimization_flags.include?(arch.to_sym)
raise "Unrecognized architecture for --bottle-arch: #{arch}"
end
2014-12-27 14:26:56 -05:00
formula.deprecated_flags.each do |deprecated_option|
old_flag = deprecated_option.old_flag
new_flag = deprecated_option.current_flag
opoo "#{formula.full_name}: #{old_flag} was deprecated; using #{new_flag} instead!"
end
options = display_options(formula).join(" ")
oh1 "Installing #{Formatter.identifier(formula.full_name)} #{options}".strip if show_header?
2017-09-24 20:12:58 +01:00
unless formula.tap&.private?
action = "#{formula.full_name} #{options}".strip
Utils::Analytics.report_event("install", action)
Utils::Analytics.report_event("install_on_request", action) if installed_on_request
end
self.class.attempted << formula
if pour_bottle?
2014-10-17 22:41:26 -05:00
begin
pour
rescue Exception => e # rubocop:disable Lint/RescueException
# any exceptions must leave us with nothing installed
ignore_interrupts do
begin
formula.prefix.rmtree if formula.prefix.directory?
rescue Errno::EACCES, Errno::ENOTEMPTY
odie <<~EOS
Could not remove #{formula.prefix.basename} keg! Do so manually:
sudo rm -rf #{formula.prefix}
EOS
end
formula.rack.rmdir_if_possible
end
2020-04-05 15:44:50 +01:00
raise if Homebrew::EnvConfig.developer? ||
Homebrew::EnvConfig.no_bottle_source_fallback? ||
e.is_a?(Interrupt)
2018-09-17 02:45:00 +02:00
2014-10-17 22:41:26 -05:00
@pour_failed = true
onoe e.message
opoo "Bottle installation failed: building from source."
raise BuildToolsError, [formula] unless DevelopmentTools.installed?
2018-09-17 02:45:00 +02:00
compute_and_install_dependencies unless ignore_deps?
2014-10-17 22:41:26 -05:00
else
@poured_bottle = true
end
end
puts_requirement_messages
build_bottle_preinstall if build_bottle?
unless @poured_bottle
build
clean
# Store the formula used to build the keg in the keg.
formula_contents = if formula.local_bottle_path
Utils::Bottles.formula_contents formula.local_bottle_path, name: formula.name
else
formula.path.read
end
s = formula_contents.gsub(/ bottle do.+?end\n\n?/m, "")
brew_prefix = formula.prefix/".brew"
brew_prefix.mkdir
Pathname(brew_prefix/"#{formula.name}.rb").atomic_write(s)
keg = Keg.new(formula.prefix)
tab = Tab.for_keg(keg)
tab.installed_as_dependency = installed_as_dependency
tab.installed_on_request = installed_on_request
tab.write
end
build_bottle_postinstall if build_bottle?
opoo "Nothing was installed to #{formula.prefix}" unless formula.latest_version_installed?
end_time = Time.now
Homebrew.messages.formula_installed(formula, end_time - start_time)
end
def check_conflicts
return if Homebrew.args.force?
conflicts = formula.conflicts.select do |c|
f = Formulary.factory(c.name)
rescue TapFormulaUnavailableError
# If the formula name is a fully-qualified name let's silently
# ignore it as we don't care about things used in taps that aren't
# currently tapped.
false
rescue FormulaUnavailableError => e
# If the formula name doesn't exist any more then complain but don't
# stop installation from continuing.
opoo <<~EOS
2019-10-13 10:11:35 +01:00
#{formula}: #{e.message}
'conflicts_with \"#{c.name}\"' should be removed from #{formula.path.basename}.
EOS
2016-09-23 22:02:23 +02:00
2020-04-05 15:44:50 +01:00
raise if Homebrew::EnvConfig.developer?
2016-09-23 22:02:23 +02:00
$stderr.puts "Please report this issue to the #{formula.tap} tap (not Homebrew/brew or Homebrew/core)!"
false
2019-10-16 19:10:52 +02:00
else # rubocop:disable Layout/ElseAlignment
f.linked_keg.exist? && f.opt_prefix.exist?
end
raise FormulaConflictError.new(formula, conflicts) unless conflicts.empty?
end
# Compute and collect the dependencies needed by the formula currently
# being installed.
def compute_dependencies
req_map, req_deps = expand_requirements
check_requirements(req_map)
2020-07-07 11:29:33 +01:00
expand_dependencies(req_deps + formula.deps)
end
# Check that each dependency in deps has a bottle available, terminating
# abnormally with a BuildToolsError if one or more don't.
# Only invoked when the user has no developer tools.
def check_dependencies_bottled(deps)
unbottled = deps.reject do |dep, _|
dep_f = dep.to_formula
dep_f.pour_bottle? || dep_f.bottle_unneeded?
end
raise BuildToolsError, unbottled unless unbottled.empty?
end
def compute_and_install_dependencies
deps = compute_dependencies
install_dependencies(deps)
end
def check_requirements(req_map)
@requirement_messages = []
fatals = []
req_map.each_pair do |dependent, reqs|
reqs.each do |req|
formula_installer: Fix `.installed?` method deprecation - Trying to test out a user-submitted `brew bump-formula-pr` for `app-engine-java` gave an error locally that [hasn't shown up on CI](https://github.com/Homebrew/homebrew-core/pull/55798/checks?check_run_id=740165542), oddly. ``` ➜ brew install app-engine-java -s && brew test app-engine-java Error: Calling Formula#installed? is deprecated! Use Formula#latest_version_installed? (or Formula#any_version_installed? ) instead. /usr/local/Homebrew/Library/Homebrew/compat/formula.rb:6:in `installed?' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:421:in `block (2 levels) in check_requirements' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:420:in `each' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:420:in `block in check_requirements' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:419:in `each_pair' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:419:in `check_requirements' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:392:in `compute_dependencies' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:149:in `verify_deps_exist' /usr/local/Homebrew/Library/Homebrew/formula_installer.rb:143:in `prelude' /usr/local/Homebrew/Library/Homebrew/cmd/install.rb:328:in `install_formula' /usr/local/Homebrew/Library/Homebrew/cmd/install.rb:261:in `block in install' /usr/local/Homebrew/Library/Homebrew/cmd/install.rb:259:in `each' /usr/local/Homebrew/Library/Homebrew/cmd/install.rb:259:in `install' /usr/local/Homebrew/Library/Homebrew/brew.rb:110:in `<main>' ```
2020-06-04 22:46:47 +01:00
next if dependent.latest_version_installed? && req.name == "maximummacos"
2018-09-17 02:45:00 +02:00
@requirement_messages << "#{dependent}: #{req.message}"
fatals << req if req.fatal?
end
end
return if fatals.empty?
puts_requirement_messages
raise UnsatisfiedRequirements, fatals
end
def runtime_requirements(formula)
runtime_deps = formula.runtime_formula_dependencies(undeclared: false)
recursive_requirements = formula.recursive_requirements do |dependent, _|
Requirement.prune unless runtime_deps.include?(dependent)
end
(recursive_requirements.to_a + formula.requirements.to_a).reject(&:build?).uniq
end
def expand_requirements
unsatisfied_reqs = Hash.new { |h, k| h[k] = [] }
req_deps = []
formulae = [formula]
formula_deps_map = Dependency.expand(formula)
.each_with_object({}) do |dep, hash|
hash[dep.name] = dep
end
while f = formulae.pop
runtime_requirements = runtime_requirements(f)
2014-06-19 21:35:47 -05:00
f.recursive_requirements do |dependent, req|
build = effective_build_options_for(dependent)
install_bottle_for_dependent = install_bottle_for?(dependent, build)
2014-06-19 21:35:47 -05:00
keep_build_test = false
keep_build_test ||= runtime_requirements.include?(req)
keep_build_test ||= req.test? && include_test? && dependent == f
keep_build_test ||= req.build? && !install_bottle_for_dependent && !dependent.latest_version_installed?
if req.prune_from_option?(build)
2014-06-19 21:35:47 -05:00
Requirement.prune
elsif req.satisfied?
Requirement.prune
elsif (req.build? || req.test?) && !keep_build_test
Requirement.prune
elsif (dep = formula_deps_map[dependent.name]) && dep.build?
Requirement.prune
2014-06-19 21:35:47 -05:00
else
unsatisfied_reqs[dependent] << req
end
end
end
# Merge the repeated dependencies, which may have different tags.
req_deps = Dependency.merge_repeats(req_deps)
[unsatisfied_reqs, req_deps]
end
def expand_dependencies(deps)
inherited_options = Hash.new { |hash, key| hash[key] = Options.new }
pour_bottle = pour_bottle?
expanded_deps = Dependency.expand(formula, deps) do |dependent, dep|
inherited_options[dep.name] |= inherited_options_for(dep)
2014-06-19 21:35:47 -05:00
build = effective_build_options_for(
dependent,
inherited_options.fetch(dependent.name, []),
2014-06-19 21:35:47 -05:00
)
keep_build_test = false
keep_build_test ||= dep.test? && include_test? && Homebrew.args.include_formula_test_deps?(dependent)
keep_build_test ||= dep.build? && !install_bottle_for?(dependent, build) && !dependent.latest_version_installed?
if dep.prune_from_option?(build)
2014-06-19 21:35:47 -05:00
Dependency.prune
elsif (dep.build? || dep.test?) && !keep_build_test
Dependency.prune
elsif dep.satisfied?(inherited_options[dep.name])
2014-06-19 21:35:47 -05:00
Dependency.skip
else
pour_bottle ||= install_bottle_for?(dep.to_formula, build)
2013-06-03 22:50:11 -05:00
end
end
if pour_bottle && !Keg.bottle_dependencies.empty?
bottle_deps = if !Keg.bottle_dependencies.include?(formula.name)
Keg.bottle_dependencies
elsif !Keg.relocation_formulae.include?(formula.name)
Keg.relocation_formulae
else
[]
end
bottle_deps = bottle_deps.map { |formula| Dependency.new(formula) }
.reject do |dep|
inherited_options[dep.name] |= inherited_options_for(dep)
dep.satisfied? inherited_options[dep.name]
end
expanded_deps = Dependency.merge_repeats(bottle_deps + expanded_deps)
end
expanded_deps.map { |dep| [dep, inherited_options[dep.name]] }
end
def effective_build_options_for(dependent, inherited_options = [])
args = dependent.build.used_options
2017-05-29 18:24:52 +01:00
args |= (dependent == formula) ? options : inherited_options
args |= Tab.for_formula(dependent).used_options
args &= dependent.options
BuildOptions.new(args, dependent.options)
2014-03-02 14:02:18 -06:00
end
def display_options(formula)
options = if formula.head?
["--HEAD"]
elsif formula.devel?
["--devel"]
else
[]
end
options += effective_build_options_for(formula).used_options.to_a
options
end
def inherited_options_for(dep)
inherited_options = Options.new
2014-07-31 19:37:39 -05:00
u = Option.new("universal")
if (options.include?(u) || formula.require_universal_deps?) && !dep.build? && dep.to_formula.option_defined?(u)
2014-07-31 19:37:39 -05:00
inherited_options << u
end
inherited_options
end
def install_dependencies(deps)
if deps.empty? && only_deps?
puts "All dependencies for #{formula.full_name} are satisfied."
elsif !deps.empty?
oh1 "Installing dependencies for #{formula.full_name}: " \
"#{deps.map(&:first).map(&Formatter.method(:identifier)).to_sentence}",
truncate: false
deps.each { |dep, options| install_dependency(dep, options) }
end
@show_header = true unless deps.empty?
end
def fetch_dependency(dep)
df = dep.to_formula
fi = FormulaInstaller.new(df)
fi.build_from_source = Homebrew.args.build_formula_from_source?(df)
fi.force_bottle = false
fi.include_test = Homebrew.args.include_formula_test_deps?(df)
fi.verbose = verbose?
fi.quiet = quiet?
fi.debug = debug?
# When fetching we don't need to recurse the dependency tree as it's already
# been done for us in `compute_dependencies` and there's no requirement to
# fetch in a particular order.
fi.ignore_deps = true
fi.fetch
end
def install_dependency(dep, inherited_options)
df = dep.to_formula
tab = Tab.for_formula(df)
if df.linked_keg.directory?
linked_keg = Keg.new(df.linked_keg.resolved_path)
keg_had_linked_keg = true
2017-07-30 20:04:41 +01:00
keg_was_linked = linked_keg.linked?
linked_keg.unlink
end
if df.latest_version_installed?
installed_keg = Keg.new(df.prefix)
tmp_keg = Pathname.new("#{installed_keg}.tmp")
installed_keg.rename(tmp_keg)
end
tab_tap = tab.source["tap"]
if tab_tap.present? && df.tap.present? && df.tap.to_s != tab_tap.to_s
odie <<~EOS
#{df} is already installed from #{tab_tap}!
Please `brew uninstall #{df}` first."
EOS
end
fi = FormulaInstaller.new(df)
fi.options |= tab.used_options
fi.options |= Tab.remap_deprecated_options(df.deprecated_options, dep.options)
fi.options |= inherited_options
fi.options &= df.options
fi.build_from_source = Homebrew.args.build_formula_from_source?(df)
fi.force_bottle = false
fi.include_test = Homebrew.args.include_formula_test_deps?(df)
fi.verbose = verbose?
2020-02-05 19:43:59 +00:00
fi.quiet = quiet?
fi.debug = debug?
fi.link_keg ||= keg_was_linked if keg_had_linked_keg
fi.installed_as_dependency = true
fi.installed_on_request = df.any_version_installed? && tab.installed_on_request
fi.prelude
2016-08-30 21:38:13 +02:00
oh1 "Installing #{formula.full_name} dependency: #{Formatter.identifier(dep.name)}"
fi.install
fi.finish
rescue Exception => e # rubocop:disable Lint/RescueException
2014-04-05 10:48:54 -05:00
ignore_interrupts do
tmp_keg.rename(installed_keg) if tmp_keg && !installed_keg.directory?
linked_keg.link if keg_was_linked
2014-04-05 10:48:54 -05:00
end
raise unless e.is_a? FormulaInstallationAlreadyAttemptedError
# We already attempted to install f as part of another formula's
# dependency tree. In that case, don't generate an error, just move on.
nil
else
2017-09-24 19:24:46 +01:00
ignore_interrupts { tmp_keg.rmtree if tmp_keg&.directory? }
end
def caveats
return if only_deps?
2020-04-05 15:44:50 +01:00
audit_installed if Homebrew::EnvConfig.developer?
caveats = Caveats.new(formula)
return if caveats.empty?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
@show_summary_heading = true
ohai "Caveats", caveats.to_s
Homebrew.messages.record_caveats(formula, caveats)
end
def finish
return if only_deps?
ohai "Finishing up" if verbose?
install_plist
keg = Keg.new(formula.prefix)
2014-07-24 19:39:09 -05:00
link(keg)
fix_dynamic_linkage(keg) unless @poured_bottle && formula.bottle_specification.skip_relocation?
if build_bottle?
ohai "Not running post_install as we're building a bottle"
puts "You can run it manually using `brew postinstall #{formula.full_name}`"
else
post_install
end
# Updates the cache for a particular formula after doing an install
2018-05-22 14:46:14 +01:00
CacheStoreDatabase.use(:linkage) do |db|
break unless db.created?
2018-09-17 02:45:00 +02:00
LinkageChecker.new(keg, formula, cache_db: db, rebuild_cache: true)
end
# Update tab with actual runtime dependencies
tab = Tab.for_keg(keg)
Tab.clear_cache
f_runtime_deps = formula.runtime_dependencies(read_from_tab: false)
tab.runtime_dependencies = Tab.runtime_deps_hash(f_runtime_deps)
tab.write
# let's reset Utils.git_available? if we just installed git
Utils.clear_git_available_cache if formula.name == "git"
# use installed curl when it's needed and available
if formula.name == "curl" &&
!DevelopmentTools.curl_handles_most_https_certificates?
ENV["HOMEBREW_CURL"] = formula.opt_bin/"curl"
end
caveats
ohai "Summary" if verbose? || show_summary_heading?
puts summary
self.class.installed << formula
ensure
unlock
end
def summary
2019-04-20 14:07:29 +09:00
s = +""
2020-04-05 15:44:50 +01:00
s << "#{Homebrew::EnvConfig.install_badge} " unless Homebrew::EnvConfig.no_emoji?
s << "#{formula.prefix.resolved_path}: #{formula.prefix.abv}"
s << ", built in #{pretty_duration build_time}" if build_time
s.freeze
end
def build_time
2014-03-13 14:16:15 -05:00
@build_time ||= Time.now - @start_time if @start_time && !interactive?
end
2016-09-21 09:07:04 +02:00
def sanitized_argv_options
args = []
args << "--ignore-dependencies" if ignore_deps?
if build_bottle?
args << "--build-bottle"
args << "--bottle-arch=#{Homebrew.args.bottle_arch}" if Homebrew.args.bottle_arch
end
2014-11-03 21:34:41 -06:00
args << "--git" if git?
args << "--interactive" if interactive?
2014-03-13 10:11:00 -05:00
args << "--verbose" if verbose?
2014-03-13 10:11:00 -05:00
args << "--debug" if debug?
args << "--cc=#{Homebrew.args.cc}" if Homebrew.args.cc
2020-03-15 17:02:31 +05:30
args << "--keep-tmp" if Homebrew.args.keep_tmp?
if Homebrew.args.env.present?
args << "--env=#{Homebrew.args.env}"
elsif formula.env.std? || formula.deps.select(&:build?).any? { |d| d.name == "scons" }
args << "--env=std"
end
2014-06-19 21:35:47 -05:00
if formula.head?
args << "--HEAD"
elsif formula.devel?
args << "--devel"
2014-06-19 21:35:47 -05:00
end
formula.options.each do |opt|
name = opt.name[/^([^=]+)=$/, 1]
value = Homebrew.args.value(name) if name
args << "--#{name}=#{value}" if value
end
args
end
def build_argv
2016-09-21 09:07:04 +02:00
sanitized_argv_options + options.as_flags
end
def build
2015-04-25 22:07:06 -04:00
FileUtils.rm_rf(formula.logs)
@start_time = Time.now
# 1. formulae can modify ENV, so we must ensure that each
# installation has a pristine ENV when it starts, forking now is
# the easiest way to do this
args = %W[
nice #{RUBY_PATH}
-W0
2018-07-17 14:23:33 +02:00
-I #{$LOAD_PATH.join(File::PATH_SEPARATOR)}
--
#{HOMEBREW_LIBRARY_PATH}/build.rb
2016-09-05 22:13:55 +01:00
#{formula.specified_path}
].concat(build_argv)
Utils.safe_fork do
if Sandbox.available?
2015-04-13 18:05:15 +08:00
sandbox = Sandbox.new
2015-04-25 22:07:06 -04:00
formula.logs.mkpath
2016-05-27 01:53:08 -04:00
sandbox.record_log(formula.logs/"build.sandbox.log")
2020-03-14 19:49:15 +05:30
sandbox.allow_write_path(ENV["HOME"]) if Homebrew.args.interactive?
2015-04-13 18:05:15 +08:00
sandbox.allow_write_temp_and_cache
sandbox.allow_write_log(formula)
2018-07-01 23:35:29 +02:00
sandbox.allow_cvs
sandbox.allow_fossil
sandbox.allow_write_xcode
2015-04-13 18:05:15 +08:00
sandbox.allow_write_cellar(formula)
sandbox.exec(*args)
else
exec(*args)
end
end
2016-07-13 10:11:59 +03:00
formula.update_head_version
raise "Empty installation" if !formula.prefix.directory? || Keg.new(formula.prefix).empty_installation?
rescue Exception => e # rubocop:disable Lint/RescueException
if e.is_a? BuildError
e.formula = formula
e.options = display_options(formula)
end
ignore_interrupts do
# any exceptions must leave us with nothing installed
2016-07-13 10:11:59 +03:00
formula.update_head_version
formula.prefix.rmtree if formula.prefix.directory?
formula.rack.rmdir_if_possible
end
raise e
end
def link(keg)
unless link_keg
2014-07-24 19:39:09 -05:00
begin
keg.optlink
Formula.clear_cache
rescue Keg::LinkError => e
onoe "Failed to create #{formula.opt_prefix}"
puts "Things that depend on #{formula.full_name} will probably not build."
puts e
Homebrew.failed = true
2014-07-24 19:39:09 -05:00
end
return
end
cask_installed_with_formula_name = begin
Cask::CaskLoader.load(formula.name).installed?
rescue Cask::CaskUnavailableError, Cask::CaskInvalidError
false
end
if cask_installed_with_formula_name
ohai "#{formula.name} cask is installed, skipping link."
return
end
if keg.linked?
opoo "This keg was marked linked already, continuing anyway"
keg.remove_linked_keg_record
end
link_overwrite_backup = {} # Hash: conflict file -> backup file
backup_dir = HOMEBREW_CACHE/"Backup"
begin
keg.link
rescue Keg::ConflictError => e
conflict_file = e.dst
if formula.link_overwrite?(conflict_file) && !link_overwrite_backup.key?(conflict_file)
backup_file = backup_dir/conflict_file.relative_path_from(HOMEBREW_PREFIX).to_s
backup_file.parent.mkpath
conflict_file.rename backup_file
link_overwrite_backup[conflict_file] = backup_file
retry
end
onoe "The `brew link` step did not complete successfully"
puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
puts e
puts
puts "Possible conflicting files are:"
mode = OpenStruct.new(dry_run: true, overwrite: true)
keg.link(mode)
@show_summary_heading = true
Homebrew.failed = true
rescue Keg::LinkError => e
onoe "The `brew link` step did not complete successfully"
puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
puts e
puts
puts "You can try again using:"
puts " brew link #{formula.name}"
@show_summary_heading = true
Homebrew.failed = true
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "An unexpected error occurred during the `brew link` step"
puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
puts e
puts e.backtrace if debug?
@show_summary_heading = true
ignore_interrupts do
keg.unlink
link_overwrite_backup.each do |origin, backup|
origin.parent.mkpath
backup.rename origin
end
end
Homebrew.failed = true
raise
end
2016-09-23 22:02:23 +02:00
return if link_overwrite_backup.empty?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
opoo "These files were overwritten during `brew link` step:"
puts link_overwrite_backup.keys
puts
puts "They have been backed up in #{backup_dir}"
@show_summary_heading = true
end
def install_plist
return unless formula.plist
2018-09-17 02:45:00 +02:00
formula.plist_path.atomic_write(formula.plist)
formula.plist_path.chmod 0644
log = formula.var/"log"
log.mkpath if formula.plist.include? log.to_s
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Failed to install plist file"
ohai e, e.backtrace if debug?
Homebrew.failed = true
end
def fix_dynamic_linkage(keg)
keg.fix_dynamic_linkage
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "Failed to fix install linkage"
puts "The formula built, but you may encounter issues using it or linking other"
2019-04-08 12:47:15 -04:00
puts "formulae against it."
2014-03-13 10:11:00 -05:00
ohai e, e.backtrace if debug?
Homebrew.failed = true
@show_summary_heading = true
end
def clean
2014-03-13 10:11:00 -05:00
ohai "Cleaning" if verbose?
Cleaner.new(formula).clean
rescue Exception => e # rubocop:disable Lint/RescueException
opoo "The cleaning step did not complete successfully"
puts "Still, the installation was successful, so we will link it into your prefix"
2014-03-13 10:11:00 -05:00
ohai e, e.backtrace if debug?
Homebrew.failed = true
@show_summary_heading = true
end
def post_install
args = %W[
nice #{RUBY_PATH}
-W0
2018-07-17 14:23:33 +02:00
-I #{$LOAD_PATH.join(File::PATH_SEPARATOR)}
--
#{HOMEBREW_LIBRARY_PATH}/postinstall.rb
#{formula.path}
]
Utils.safe_fork do
if Sandbox.available?
sandbox = Sandbox.new
formula.logs.mkpath
sandbox.record_log(formula.logs/"postinstall.sandbox.log")
sandbox.allow_write_temp_and_cache
sandbox.allow_write_log(formula)
sandbox.allow_write_xcode
sandbox.deny_write_homebrew_repository
sandbox.allow_write_cellar(formula)
Keg::KEG_LINK_DIRECTORIES.each do |dir|
sandbox.allow_write_path "#{HOMEBREW_PREFIX}/#{dir}"
end
sandbox.exec(*args)
else
exec(*args)
end
end
rescue Exception => e # rubocop:disable Lint/RescueException
opoo "The post-install step did not complete successfully"
puts "You can try again using `brew postinstall #{formula.full_name}`"
2020-04-05 15:44:50 +01:00
ohai e, e.backtrace if debug? || Homebrew::EnvConfig.developer?
Homebrew.failed = true
@show_summary_heading = true
end
def fetch_dependencies
return if ignore_deps?
deps = compute_dependencies
return if deps.empty?
deps.each { |dep, _options| fetch_dependency(dep) }
end
def fetch
fetch_dependencies
return if only_deps?
if pour_bottle?(warn: true)
begin
downloader.fetch
rescue Exception => e # rubocop:disable Lint/RescueException
raise if Homebrew::EnvConfig.developer? ||
Homebrew::EnvConfig.no_bottle_source_fallback? ||
e.is_a?(Interrupt)
@pour_failed = true
onoe e.message
opoo "Bottle installation failed: building from source."
fetch_dependencies
end
end
return if pour_bottle?
formula.fetch_patches
formula.resources.each(&:fetch)
downloader.fetch
end
def downloader
if (bottle_path = formula.local_bottle_path)
LocalBottleDownloadStrategy.new(bottle_path)
elsif pour_bottle?
formula.bottle
else
formula
end
end
def pour
HOMEBREW_CELLAR.cd do
downloader.stage
end
keg = Keg.new(formula.prefix)
tab = Tab.for_keg(keg)
Tab.clear_cache
skip_linkage = formula.bottle_specification.skip_relocation?
keg.replace_placeholders_with_locations tab.changed_files, skip_linkage: skip_linkage
tab = Tab.for_keg(keg)
2014-10-17 22:40:03 -05:00
CxxStdlib.check_compatibility(
formula, formula.recursive_dependencies,
Keg.new(formula.prefix), tab.compiler
2014-10-17 22:40:03 -05:00
)
tab.tap = formula.tap
2014-10-17 22:40:03 -05:00
tab.poured_from_bottle = true
tab.time = Time.now.to_i
tab.head = HOMEBREW_REPOSITORY.git_head
tab.source["path"] = formula.specified_path.to_s
tab.installed_as_dependency = installed_as_dependency
tab.installed_on_request = installed_on_request
tab.aliases = formula.aliases
2014-10-17 22:40:03 -05:00
tab.write
end
def problem_if_output(output)
2016-09-23 22:02:23 +02:00
return unless output
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
opoo output
@show_summary_heading = true
end
def audit_installed
unless formula.keg_only?
problem_if_output(check_env_path(formula.bin))
problem_if_output(check_env_path(formula.sbin))
end
super
end
def self.locked
@locked ||= []
end
private
attr_predicate :hold_locks?
def lock
return unless self.class.locked.empty?
2018-09-17 02:45:00 +02:00
unless ignore_deps?
formula.recursive_dependencies.each do |dep|
self.class.locked << dep.to_formula
end
end
self.class.locked.unshift(formula)
self.class.locked.uniq!
self.class.locked.each(&:lock)
2016-09-23 22:02:23 +02:00
@hold_locks = true
end
def unlock
2016-09-23 22:02:23 +02:00
return unless hold_locks?
2018-09-17 02:45:00 +02:00
self.class.locked.each(&:unlock)
self.class.locked.clear
2016-09-23 22:02:23 +02:00
@hold_locks = false
end
def puts_requirement_messages
return unless @requirement_messages
return if @requirement_messages.empty?
2018-09-17 02:45:00 +02:00
$stderr.puts @requirement_messages
end
def env_forbidden_licenses
Homebrew::EnvConfig.forbidden_licenses.split(" ")
end
def forbidden_license_check(f)
return if ENV["HOMEBREW_FORBIDDEN_LICENSES"].blank?
forbidden_licenses = env_forbidden_licenses
if forbidden_licenses.include? f.license
raise CannotInstallFormulaError, <<~EOS
#{f.name} has a forbidden license #{f.license}.
EOS
end
fi = FormulaInstaller.new(f)
fi.compute_dependencies.each do |dep, _|
dep_f = dep.to_formula
next unless forbidden_licenses.include? dep_f.license
raise CannotInstallFormulaError, <<~EOS
The installation of #{f.name} has a dependency on #{dep.name} with a forbidden license #{dep_f.license}.
EOS
end
end
end