brew/Library/Homebrew/cmd/upgrade.rb
apainintheneck 200452e23d cmd/upgrade: required named args when upgrading from source
The --build-from-source flag can currently be used without specifying
a formula by name but it doesn't behave the way you'd expect it to.

It will upgrade everything using bottles if they are available and skip
building things from source. This is intentional because we want to
discourage non-developers from building packages from source since the
result is less predictable.

The change here is to error out in that case. It might be smarter to
go through the entire deprecation cycle here just in case someone
is using this in scripts.
2024-10-14 20:49:04 -07:00

278 lines
11 KiB
Ruby

# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula_installer"
require "install"
require "upgrade"
require "cask/utils"
require "cask/upgrade"
require "cask/macos"
require "api"
module Homebrew
module Cmd
class UpgradeCmd < AbstractCommand
cmd_args do
description <<~EOS
Upgrade outdated casks and outdated, unpinned formulae using the same options they were originally
installed with, plus any appended brew formula options. If <cask> or <formula> are specified,
upgrade only the given <cask> or <formula> kegs (unless they are pinned; see `pin`, `unpin`).
Unless `HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK` is set, `brew upgrade` or `brew reinstall` will be run for
outdated dependents and dependents with broken linkage, respectively.
Unless `HOMEBREW_NO_INSTALL_CLEANUP` is set, `brew cleanup` will then be run for the
upgraded formulae or, every 30 days, for all formulae.
EOS
switch "-d", "--debug",
description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory."
switch "--display-times",
env: :display_install_times,
description: "Print install times for each package at the end of the run."
switch "-f", "--force",
description: "Install formulae without checking for previously installed keg-only or " \
"non-migrated versions. When installing casks, overwrite existing files " \
"(binaries and symlinks are excluded, unless originally from the same cask)."
switch "-v", "--verbose",
description: "Print the verification and post-install steps."
switch "-n", "--dry-run",
description: "Show what would be upgraded, but do not actually upgrade anything."
[
[:switch, "--formula", "--formulae", {
description: "Treat all named arguments as formulae. If no named arguments " \
"are specified, upgrade only outdated formulae.",
}],
[:switch, "-s", "--build-from-source", {
description: "Compile <formula> from source even if a bottle is available.",
}],
[:switch, "-i", "--interactive", {
description: "Download and patch <formula>, then open a shell. This allows the user to " \
"run `./configure --help` and otherwise determine how to turn the software " \
"package into a Homebrew package.",
}],
[:switch, "--force-bottle", {
description: "Install from a bottle if it exists for the current or newest version of " \
"macOS, even if it would not normally be used for installation.",
}],
[:switch, "--fetch-HEAD", {
description: "Fetch the upstream repository to detect if the HEAD installation of the " \
"formula is outdated. Otherwise, the repository's HEAD will only be checked for " \
"updates when a new stable or development version has been released.",
}],
[:switch, "--keep-tmp", {
description: "Retain the temporary files created during installation.",
}],
[:switch, "--debug-symbols", {
depends_on: "--build-from-source",
description: "Generate debug symbols on build. Source will be retained in a cache directory.",
}],
[:switch, "--overwrite", {
description: "Delete files that already exist in the prefix while linking.",
}],
].each do |args|
options = args.pop
send(*args, **options)
conflicts "--cask", args.last
end
formula_options
[
[:switch, "--cask", "--casks", {
description: "Treat all named arguments as casks. If no named arguments " \
"are specified, upgrade only outdated casks.",
}],
[:switch, "--skip-cask-deps", {
description: "Skip installing cask dependencies.",
}],
[:switch, "-g", "--greedy", {
description: "Also include casks with `auto_updates true` or `version :latest`.",
}],
[:switch, "--greedy-latest", {
description: "Also include casks with `version :latest`.",
}],
[:switch, "--greedy-auto-updates", {
description: "Also include casks with `auto_updates true`.",
}],
[:switch, "--[no-]binaries", {
description: "Disable/enable linking of helper executables (default: enabled).",
env: :cask_opts_binaries,
}],
[:switch, "--require-sha", {
description: "Require all casks to have a checksum.",
env: :cask_opts_require_sha,
}],
[:switch, "--[no-]quarantine", {
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine,
}],
].each do |args|
options = args.pop
send(*args, **options)
conflicts "--formula", args.last
end
cask_options
conflicts "--build-from-source", "--force-bottle"
named_args [:installed_formula, :installed_cask]
end
sig { override.void }
def run
if args.build_from_source? && args.named.empty?
raise ArgumentError, "--build-from-source requires at least one formula"
end
formulae, casks = args.named.to_resolved_formulae_to_casks
# If one or more formulae are specified, but no casks were
# specified, we want to make note of that so we don't
# try to upgrade all outdated casks.
only_upgrade_formulae = formulae.present? && casks.blank?
only_upgrade_casks = casks.present? && formulae.blank?
formulae = Homebrew::Attestation.sort_formulae_for_install(formulae) if Homebrew::Attestation.enabled?
upgrade_outdated_formulae(formulae) unless only_upgrade_casks
upgrade_outdated_casks(casks) unless only_upgrade_formulae
Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?)
end
private
sig { params(formulae: T::Array[Formula]).returns(T::Boolean) }
def upgrade_outdated_formulae(formulae)
return false if args.cask?
if args.build_from_source?
unless DevelopmentTools.installed?
raise BuildFlagsError.new(["--build-from-source"], bottled: formulae.all?(&:bottled?))
end
unless Homebrew::EnvConfig.developer?
opoo "building from source is not supported!"
puts "You're on your own. Failures are expected so don't create any issues, please!"
end
end
if formulae.blank?
outdated = Formula.installed.select do |f|
f.outdated?(fetch_head: args.fetch_HEAD?)
end
else
outdated, not_outdated = formulae.partition do |f|
f.outdated?(fetch_head: args.fetch_HEAD?)
end
not_outdated.each do |f|
latest_keg = f.installed_kegs.max_by(&:scheme_and_version)
if latest_keg.nil?
ofail "#{f.full_specified_name} not installed"
else
opoo "#{f.full_specified_name} #{latest_keg.version} already installed" unless args.quiet?
end
end
end
return false if outdated.blank?
pinned = outdated.select(&:pinned?)
outdated -= pinned
formulae_to_install = outdated.map do |f|
f_latest = f.latest_formula
if f_latest.latest_version_installed?
f
else
f_latest
end
end
if pinned.any?
Kernel.public_send(
formulae.any? ? :ofail : :opoo, # only fail when pinned formulae are named explicitly
"Not upgrading #{pinned.count} pinned #{Utils.pluralize("package", pinned.count)}:",
)
puts pinned.map { |f| "#{f.full_specified_name} #{f.pkg_version}" } * ", "
end
if formulae_to_install.empty?
oh1 "No packages to upgrade"
else
verb = args.dry_run? ? "Would upgrade" : "Upgrading"
oh1 "#{verb} #{formulae_to_install.count} outdated #{Utils.pluralize("package",
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("\n")
end
Install.perform_preinstall_checks_once
Upgrade.upgrade_formulae(
formulae_to_install,
flags: args.flags_only,
dry_run: args.dry_run?,
installed_on_request: args.named.present?,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
overwrite: args.overwrite?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?,
)
Upgrade.check_installed_dependents(
formulae_to_install,
flags: args.flags_only,
dry_run: args.dry_run?,
installed_on_request: args.named.present?,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?,
)
true
end
sig { params(casks: T::Array[Cask::Cask]).returns(T::Boolean) }
def upgrade_outdated_casks(casks)
return false if args.formula?
Cask::Upgrade.upgrade_casks(
*casks,
force: args.force?,
greedy: args.greedy?,
greedy_latest: args.greedy_latest?,
greedy_auto_updates: args.greedy_auto_updates?,
dry_run: args.dry_run?,
binaries: args.binaries?,
quarantine: args.quarantine?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
verbose: args.verbose?,
quiet: args.quiet?,
args:,
)
end
end
end
end