brew/Library/Homebrew/cmd/upgrade.rb

371 lines
11 KiB
Ruby
Raw Normal View History

#: * `upgrade` [<install-options>] [`--cleanup`] [`--fetch-HEAD`] [`--ignore-pinned`] [`--display-times`] [<formulae>]:
#: Upgrade outdated, unpinned brews (with existing install options).
2016-04-08 16:28:43 +02:00
#:
#: Options for the `install` command are also valid here.
#:
#: If `--cleanup` is specified or `HOMEBREW_UPGRADE_CLEANUP` is set then remove
#: previously installed version(s) of upgraded <formulae>.
2016-04-08 16:28:43 +02:00
#:
2016-08-11 09:28:29 +02:00
#: If `--fetch-HEAD` is passed, fetch the upstream repository to detect if
#: the HEAD installation of the formula is outdated. Otherwise, the
#: repository's HEAD will be checked for updates when a new stable or devel
#: version has been released.
#:
#: If `--ignore-pinned` is passed, set a 0 exit code even if pinned formulae
#: are not upgraded.
#:
#: If `--display-times` is passed, install times for each formula are printed
#: at the end of the run.
#:
#: If <formulae> are given, upgrade only the specified brews (unless they
#: are pinned; see `pin`, `unpin`).
2016-04-08 16:28:43 +02:00
require "install"
require "reinstall"
require "formula_installer"
require "cleanup"
require "development_tools"
require "messages"
module Homebrew
2016-09-26 01:44:51 +02:00
module_function
def upgrade
FormulaInstaller.prevent_build_flags unless DevelopmentTools.installed?
Install.perform_preinstall_checks
if ARGV.named.empty?
outdated = Formula.installed.select do |f|
f.outdated?(fetch_head: ARGV.fetch_head?)
end
exit 0 if outdated.empty?
else
outdated = ARGV.resolved_formulae.select do |f|
f.outdated?(fetch_head: ARGV.fetch_head?)
end
2015-05-17 21:16:07 +08:00
(ARGV.resolved_formulae - outdated).each do |f|
2016-09-10 10:24:57 +01:00
versions = f.installed_kegs.map(&:version)
if versions.empty?
onoe "#{f.full_specified_name} not installed"
else
2015-11-29 15:37:06 +08:00
version = versions.max
onoe "#{f.full_specified_name} #{version} already installed"
end
end
exit 1 if outdated.empty?
end
pinned = outdated.select(&:pinned?)
outdated -= pinned
formulae_to_install = outdated.map(&:latest_formula)
if !pinned.empty? && !ARGV.include?("--ignore-pinned")
ofail "Not upgrading #{pinned.count} pinned #{"package".pluralize(pinned.count)}:"
puts pinned.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", "
end
if formulae_to_install.empty?
2016-09-10 10:24:57 +01:00
oh1 "No packages to upgrade"
else
oh1 "Upgrading #{formulae_to_install.count} outdated #{"package".pluralize(formulae_to_install.count)}:"
formulae_upgrades = formulae_to_install.map do |f|
if f.optlinked?
"#{f.full_specified_name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"#{f.full_specified_name} #{f.pkg_version}"
end
end
puts formulae_upgrades.join(", ")
end
upgrade_formulae(formulae_to_install)
check_dependents(formulae_to_install)
Homebrew.messages.display_messages
end
def upgrade_formulae(formulae_to_install)
return if formulae_to_install.empty?
# Sort keg-only before non-keg-only formulae to avoid any needless conflicts
# with outdated, non-keg-only versions of formulae being upgraded.
formulae_to_install.sort! do |a, b|
if !a.keg_only? && b.keg_only?
1
elsif a.keg_only? && !b.keg_only?
-1
else
0
end
end
formulae_to_install.each do |f|
Migrator.migrate_if_needed(f)
begin
upgrade_formula(f)
next if !ARGV.include?("--cleanup") && !ENV["HOMEBREW_UPGRADE_CLEANUP"]
next unless f.installed?
2018-09-17 02:45:00 +02:00
2018-08-08 11:20:53 +02:00
Cleanup.new.cleanup_formula(f)
rescue UnsatisfiedRequirements => e
Homebrew.failed = true
onoe "#{f}: #{e}"
end
end
end
def upgrade_formula(f)
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
keg_had_linked_opt = true
keg_was_linked = keg.linked?
end
formulae_maybe_with_kegs = [f] + f.old_installed_formulae
2016-09-19 16:04:32 +01:00
outdated_kegs = formulae_maybe_with_kegs
.map(&:linked_keg)
.select(&:directory?)
.map { |k| Keg.new(k.resolved_path) }
linked_kegs = outdated_kegs.select(&:linked?)
if f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
end
build_options = BuildOptions.new(Options.create(ARGV.flags_only), f.options)
options = build_options.used_options
options |= f.build.used_options
options &= f.options
fi = FormulaInstaller.new(f)
fi.options = options
fi.build_bottle = ARGV.build_bottle? || (!f.bottled? && f.build.bottle?)
fi.installed_on_request = !ARGV.named.empty?
fi.link_keg ||= keg_was_linked if keg_had_linked_opt
if tab
fi.installed_as_dependency = tab.installed_as_dependency
fi.installed_on_request ||= tab.installed_on_request
end
fi.prelude
2018-02-09 11:31:51 +09:00
oh1 "Upgrading #{Formatter.identifier(f.full_specified_name)} #{fi.options.to_a.join " "}"
# first we unlink the currently active keg for this formula otherwise it is
# possible for the existing build to interfere with the build we are about to
# do! Seriously, it happens!
outdated_kegs.each(&:unlink)
fi.install
fi.finish
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
2018-03-06 09:36:49 +00:00
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
ensure
# restore previous installation state if build failed
2016-09-10 10:24:57 +01:00
begin
linked_kegs.each(&:link) unless f.installed?
2016-09-10 10:24:57 +01:00
rescue
nil
end
end
def upgradable_dependents(kegs, formulae)
formulae_to_upgrade = Set.new
formulae_pinned = Set.new
formulae.each do |formula|
descendants = Set.new
dependents = kegs.select do |keg|
keg.runtime_dependencies
.any? { |d| d["full_name"] == formula.full_name }
end
next if dependents.empty?
dependent_formulae = dependents.map(&:to_formula)
dependent_formulae.each do |f|
next if formulae_to_upgrade.include?(f)
next if formulae_pinned.include?(f)
if f.outdated?(fetch_head: ARGV.fetch_head?)
if f.pinned?
formulae_pinned << f
else
formulae_to_upgrade << f
end
end
descendants << f
end
upgradable_descendants, pinned_descendants = upgradable_dependents(kegs, descendants)
formulae_to_upgrade.merge upgradable_descendants
formulae_pinned.merge pinned_descendants
end
[formulae_to_upgrade, formulae_pinned]
end
def broken_dependents(kegs, formulae)
formulae_to_reinstall = Set.new
formulae_pinned_and_outdated = Set.new
CacheStoreDatabase.use(:linkage) do |db|
formulae.each do |formula|
descendants = Set.new
dependents = kegs.select do |keg|
keg.runtime_dependencies
.any? { |d| d["full_name"] == formula.full_name }
end
next if dependents.empty?
dependents.each do |keg|
f = keg.to_formula
next if formulae_to_reinstall.include?(f)
next if formulae_pinned_and_outdated.include?(f)
checker = LinkageChecker.new(keg, cache_db: db)
if checker.broken_library_linkage?
if f.outdated?(fetch_head: ARGV.fetch_head?)
# Outdated formulae = pinned formulae (see function above)
formulae_pinned_and_outdated << f
else
formulae_to_reinstall << f
end
end
descendants << f
end
descendants_to_reinstall, descendants_pinned = broken_dependents(kegs, descendants)
formulae_to_reinstall.merge descendants_to_reinstall
formulae_pinned_and_outdated.merge descendants_pinned
end
end
[formulae_to_reinstall, formulae_pinned_and_outdated]
end
# @private
def depends_on(a, b)
if a.opt_or_installed_prefix_keg
.runtime_dependencies
.any? { |d| d["full_name"] == b.full_name }
1
else
a <=> b
end
end
# @private
def formulae_with_runtime_dependencies
Formula.installed
.map(&:opt_or_installed_prefix_keg)
.reject(&:nil?)
.reject { |f| f.runtime_dependencies.to_a.empty? }
end
def check_dependents(formulae)
return if formulae.empty?
# First find all the outdated dependents.
kegs = formulae_with_runtime_dependencies
return if kegs.empty?
oh1 "Checking dependents for outdated formulae" if ARGV.verbose?
upgradable, pinned = upgradable_dependents(kegs, formulae).map(&:to_a)
upgradable.sort! { |a, b| depends_on(a, b) }
pinned.sort! { |a, b| depends_on(a, b) }
# Print the pinned dependents.
unless pinned.empty?
ohai "Not upgrading #{pinned.count} pinned #{"dependent".pluralize(pinned.count)}:"
puts pinned.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", "
end
# Print the upgradable dependents.
if upgradable.empty?
ohai "No dependents to upgrade" if ARGV.verbose?
else
ohai "Upgrading #{upgradable.count} #{"dependent".pluralize(upgradable.count)}:"
formulae_upgrades = upgradable.map do |f|
if f.optlinked?
"#{f.full_specified_name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
else
"#{f.full_specified_name} #{f.pkg_version}"
end
end
puts formulae_upgrades.join(", ")
end
upgrade_formulae(upgradable)
# Assess the dependents tree again.
kegs = formulae_with_runtime_dependencies
oh1 "Checking dependents for broken library links" if ARGV.verbose?
reinstallable, pinned = broken_dependents(kegs, formulae).map(&:to_a)
reinstallable.sort! { |a, b| depends_on(a, b) }
pinned.sort! { |a, b| depends_on(a, b) }
# Print the pinned dependents.
unless pinned.empty?
onoe "Not reinstalling #{pinned.count} broken and outdated, but pinned #{"dependent".pluralize(pinned.count)}:"
$stderr.puts pinned.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", "
end
# Print the broken dependents.
if reinstallable.empty?
ohai "No broken dependents to reinstall" if ARGV.verbose?
else
ohai "Reinstalling #{reinstallable.count} broken #{"dependent".pluralize(reinstallable.count)} from source:"
puts reinstallable.map(&:full_specified_name).join(", ")
end
reinstallable.each do |f|
begin
reinstall_formula(f, build_from_source: true)
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to reinstall f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
nil
rescue CannotInstallFormulaError => e
ofail e
rescue BuildError => e
e.dump
puts
Homebrew.failed = true
rescue DownloadError => e
ofail e
end
end
end
end