Merge pull request #16998 from Homebrew/ported-cmds

Port remaining commands to use AbstractCommand
This commit is contained in:
Mike McQuaid 2024-04-02 09:00:44 +01:00 committed by GitHub
commit 0b98c50866
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 2500 additions and 2436 deletions

View File

@ -1,122 +1,128 @@
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "fetch"
require "cli/parser"
require "cask/download"
module Homebrew
extend Fetch
module Cmd
class Cache < AbstractCommand
include Fetch
sig { returns(CLI::Parser) }
def self.__cache_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display Homebrew's download cache. See also `HOMEBREW_CACHE`.
sig { override.returns(String) }
def self.command_name = "--cache"
If <formula> is provided, display the file or directory used to cache <formula>.
EOS
flag "--os=",
description: "Show cache file for the given operating system. " \
"(Pass `all` to show cache files for all operating systems.)"
flag "--arch=",
description: "Show cache file for the given CPU architecture. " \
"(Pass `all` to show cache files for all architectures.)"
switch "-s", "--build-from-source",
description: "Show the cache file used when building from source."
switch "--force-bottle",
description: "Show the cache file used when pouring a bottle."
flag "--bottle-tag=",
description: "Show the cache file used when pouring a bottle for the given tag."
switch "--HEAD",
description: "Show the cache file used when building from HEAD."
switch "--formula", "--formulae",
description: "Only show cache files for formulae."
switch "--cask", "--casks",
description: "Only show cache files for casks."
cmd_args do
description <<~EOS
Display Homebrew's download cache. See also `HOMEBREW_CACHE`.
conflicts "--build-from-source", "--force-bottle", "--bottle-tag", "--HEAD", "--cask"
conflicts "--formula", "--cask"
conflicts "--os", "--bottle-tag"
conflicts "--arch", "--bottle-tag"
If <formula> is provided, display the file or directory used to cache <formula>.
EOS
flag "--os=",
description: "Show cache file for the given operating system. " \
"(Pass `all` to show cache files for all operating systems.)"
flag "--arch=",
description: "Show cache file for the given CPU architecture. " \
"(Pass `all` to show cache files for all architectures.)"
switch "-s", "--build-from-source",
description: "Show the cache file used when building from source."
switch "--force-bottle",
description: "Show the cache file used when pouring a bottle."
flag "--bottle-tag=",
description: "Show the cache file used when pouring a bottle for the given tag."
switch "--HEAD",
description: "Show the cache file used when building from HEAD."
switch "--formula", "--formulae",
description: "Only show cache files for formulae."
switch "--cask", "--casks",
description: "Only show cache files for casks."
named_args [:formula, :cask]
end
end
conflicts "--build-from-source", "--force-bottle", "--bottle-tag", "--HEAD", "--cask"
conflicts "--formula", "--cask"
conflicts "--os", "--bottle-tag"
conflicts "--arch", "--bottle-tag"
sig { void }
def self.__cache
args = __cache_args.parse
named_args [:formula, :cask]
end
if args.no_named?
puts HOMEBREW_CACHE
return
end
formulae_or_casks = args.named.to_formulae_and_casks
os_arch_combinations = args.os_arch_combinations
formulae_or_casks.each do |formula_or_cask|
case formula_or_cask
when Formula
formula = formula_or_cask
ref = formula.loaded_from_api? ? formula.full_name : formula.path
os_arch_combinations.each do |os, arch|
SimulateSystem.with(os:, arch:) do
formula = Formulary.factory(ref)
print_formula_cache(formula, os:, arch:, args:)
end
sig { override.void }
def run
if args.no_named?
puts HOMEBREW_CACHE
return
end
else
cask = formula_or_cask
ref = cask.loaded_from_api? ? cask.full_name : cask.sourcefile_path
os_arch_combinations.each do |os, arch|
next if os == :linux
formulae_or_casks = args.named.to_formulae_and_casks
os_arch_combinations = args.os_arch_combinations
SimulateSystem.with(os:, arch:) do
cask = Cask::CaskLoader.load(ref)
print_cask_cache(cask)
formulae_or_casks.each do |formula_or_cask|
case formula_or_cask
when Formula
formula = formula_or_cask
ref = formula.loaded_from_api? ? formula.full_name : formula.path
os_arch_combinations.each do |os, arch|
SimulateSystem.with(os:, arch:) do
formula = Formulary.factory(ref)
print_formula_cache(formula, os:, arch:)
end
end
when Cask::Cask
cask = formula_or_cask
ref = cask.loaded_from_api? ? cask.full_name : cask.sourcefile_path
os_arch_combinations.each do |os, arch|
next if os == :linux
SimulateSystem.with(os:, arch:) do
loaded_cask = Cask::CaskLoader.load(ref)
print_cask_cache(loaded_cask)
end
end
else
raise "Invalid type: #{formula_or_cask.class}"
end
end
end
end
end
sig { params(formula: Formula, os: Symbol, arch: Symbol, args: CLI::Args).void }
def self.print_formula_cache(formula, os:, arch:, args:)
if fetch_bottle?(
formula,
force_bottle: args.force_bottle?,
bottle_tag: args.bottle_tag&.to_sym,
build_from_source_formulae: args.build_from_source_formulae,
os: args.os&.to_sym,
arch: args.arch&.to_sym,
)
bottle_tag = if (bottle_tag = args.bottle_tag&.to_sym)
Utils::Bottles::Tag.from_symbol(bottle_tag)
else
Utils::Bottles::Tag.new(system: os, arch:)
private
sig { params(formula: Formula, os: Symbol, arch: Symbol).void }
def print_formula_cache(formula, os:, arch:)
if fetch_bottle?(
formula,
force_bottle: args.force_bottle?,
bottle_tag: args.bottle_tag&.to_sym,
build_from_source_formulae: args.build_from_source_formulae,
os: args.os&.to_sym,
arch: args.arch&.to_sym,
)
bottle_tag = if (bottle_tag = args.bottle_tag&.to_sym)
Utils::Bottles::Tag.from_symbol(bottle_tag)
else
Utils::Bottles::Tag.new(system: os, arch:)
end
bottle = formula.bottle_for_tag(bottle_tag)
if bottle.nil?
opoo "Bottle for tag #{bottle_tag.to_sym.inspect} is unavailable."
return
end
puts bottle.cached_download
elsif args.HEAD?
puts T.must(formula.head).cached_download
else
puts formula.cached_download
end
end
bottle = formula.bottle_for_tag(bottle_tag)
if bottle.nil?
opoo "Bottle for tag #{bottle_tag.to_sym.inspect} is unavailable."
return
sig { params(cask: Cask::Cask).void }
def print_cask_cache(cask)
puts Cask::Download.new(cask).downloader.cached_location
end
puts bottle.cached_download
elsif args.HEAD?
puts T.must(formula.head).cached_download
else
puts formula.cached_download
end
end
sig { params(cask: Cask::Cask).void }
def self.print_cask_cache(cask)
puts Cask::Download.new(cask).downloader.cached_location
end
end

View File

@ -1,32 +1,34 @@
# typed: strict
# frozen_string_literal: true
require "abstract_command"
module Homebrew
module_function
module Cmd
class Caskroom < AbstractCommand
sig { override.returns(String) }
def self.command_name = "--caskroom"
sig { returns(CLI::Parser) }
def __caskroom_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display Homebrew's Caskroom path.
cmd_args do
description <<~EOS
Display Homebrew's Caskroom path.
If <cask> is provided, display the location in the Caskroom where <cask>
would be installed, without any sort of versioned directory as the last path.
EOS
If <cask> is provided, display the location in the Caskroom where <cask>
would be installed, without any sort of versioned directory as the last path.
EOS
named_args :cask
end
end
named_args :cask
end
sig { void }
def __caskroom
args = __caskroom_args.parse
if args.named.to_casks.blank?
puts Cask::Caskroom.path
else
args.named.to_casks.each do |cask|
puts "#{Cask::Caskroom.path}/#{cask.token}"
sig { override.void }
def run
if args.named.to_casks.blank?
puts Cask::Caskroom.path
else
args.named.to_casks.each do |cask|
puts "#{Cask::Caskroom.path}/#{cask.token}"
end
end
end
end
end

View File

@ -1,32 +1,34 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
module Homebrew
module_function
module Cmd
class Cellar < AbstractCommand
sig { override.returns(String) }
def self.command_name = "--cellar"
def __cellar_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display Homebrew's Cellar path. *Default:* `$(brew --prefix)/Cellar`, or if
that directory doesn't exist, `$(brew --repository)/Cellar`.
cmd_args do
description <<~EOS
Display Homebrew's Cellar path. *Default:* `$(brew --prefix)/Cellar`, or if
that directory doesn't exist, `$(brew --repository)/Cellar`.
If <formula> is provided, display the location in the Cellar where <formula>
would be installed, without any sort of versioned directory as the last path.
EOS
If <formula> is provided, display the location in the Cellar where <formula>
would be installed, without any sort of versioned directory as the last path.
EOS
named_args :formula
end
end
named_args :formula
end
def __cellar
args = __cellar_args.parse
if args.no_named?
puts HOMEBREW_CELLAR
else
puts args.named.to_resolved_formulae.map(&:rack)
sig { override.void }
def run
if args.no_named?
puts HOMEBREW_CELLAR
else
puts args.named.to_resolved_formulae.map(&:rack)
end
end
end
end
end

View File

@ -1,56 +1,56 @@
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "extend/ENV"
require "build_environment"
require "utils/shell"
require "cli/parser"
module Homebrew
module_function
module Cmd
class Env < AbstractCommand
sig { override.returns(String) }
def self.command_name = "--env"
sig { returns(CLI::Parser) }
def __env_args
Homebrew::CLI::Parser.new do
description <<~EOS
Summarise Homebrew's build environment as a plain list.
cmd_args do
description <<~EOS
Summarise Homebrew's build environment as a plain list.
If the command's output is sent through a pipe and no shell is specified,
the list is formatted for export to `bash`(1) unless `--plain` is passed.
EOS
flag "--shell=",
description: "Generate a list of environment variables for the specified shell, " \
"or `--shell=auto` to detect the current shell."
switch "--plain",
description: "Generate plain output even when piped."
If the command's output is sent through a pipe and no shell is specified,
the list is formatted for export to `bash`(1) unless `--plain` is passed.
EOS
flag "--shell=",
description: "Generate a list of environment variables for the specified shell, " \
"or `--shell=auto` to detect the current shell."
switch "--plain",
description: "Generate plain output even when piped."
named_args :formula
end
end
named_args :formula
end
sig { void }
def __env
args = __env_args.parse
sig { override.void }
def run
ENV.activate_extensions!
ENV.deps = args.named.to_formulae if superenv?(nil)
ENV.setup_build_environment
ENV.activate_extensions!
ENV.deps = args.named.to_formulae if superenv?(nil)
ENV.setup_build_environment
shell = if args.plain?
nil
elsif args.shell.nil?
:bash unless $stdout.tty?
elsif args.shell == "auto"
Utils::Shell.parent || Utils::Shell.preferred
elsif args.shell
Utils::Shell.from_path(T.must(args.shell))
end
shell = if args.plain?
nil
elsif args.shell.nil?
:bash unless $stdout.tty?
elsif args.shell == "auto"
Utils::Shell.parent || Utils::Shell.preferred
elsif args.shell
Utils::Shell.from_path(args.shell)
end
if shell.nil?
BuildEnvironment.dump ENV.to_h
else
BuildEnvironment.keys(ENV.to_h).each do |key|
puts Utils::Shell.export_value(key, ENV.fetch(key), shell)
if shell.nil?
BuildEnvironment.dump ENV.to_h
else
BuildEnvironment.keys(ENV.to_h).each do |key|
puts Utils::Shell.export_value(key, ENV.fetch(key), shell)
end
end
end
end
end

View File

@ -1,106 +1,114 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "fileutils"
module Homebrew
sig { returns(CLI::Parser) }
def self.__prefix_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display Homebrew's install path. *Default:*
module Cmd
class Prefix < AbstractCommand
include FileUtils
- macOS ARM: `#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}`
- macOS Intel: `#{HOMEBREW_DEFAULT_PREFIX}`
- Linux: `#{HOMEBREW_LINUX_DEFAULT_PREFIX}`
UNBREWED_EXCLUDE_FILES = %w[.DS_Store].freeze
UNBREWED_EXCLUDE_PATHS = %w[
*/.keepme
.github/*
bin/brew
completions/zsh/_brew
docs/*
lib/gdk-pixbuf-2.0/*
lib/gio/*
lib/node_modules/*
lib/python[23].[0-9]/*
lib/python3.[0-9][0-9]/*
lib/pypy/*
lib/pypy3/*
lib/ruby/gems/[12].*
lib/ruby/site_ruby/[12].*
lib/ruby/vendor_ruby/[12].*
manpages/brew.1
share/pypy/*
share/pypy3/*
share/info/dir
share/man/whatis
share/mime/*
texlive/*
].freeze
If <formula> is provided, display the location where <formula> is or would be installed.
EOS
switch "--unbrewed",
description: "List files in Homebrew's prefix not installed by Homebrew."
switch "--installed",
description: "Outputs nothing and returns a failing status code if <formula> is not installed."
conflicts "--unbrewed", "--installed"
sig { override.returns(String) }
def self.command_name = "--prefix"
named_args :formula
end
end
cmd_args do
description <<~EOS
Display Homebrew's install path. *Default:*
def self.__prefix
args = __prefix_args.parse
- macOS ARM: `#{HOMEBREW_MACOS_ARM_DEFAULT_PREFIX}`
- macOS Intel: `#{HOMEBREW_DEFAULT_PREFIX}`
- Linux: `#{HOMEBREW_LINUX_DEFAULT_PREFIX}`
raise UsageError, "`--installed` requires a formula argument." if args.installed? && args.no_named?
if args.unbrewed?
raise UsageError, "`--unbrewed` does not take a formula argument." unless args.no_named?
list_unbrewed
elsif args.no_named?
puts HOMEBREW_PREFIX
else
formulae = args.named.to_resolved_formulae
prefixes = formulae.filter_map do |f|
next nil if args.installed? && !f.opt_prefix.exist?
# this case will be short-circuited by brew.sh logic for a single formula
f.opt_prefix
end
puts prefixes
if args.installed?
missing_formulae = formulae.reject(&:optlinked?)
.map(&:name)
return if missing_formulae.blank?
raise NotAKegError, <<~EOS
The following formulae are not installed:
#{missing_formulae.join(" ")}
If <formula> is provided, display the location where <formula> is or would be installed.
EOS
switch "--unbrewed",
description: "List files in Homebrew's prefix not installed by Homebrew."
switch "--installed",
description: "Outputs nothing and returns a failing status code if <formula> is not installed."
conflicts "--unbrewed", "--installed"
named_args :formula
end
sig { override.void }
def run
raise UsageError, "`--installed` requires a formula argument." if args.installed? && args.no_named?
if args.unbrewed?
raise UsageError, "`--unbrewed` does not take a formula argument." unless args.no_named?
list_unbrewed
elsif args.no_named?
puts HOMEBREW_PREFIX
else
formulae = args.named.to_resolved_formulae
prefixes = formulae.filter_map do |f|
next nil if args.installed? && !f.opt_prefix.exist?
# this case will be short-circuited by brew.sh logic for a single formula
f.opt_prefix
end
puts prefixes
if args.installed?
missing_formulae = formulae.reject(&:optlinked?)
.map(&:name)
return if missing_formulae.blank?
raise NotAKegError, <<~EOS
The following formulae are not installed:
#{missing_formulae.join(" ")}
EOS
end
end
end
private
def list_unbrewed
dirs = HOMEBREW_PREFIX.subdirs.map { |dir| dir.basename.to_s }
dirs -= %w[Library Cellar Caskroom .git]
# Exclude cache, logs, and repository, if they are located under the prefix.
[HOMEBREW_CACHE, HOMEBREW_LOGS, HOMEBREW_REPOSITORY].each do |dir|
dirs.delete dir.relative_path_from(HOMEBREW_PREFIX).to_s
end
dirs.delete "etc"
dirs.delete "var"
arguments = dirs.sort + %w[-type f (]
arguments.concat UNBREWED_EXCLUDE_FILES.flat_map { |f| %W[! -name #{f}] }
arguments.concat UNBREWED_EXCLUDE_PATHS.flat_map { |d| %W[! -path #{d}] }
arguments.push ")"
cd(HOMEBREW_PREFIX) { safe_system("find", *arguments) }
end
end
end
UNBREWED_EXCLUDE_FILES = %w[.DS_Store].freeze
UNBREWED_EXCLUDE_PATHS = %w[
*/.keepme
.github/*
bin/brew
completions/zsh/_brew
docs/*
lib/gdk-pixbuf-2.0/*
lib/gio/*
lib/node_modules/*
lib/python[23].[0-9]/*
lib/python3.[0-9][0-9]/*
lib/pypy/*
lib/pypy3/*
lib/ruby/gems/[12].*
lib/ruby/site_ruby/[12].*
lib/ruby/vendor_ruby/[12].*
manpages/brew.1
share/pypy/*
share/pypy3/*
share/info/dir
share/man/whatis
share/mime/*
texlive/*
].freeze
def self.list_unbrewed
dirs = HOMEBREW_PREFIX.subdirs.map { |dir| dir.basename.to_s }
dirs -= %w[Library Cellar Caskroom .git]
# Exclude cache, logs, and repository, if they are located under the prefix.
[HOMEBREW_CACHE, HOMEBREW_LOGS, HOMEBREW_REPOSITORY].each do |dir|
dirs.delete dir.relative_path_from(HOMEBREW_PREFIX).to_s
end
dirs.delete "etc"
dirs.delete "var"
arguments = dirs.sort + %w[-type f (]
arguments.concat UNBREWED_EXCLUDE_FILES.flat_map { |f| %W[! -name #{f}] }
arguments.concat UNBREWED_EXCLUDE_PATHS.flat_map { |d| %W[! -path #{d}] }
arguments.push ")"
cd(HOMEBREW_PREFIX) { safe_system("find", *arguments) }
end
end

View File

@ -1,32 +1,32 @@
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
module Homebrew
module_function
module Cmd
class Repository < AbstractCommand
sig { override.returns(String) }
def self.command_name = "--repository"
sig { returns(CLI::Parser) }
def __repository_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display where Homebrew's Git repository is located.
cmd_args do
description <<~EOS
Display where Homebrew's Git repository is located.
If <user>`/`<repo> are provided, display where tap <user>`/`<repo>'s directory is located.
EOS
If <user>`/`<repo> are provided, display where tap <user>`/`<repo>'s directory is located.
EOS
named_args :tap
end
end
named_args :tap
end
sig { void }
def __repository
args = __repository_args.parse
if args.no_named?
puts HOMEBREW_REPOSITORY
else
puts args.named.to_taps.map(&:path)
sig { override.void }
def run
if args.no_named?
puts HOMEBREW_REPOSITORY
else
puts args.named.to_taps.map(&:path)
end
end
end
end
end

View File

@ -1,83 +1,88 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "fileutils"
module Homebrew
sig { returns(CLI::Parser) }
def self.log_args
Homebrew::CLI::Parser.new do
description <<~EOS
Show the `git log` for <formula> or <cask>, or show the log for the Homebrew repository
if no formula or cask is provided.
EOS
switch "-p", "-u", "--patch",
description: "Also print patch from commit."
switch "--stat",
description: "Also print diffstat from commit."
switch "--oneline",
description: "Print only one line per commit."
switch "-1",
description: "Print only one commit."
flag "-n", "--max-count=",
description: "Print only a specified number of commits."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
module Cmd
class Log < AbstractCommand
include FileUtils
conflicts "-1", "--max-count"
conflicts "--formula", "--cask"
named_args [:formula, :cask], max: 1, without_api: true
end
end
def self.log
args = log_args.parse
# As this command is simplifying user-run commands then let's just use a
# user path, too.
ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s
if args.no_named?
git_log(HOMEBREW_REPOSITORY, args:)
else
path = args.named.to_paths.first
tap = Tap.from_path(path)
git_log path.dirname, path, tap, args:
end
end
def self.git_log(cd_dir, path = nil, tap = nil, args:)
cd cd_dir do
repo = Utils.popen_read("git", "rev-parse", "--show-toplevel").chomp
if tap
name = tap.to_s
git_cd = "$(brew --repo #{tap})"
elsif cd_dir == HOMEBREW_REPOSITORY
name = "Homebrew/brew"
git_cd = "$(brew --repo)"
else
name, git_cd = cd_dir
end
if File.exist? "#{repo}/.git/shallow"
opoo <<~EOS
#{name} is a shallow clone so only partial output will be shown.
To get a full clone, run:
git -C "#{git_cd}" fetch --unshallow
cmd_args do
description <<~EOS
Show the `git log` for <formula> or <cask>, or show the log for the Homebrew repository
if no formula or cask is provided.
EOS
switch "-p", "-u", "--patch",
description: "Also print patch from commit."
switch "--stat",
description: "Also print diffstat from commit."
switch "--oneline",
description: "Print only one line per commit."
switch "-1",
description: "Print only one commit."
flag "-n", "--max-count=",
description: "Print only a specified number of commits."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "-1", "--max-count"
conflicts "--formula", "--cask"
named_args [:formula, :cask], max: 1, without_api: true
end
git_args = []
git_args << "--patch" if args.patch?
git_args << "--stat" if args.stat?
git_args << "--oneline" if args.oneline?
git_args << "-1" if args.public_send(:"1?")
git_args << "--max-count" << args.max_count if args.max_count
git_args += ["--follow", "--", path] if path.present?
system "git", "log", *git_args
sig { override.void }
def run
# As this command is simplifying user-run commands then let's just use a
# user path, too.
ENV["PATH"] = PATH.new(ORIGINAL_PATHS).to_s
if args.no_named?
git_log(HOMEBREW_REPOSITORY, args:)
else
path = args.named.to_paths.first
tap = Tap.from_path(path)
git_log T.must(path).dirname, path, tap
end
end
private
def git_log(cd_dir, path = nil, tap = nil)
cd cd_dir do
repo = Utils.popen_read("git", "rev-parse", "--show-toplevel").chomp
if tap
name = tap.to_s
git_cd = "$(brew --repo #{tap})"
elsif cd_dir == HOMEBREW_REPOSITORY
name = "Homebrew/brew"
git_cd = "$(brew --repo)"
else
name, git_cd = cd_dir
end
if File.exist? "#{repo}/.git/shallow"
opoo <<~EOS
#{name} is a shallow clone so only partial output will be shown.
To get a full clone, run:
git -C "#{git_cd}" fetch --unshallow
EOS
end
git_args = []
git_args << "--patch" if args.patch?
git_args << "--stat" if args.stat?
git_args << "--oneline" if args.oneline?
git_args << "-1" if args.public_send(:"1?")
git_args << "--max-count" << args.max_count if args.max_count
git_args += ["--follow", "--", path] if path.present?
system "git", "log", *git_args
end
end
end
end
end

View File

@ -1,43 +1,43 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "migrator"
require "cli/parser"
require "cask/migrator"
module Homebrew
sig { returns(CLI::Parser) }
def self.migrate_args
Homebrew::CLI::Parser.new do
description <<~EOS
Migrate renamed packages to new names, where <formula> are old names of
packages.
EOS
switch "-f", "--force",
description: "Treat installed <formula> and provided <formula> as if they are from " \
"the same taps and migrate them anyway."
switch "-n", "--dry-run",
description: "Show what would be migrated, but do not actually migrate anything."
switch "--formula", "--formulae",
description: "Only migrate formulae."
switch "--cask", "--casks",
description: "Only migrate casks."
module Cmd
class Migrate < AbstractCommand
cmd_args do
description <<~EOS
Migrate renamed packages to new names, where <formula> are old names of
packages.
EOS
switch "-f", "--force",
description: "Treat installed <formula> and provided <formula> as if they are from " \
"the same taps and migrate them anyway."
switch "-n", "--dry-run",
description: "Show what would be migrated, but do not actually migrate anything."
switch "--formula", "--formulae",
description: "Only migrate formulae."
switch "--cask", "--casks",
description: "Only migrate casks."
conflicts "--formula", "--cask"
conflicts "--formula", "--cask"
named_args [:installed_formula, :installed_cask], min: 1
end
end
named_args [:installed_formula, :installed_cask], min: 1
end
def self.migrate
args = migrate_args.parse
args.named.to_formulae_and_casks(warn: false).each do |formula_or_cask|
case formula_or_cask
when Formula
Migrator.migrate_if_needed(formula_or_cask, force: args.force?, dry_run: args.dry_run?)
when Cask::Cask
Cask::Migrator.migrate_if_needed(formula_or_cask, dry_run: args.dry_run?)
sig { override.void }
def run
args.named.to_formulae_and_casks(warn: false).each do |formula_or_cask|
case formula_or_cask
when Formula
Migrator.migrate_if_needed(formula_or_cask, force: args.force?, dry_run: args.dry_run?)
when Cask::Cask
Cask::Migrator.migrate_if_needed(formula_or_cask, dry_run: args.dry_run?)
end
end
end
end
end

View File

@ -1,48 +1,46 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "tab"
require "diagnostic"
require "cli/parser"
module Homebrew
module_function
module Cmd
class Missing < AbstractCommand
cmd_args do
description <<~EOS
Check the given <formula> kegs for missing dependencies. If no <formula> are
provided, check all kegs. Will exit with a non-zero status if any kegs are found
to be missing dependencies.
EOS
comma_array "--hide",
description: "Act as if none of the specified <hidden> are installed. <hidden> should be " \
"a comma-separated list of formulae."
sig { returns(CLI::Parser) }
def missing_args
Homebrew::CLI::Parser.new do
description <<~EOS
Check the given <formula> kegs for missing dependencies. If no <formula> are
provided, check all kegs. Will exit with a non-zero status if any kegs are found
to be missing dependencies.
EOS
comma_array "--hide",
description: "Act as if none of the specified <hidden> are installed. <hidden> should be " \
"a comma-separated list of formulae."
named_args :formula
end
named_args :formula
end
end
sig { override.void }
def run
return unless HOMEBREW_CELLAR.exist?
def missing
args = missing_args.parse
ff = if args.no_named?
Formula.installed.sort
else
args.named.to_resolved_formulae.sort
end
return unless HOMEBREW_CELLAR.exist?
ff.each do |f|
missing = f.missing_dependencies(hide: args.hide)
next if missing.empty?
ff = if args.no_named?
Formula.installed.sort
else
args.named.to_resolved_formulae.sort
end
ff.each do |f|
missing = f.missing_dependencies(hide: args.hide)
next if missing.empty?
Homebrew.failed = true
print "#{f}: " if ff.size > 1
puts missing.join(" ")
Homebrew.failed = true
print "#{f}: " if ff.size > 1
puts missing.join(" ")
end
end
end
end
end

View File

@ -1,69 +1,68 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "formula"
module Homebrew
module_function
module Cmd
class NodenvSync < AbstractCommand
cmd_args do
description <<~EOS
Create symlinks for Homebrew's installed NodeJS versions in `~/.nodenv/versions`.
sig { returns(CLI::Parser) }
def nodenv_sync_args
Homebrew::CLI::Parser.new do
description <<~EOS
Create symlinks for Homebrew's installed NodeJS versions in `~/.nodenv/versions`.
Note that older version symlinks will also be created so e.g. NodeJS 19.1.0 will
also be symlinked to 19.0.0.
EOS
Note that older version symlinks will also be created so e.g. NodeJS 19.1.0 will
also be symlinked to 19.0.0.
EOS
named_args :none
end
named_args :none
end
end
sig { override.void }
def run
nodenv_root = Pathname(ENV.fetch("HOMEBREW_NODENV_ROOT", Pathname(Dir.home)/".nodenv"))
sig { void }
def nodenv_sync
nodenv_root = Pathname(ENV.fetch("HOMEBREW_NODENV_ROOT", Pathname(Dir.home)/".nodenv"))
# Don't run multiple times at once.
nodenv_sync_running = nodenv_root/".nodenv_sync_running"
return if nodenv_sync_running.exist?
# Don't run multiple times at once.
nodenv_sync_running = nodenv_root/".nodenv_sync_running"
return if nodenv_sync_running.exist?
begin
nodenv_versions = nodenv_root/"versions"
nodenv_versions.mkpath
FileUtils.touch nodenv_sync_running
begin
nodenv_versions = nodenv_root/"versions"
nodenv_versions.mkpath
FileUtils.touch nodenv_sync_running
HOMEBREW_CELLAR.glob("node{,@*}")
.flat_map(&:children)
.each { |path| link_nodenv_versions(path, nodenv_versions) }
nodenv_sync_args.parse
nodenv_versions.children
.select(&:symlink?)
.reject(&:exist?)
.each { |path| FileUtils.rm_f path }
ensure
nodenv_sync_running.unlink if nodenv_sync_running.exist?
end
end
HOMEBREW_CELLAR.glob("node{,@*}")
.flat_map(&:children)
.each { |path| link_nodenv_versions(path, nodenv_versions) }
private
nodenv_versions.children
.select(&:symlink?)
.reject(&:exist?)
.each { |path| FileUtils.rm_f path }
ensure
nodenv_sync_running.unlink if nodenv_sync_running.exist?
end
end
sig { params(path: Pathname, nodenv_versions: Pathname).void }
def link_nodenv_versions(path, nodenv_versions)
nodenv_versions.mkpath
sig { params(path: Pathname, nodenv_versions: Pathname).void }
def link_nodenv_versions(path, nodenv_versions)
nodenv_versions.mkpath
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i || 0
patch_version = version.patch.to_i || 0
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i || 0
patch_version = version.patch.to_i || 0
(0..minor_version).each do |minor|
(0..patch_version).each do |patch|
link_path = nodenv_versions/"#{major_version}.#{minor}.#{patch}"
(0..minor_version).each do |minor|
(0..patch_version).each do |patch|
link_path = nodenv_versions/"#{major_version}.#{minor}.#{patch}"
FileUtils.rm_f link_path
FileUtils.ln_sf path, link_path
FileUtils.rm_f link_path
FileUtils.ln_sf path, link_path
end
end
end
end
end

View File

@ -1,70 +1,71 @@
# typed: true
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "options"
module Homebrew
module_function
module Cmd
class OptionsCmd < AbstractCommand
cmd_args do
description <<~EOS
Show install options specific to <formula>.
EOS
switch "--compact",
description: "Show all options on a single line separated by spaces."
switch "--installed",
description: "Show options for formulae that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show their " \
"options."
flag "--command=",
description: "Show options for the specified <command>."
sig { returns(CLI::Parser) }
def options_args
Homebrew::CLI::Parser.new do
description <<~EOS
Show install options specific to <formula>.
EOS
switch "--compact",
description: "Show all options on a single line separated by spaces."
switch "--installed",
description: "Show options for formulae that are currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show their " \
"options."
flag "--command=",
description: "Show options for the specified <command>."
conflicts "--installed", "--all", "--command"
conflicts "--installed", "--all", "--command"
named_args :formula
end
end
def options
args = options_args.parse
all = args.eval_all?
if all
puts_options(Formula.all(eval_all: args.eval_all?).sort, args:)
elsif args.installed?
puts_options(Formula.installed.sort, args:)
elsif args.command.present?
cmd_options = Commands.command_options(args.command)
odie "Unknown command: #{args.command}" if cmd_options.nil?
if args.compact?
puts cmd_options.sort.map(&:first) * " "
else
cmd_options.sort.each { |option, desc| puts "#{option}\n\t#{desc}" }
puts
named_args :formula
end
elsif args.no_named?
raise FormulaUnspecifiedError
else
puts_options args.named.to_formulae, args:
end
end
def puts_options(formulae, args:)
formulae.each do |f|
next if f.options.empty?
sig { override.void }
def run
all = args.eval_all?
if args.compact?
puts f.options.as_flags.sort * " "
else
puts f.full_name if formulae.length > 1
Options.dump_for_formula f
puts
if all
puts_options(Formula.all(eval_all: args.eval_all?).sort)
elsif args.installed?
puts_options(Formula.installed.sort)
elsif args.command.present?
cmd_options = Commands.command_options(T.must(args.command))
odie "Unknown command: #{args.command}" if cmd_options.nil?
if args.compact?
puts cmd_options.sort.map(&:first) * " "
else
cmd_options.sort.each { |option, desc| puts "#{option}\n\t#{desc}" }
puts
end
elsif args.no_named?
raise FormulaUnspecifiedError
else
puts_options args.named.to_formulae
end
end
private
def puts_options(formulae)
formulae.each do |f|
next if f.options.empty?
if args.compact?
puts f.options.as_flags.sort * " "
else
puts f.full_name if formulae.length > 1
Options.dump_for_formula f
puts
end
end
end
end
end

View File

@ -1,200 +1,202 @@
# typed: true
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "keg"
require "cli/parser"
require "cask/caskroom"
require "api"
module Homebrew
sig { returns(CLI::Parser) }
def self.outdated_args
Homebrew::CLI::Parser.new do
description <<~EOS
List installed casks and formulae that have an updated version available. By default, version
information is displayed in interactive shells, and suppressed otherwise.
EOS
switch "-q", "--quiet",
description: "List only the names of outdated kegs (takes precedence over `--verbose`)."
switch "-v", "--verbose",
description: "Include detailed version information."
switch "--formula", "--formulae",
description: "List only outdated formulae."
switch "--cask", "--casks",
description: "List only outdated casks."
flag "--json",
description: "Print output in JSON format. There are two versions: `v1` and `v2`. " \
"`v1` is deprecated and is currently the default if no version is specified. " \
"`v2` prints outdated formulae and casks."
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 "-g", "--greedy",
description: "Also include outdated casks with `auto_updates true` or `version :latest`."
module Cmd
class Outdated < AbstractCommand
cmd_args do
description <<~EOS
List installed casks and formulae that have an updated version available. By default, version
information is displayed in interactive shells, and suppressed otherwise.
EOS
switch "-q", "--quiet",
description: "List only the names of outdated kegs (takes precedence over `--verbose`)."
switch "-v", "--verbose",
description: "Include detailed version information."
switch "--formula", "--formulae",
description: "List only outdated formulae."
switch "--cask", "--casks",
description: "List only outdated casks."
flag "--json",
description: "Print output in JSON format. There are two versions: `v1` and `v2`. " \
"`v1` is deprecated and is currently the default if no version is specified. " \
"`v2` prints outdated formulae and casks."
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 "-g", "--greedy",
description: "Also include outdated casks with `auto_updates true` or `version :latest`."
switch "--greedy-latest",
description: "Also include outdated casks including those with `version :latest`."
switch "--greedy-latest",
description: "Also include outdated casks including those with `version :latest`."
switch "--greedy-auto-updates",
description: "Also include outdated casks including those with `auto_updates true`."
switch "--greedy-auto-updates",
description: "Also include outdated casks including those with `auto_updates true`."
conflicts "--quiet", "--verbose", "--json"
conflicts "--formula", "--cask"
conflicts "--quiet", "--verbose", "--json"
conflicts "--formula", "--cask"
named_args [:formula, :cask]
end
end
def self.outdated
args = outdated_args.parse
case json_version(args.json)
when :v1
odie "`brew outdated --json=v1` is no longer supported. Use brew outdated --json=v2 instead."
when :v2, :default
formulae, casks = if args.formula?
[outdated_formulae(args:), []]
elsif args.cask?
[[], outdated_casks(args:)]
else
outdated_formulae_casks(args:)
named_args [:formula, :cask]
end
json = {
"formulae" => json_info(formulae, args:),
"casks" => json_info(casks, args:),
}
puts JSON.pretty_generate(json)
outdated = formulae + casks
else
outdated = if args.formula?
outdated_formulae(args:)
elsif args.cask?
outdated_casks(args:)
else
outdated_formulae_casks(args:).flatten
end
print_outdated(outdated, args:)
end
Homebrew.failed = args.named.present? && outdated.present?
end
def self.print_outdated(formulae_or_casks, args:)
formulae_or_casks.each do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
current_version = if f.alias_changed? && !f.latest_formula.latest_version_installed?
latest = f.latest_formula
"#{latest.name} (#{latest.pkg_version})"
elsif f.head? && outdated_kegs.any? { |k| k.version.to_s == f.pkg_version.to_s }
# There is a newer HEAD but the version number has not changed.
"latest HEAD"
sig { override.void }
def run
case json_version(args.json)
when :v1
odie "`brew outdated --json=v1` is no longer supported. Use brew outdated --json=v2 instead."
when :v2, :default
formulae, casks = if args.formula?
[outdated_formulae, []]
elsif args.cask?
[[], outdated_casks]
else
f.pkg_version.to_s
outdated_formulae_casks
end
outdated_versions = outdated_kegs.group_by { |keg| Formulary.from_keg(keg).full_name }
.sort_by { |full_name, _kegs| full_name }
.map do |full_name, kegs|
"#{full_name} (#{kegs.map(&:version).join(", ")})"
end.join(", ")
json = {
"formulae" => json_info(formulae),
"casks" => json_info(casks),
}
puts JSON.pretty_generate(json)
pinned_version = " [pinned at #{f.pinned_version}]" if f.pinned?
outdated = formulae + casks
puts "#{outdated_versions} < #{current_version}#{pinned_version}"
else
puts f.full_installed_specified_name
end
else
c = formula_or_cask
outdated = if args.formula?
outdated_formulae
elsif args.cask?
outdated_casks
else
outdated_formulae_casks.flatten
end
puts c.outdated_info(args.greedy?, verbose?, false, args.greedy_latest?, args.greedy_auto_updates?)
end
end
end
def self.json_info(formulae_or_casks, args:)
formulae_or_casks.map do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
outdated_versions = f.outdated_kegs(fetch_head: args.fetch_HEAD?).map(&:version)
current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
"HEAD"
else
f.pkg_version.to_s
print_outdated(outdated)
end
{ name: f.full_name,
installed_versions: outdated_versions.map(&:to_s),
current_version:,
pinned: f.pinned?,
pinned_version: f.pinned_version }
else
c = formula_or_cask
c.outdated_info(args.greedy?, verbose?, true, args.greedy_latest?, args.greedy_auto_updates?)
Homebrew.failed = args.named.present? && outdated.present?
end
end
end
def self.verbose?
($stdout.tty? || super) && !quiet?
end
private
def self.json_version(version)
version_hash = {
nil => nil,
true => :default,
"v1" => :v1,
"v2" => :v2,
}
def print_outdated(formulae_or_casks)
formulae_or_casks.each do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
version_hash[version]
end
current_version = if f.alias_changed? && !f.latest_formula.latest_version_installed?
latest = f.latest_formula
"#{latest.name} (#{latest.pkg_version})"
elsif f.head? && outdated_kegs.any? { |k| k.version.to_s == f.pkg_version.to_s }
# There is a newer HEAD but the version number has not changed.
"latest HEAD"
else
f.pkg_version.to_s
end
def self.outdated_formulae(args:)
select_outdated((args.named.to_resolved_formulae.presence || Formula.installed), args:).sort
end
outdated_versions = outdated_kegs.group_by { |keg| Formulary.from_keg(keg).full_name }
.sort_by { |full_name, _kegs| full_name }
.map do |full_name, kegs|
"#{full_name} (#{kegs.map(&:version).join(", ")})"
end.join(", ")
def self.outdated_casks(args:)
if args.named.present?
select_outdated(args.named.to_casks, args:)
else
select_outdated(Cask::Caskroom.casks, args:)
end
end
pinned_version = " [pinned at #{f.pinned_version}]" if f.pinned?
def self.outdated_formulae_casks(args:)
formulae, casks = args.named.to_resolved_formulae_to_casks
puts "#{outdated_versions} < #{current_version}#{pinned_version}"
else
puts f.full_installed_specified_name
end
else
c = formula_or_cask
if formulae.blank? && casks.blank?
formulae = Formula.installed
casks = Cask::Caskroom.casks
end
puts c.outdated_info(args.greedy?, verbose?, false, args.greedy_latest?, args.greedy_auto_updates?)
end
end
end
[select_outdated(formulae, args:).sort, select_outdated(casks, args:)]
end
def json_info(formulae_or_casks)
formulae_or_casks.map do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
def self.select_outdated(formulae_or_casks, args:)
formulae_or_casks.select do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
else
formula_or_cask.outdated?(greedy: args.greedy?, greedy_latest: args.greedy_latest?,
greedy_auto_updates: args.greedy_auto_updates?)
outdated_versions = f.outdated_kegs(fetch_head: args.fetch_HEAD?).map(&:version)
current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
"HEAD"
else
f.pkg_version.to_s
end
{ name: f.full_name,
installed_versions: outdated_versions.map(&:to_s),
current_version:,
pinned: f.pinned?,
pinned_version: f.pinned_version }
else
c = formula_or_cask
c.outdated_info(args.greedy?, verbose?, true, args.greedy_latest?, args.greedy_auto_updates?)
end
end
end
def verbose?
($stdout.tty? || Context.current.verbose?) && !Context.current.quiet?
end
def json_version(version)
version_hash = {
nil => nil,
true => :default,
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
version_hash[version]
end
def outdated_formulae
select_outdated((args.named.to_resolved_formulae.presence || Formula.installed)).sort
end
def outdated_casks
if args.named.present?
select_outdated(args.named.to_casks)
else
select_outdated(Cask::Caskroom.casks)
end
end
def outdated_formulae_casks
formulae, casks = args.named.to_resolved_formulae_to_casks
if formulae.blank? && casks.blank?
formulae = Formula.installed
casks = Cask::Caskroom.casks
end
[select_outdated(formulae).sort, select_outdated(casks)]
end
def select_outdated(formulae_or_casks)
formulae_or_casks.select do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
else
formula_or_cask.outdated?(greedy: args.greedy?, greedy_latest: args.greedy_latest?,
greedy_auto_updates: args.greedy_auto_updates?)
end
end
end
end
end

View File

@ -1,37 +1,35 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "cli/parser"
module Homebrew
module_function
module Cmd
class Pin < AbstractCommand
cmd_args do
description <<~EOS
Pin the specified <formula>, preventing them from being upgraded when
issuing the `brew upgrade` <formula> command. See also `unpin`.
sig { returns(CLI::Parser) }
def pin_args
Homebrew::CLI::Parser.new do
description <<~EOS
Pin the specified <formula>, preventing them from being upgraded when
issuing the `brew upgrade` <formula> command. See also `unpin`.
*Note:* Other packages which depend on newer versions of a pinned formula
might not install or run correctly.
EOS
*Note:* Other packages which depend on newer versions of a pinned formula
might not install or run correctly.
EOS
named_args :installed_formula, min: 1
end
named_args :installed_formula, min: 1
end
end
def pin
args = pin_args.parse
args.named.to_resolved_formulae.each do |f|
if f.pinned?
opoo "#{f.name} already pinned"
elsif !f.pinnable?
onoe "#{f.name} not installed"
else
f.pin
sig { override.void }
def run
args.named.to_resolved_formulae.each do |f|
if f.pinned?
opoo "#{f.name} already pinned"
elsif !f.pinnable?
onoe "#{f.name} not installed"
else
f.pin
end
end
end
end
end

View File

@ -1,169 +1,167 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "formula"
module Homebrew
module_function
module Cmd
class PostgresqlUpgradeDatabase < AbstractCommand
cmd_args do
description <<~EOS
Upgrades the database for the `postgresql` formula.
EOS
sig { returns(CLI::Parser) }
def postgresql_upgrade_database_args
Homebrew::CLI::Parser.new do
description <<~EOS
Upgrades the database for the `postgresql` formula.
EOS
named_args :none
named_args :none
hide_from_man_page!
end
end
sig { void }
def postgresql_upgrade_database
postgresql_upgrade_database_args.parse
odisabled "brew postgresql_upgrade_database",
"using new, versioned e.g. `var/postgres@14` datadir and `pg_upgrade`"
name = "postgresql"
pg = Formula[name]
bin = pg.bin
var = pg.var
version = pg.version
pg_version_file = var/"postgres/PG_VERSION"
pg_version_installed = version.to_s[/^\d+/]
pg_version_data = pg_version_file.read.chomp
if pg_version_installed == pg_version_data
odie <<~EOS
#{name} data already upgraded!
EOS
end
datadir = var/"postgres"
old_datadir = var/"postgres.old"
if old_datadir.exist?
odie <<~EOS
#{old_datadir} already exists!
Remove it if you want to upgrade data automatically.
EOS
end
old_pg_name = "#{name}@#{pg_version_data}"
old_pg_glob = "#{HOMEBREW_CELLAR}/#{old_pg_name}/#{pg_version_data}.*/bin"
old_bin = Pathname.glob(old_pg_glob).first
old_bin ||= begin
Formula[old_pg_name]
ohai "brew install #{old_pg_name}"
system HOMEBREW_BREW_FILE, "install", old_pg_name
Pathname.glob(old_pg_glob).first
rescue FormulaUnavailableError
nil
end
odie "No #{name} #{pg_version_data}.* version installed!" unless old_bin
server_stopped = T.let(false, T::Boolean)
moved_data = T.let(false, T::Boolean)
initdb_run = T.let(false, T::Boolean)
upgraded = T.let(false, T::Boolean)
begin
# Following instructions from:
# https://www.postgresql.org/docs/10/static/pgupgrade.html
ohai "Upgrading #{name} data from #{pg_version_data} to #{pg_version_installed}..."
services_json_output = Utils.popen_read(HOMEBREW_BREW_FILE, "services", "info", "--all", "--json")
services_json = JSON.parse(services_json_output)
loaded_service_names = services_json.select { |sj| sj[:loaded] }.map { |sj| sj[:name] }
if loaded_service_names.include?(name)
system HOMEBREW_BREW_FILE, "services", "stop", name
service_stopped = true
elsif quiet_system "#{bin}/pg_ctl", "-D", datadir, "status"
system "#{bin}/pg_ctl", "-D", datadir, "stop"
server_stopped = true
hide_from_man_page!
end
# Shut down old server if it is up via brew services
system HOMEBREW_BREW_FILE, "services", "stop", old_pg_name if loaded_service_names.include?(old_pg_name)
sig { override.void }
def run
odisabled "brew postgresql_upgrade_database",
"using new, versioned e.g. `var/postgres@14` datadir and `pg_upgrade`"
# get 'lc_collate' from old DB"
unless quiet_system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "status"
system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "start"
end
name = "postgresql"
pg = Formula[name]
bin = pg.bin
var = pg.var
version = pg.version
pg_version_file = var/"postgres/PG_VERSION"
initdb_args = T.let([], T::Array[String])
locale_settings = %w[
lc_collate
lc_ctype
lc_messages
lc_monetary
lc_numeric
lc_time
server_encoding
]
locale_settings.each do |setting|
sql = "SELECT setting FROM pg_settings WHERE name LIKE '#{setting}';"
value = Utils.popen_read("#{old_bin}/psql", "postgres", "-qtAX", "-U", ENV.fetch("USER"), "-c", sql).strip
next if value.empty?
initdb_args += if setting == "server_encoding"
["-E #{value}"]
else
["--#{setting.tr("_", "-")}=#{value}"]
pg_version_installed = version.to_s[/^\d+/]
pg_version_data = pg_version_file.read.chomp
if pg_version_installed == pg_version_data
odie <<~EOS
#{name} data already upgraded!
EOS
end
end
if quiet_system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "status"
system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "stop"
end
ohai "Moving #{name} data from #{datadir} to #{old_datadir}..."
FileUtils.mv datadir, old_datadir
moved_data = true
(var/"postgres").mkpath
ohai "Creating database..."
safe_system "#{bin}/initdb", *initdb_args, "#{var}/postgres"
initdb_run = true
ohai "Migrating and upgrading data..."
(var/"log").cd do
safe_system "#{bin}/pg_upgrade",
"-r",
"-b", old_bin,
"-B", bin,
"-d", old_datadir,
"-D", datadir,
"-j", Hardware::CPU.cores.to_s
end
upgraded = true
ohai "Upgraded #{name} data from #{pg_version_data} to #{pg_version_installed}!"
ohai "Your #{name} #{pg_version_data} data remains at #{old_datadir}"
ensure
if upgraded
if server_stopped
safe_system "#{bin}/pg_ctl", "-D", datadir, "start"
elsif service_stopped
safe_system HOMEBREW_BREW_FILE, "services", "start", name
datadir = var/"postgres"
old_datadir = var/"postgres.old"
if old_datadir.exist?
odie <<~EOS
#{old_datadir} already exists!
Remove it if you want to upgrade data automatically.
EOS
end
else
onoe "Upgrading #{name} data from #{pg_version_data} to #{pg_version_installed} failed!"
if initdb_run
ohai "Removing empty #{name} initdb database..."
FileUtils.rm_r datadir
old_pg_name = "#{name}@#{pg_version_data}"
old_pg_glob = "#{HOMEBREW_CELLAR}/#{old_pg_name}/#{pg_version_data}.*/bin"
old_bin = Pathname.glob(old_pg_glob).first
old_bin ||= begin
Formula[old_pg_name]
ohai "brew install #{old_pg_name}"
system HOMEBREW_BREW_FILE, "install", old_pg_name
Pathname.glob(old_pg_glob).first
rescue FormulaUnavailableError
nil
end
if moved_data
ohai "Moving #{name} data back from #{old_datadir} to #{datadir}..."
FileUtils.mv old_datadir, datadir
end
if server_stopped
system "#{bin}/pg_ctl", "-D", datadir, "start"
elsif service_stopped
system HOMEBREW_BREW_FILE, "services", "start", name
odie "No #{name} #{pg_version_data}.* version installed!" unless old_bin
server_stopped = T.let(false, T::Boolean)
moved_data = T.let(false, T::Boolean)
initdb_run = T.let(false, T::Boolean)
upgraded = T.let(false, T::Boolean)
begin
# Following instructions from:
# https://www.postgresql.org/docs/10/static/pgupgrade.html
ohai "Upgrading #{name} data from #{pg_version_data} to #{pg_version_installed}..."
services_json_output = Utils.popen_read(HOMEBREW_BREW_FILE, "services", "info", "--all", "--json")
services_json = JSON.parse(services_json_output)
loaded_service_names = services_json.select { |sj| sj[:loaded] }.map { |sj| sj[:name] }
if loaded_service_names.include?(name)
system HOMEBREW_BREW_FILE, "services", "stop", name
service_stopped = true
elsif quiet_system "#{bin}/pg_ctl", "-D", datadir, "status"
system "#{bin}/pg_ctl", "-D", datadir, "stop"
server_stopped = true
end
# Shut down old server if it is up via brew services
system HOMEBREW_BREW_FILE, "services", "stop", old_pg_name if loaded_service_names.include?(old_pg_name)
# get 'lc_collate' from old DB"
unless quiet_system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "status"
system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "start"
end
initdb_args = T.let([], T::Array[String])
locale_settings = %w[
lc_collate
lc_ctype
lc_messages
lc_monetary
lc_numeric
lc_time
server_encoding
]
locale_settings.each do |setting|
sql = "SELECT setting FROM pg_settings WHERE name LIKE '#{setting}';"
value = Utils.popen_read("#{old_bin}/psql", "postgres", "-qtAX", "-U", ENV.fetch("USER"), "-c",
sql).strip
next if value.empty?
initdb_args += if setting == "server_encoding"
["-E #{value}"]
else
["--#{setting.tr("_", "-")}=#{value}"]
end
end
if quiet_system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "status"
system "#{old_bin}/pg_ctl", "-w", "-D", datadir, "stop"
end
ohai "Moving #{name} data from #{datadir} to #{old_datadir}..."
FileUtils.mv datadir, old_datadir
moved_data = true
(var/"postgres").mkpath
ohai "Creating database..."
safe_system "#{bin}/initdb", *initdb_args, "#{var}/postgres"
initdb_run = true
ohai "Migrating and upgrading data..."
(var/"log").cd do
safe_system "#{bin}/pg_upgrade",
"-r",
"-b", old_bin,
"-B", bin,
"-d", old_datadir,
"-D", datadir,
"-j", Hardware::CPU.cores.to_s
end
upgraded = true
ohai "Upgraded #{name} data from #{pg_version_data} to #{pg_version_installed}!"
ohai "Your #{name} #{pg_version_data} data remains at #{old_datadir}"
ensure
if upgraded
if server_stopped
safe_system "#{bin}/pg_ctl", "-D", datadir, "start"
elsif service_stopped
safe_system HOMEBREW_BREW_FILE, "services", "start", name
end
else
onoe "Upgrading #{name} data from #{pg_version_data} to #{pg_version_installed} failed!"
if initdb_run
ohai "Removing empty #{name} initdb database..."
FileUtils.rm_r datadir
end
if moved_data
ohai "Moving #{name} data back from #{old_datadir} to #{datadir}..."
FileUtils.mv old_datadir, datadir
end
if server_stopped
system "#{bin}/pg_ctl", "-D", datadir, "start"
elsif service_stopped
system HOMEBREW_BREW_FILE, "services", "start", name
end
end
end
end
end

View File

@ -1,35 +1,33 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "sandbox"
require "formula_installer"
require "cli/parser"
module Homebrew
module_function
module Cmd
class Postinstall < AbstractCommand
cmd_args do
description <<~EOS
Rerun the post-install steps for <formula>.
EOS
sig { returns(CLI::Parser) }
def postinstall_args
Homebrew::CLI::Parser.new do
description <<~EOS
Rerun the post-install steps for <formula>.
EOS
named_args :installed_formula, min: 1
end
named_args :installed_formula, min: 1
end
end
def postinstall
args = postinstall_args.parse
args.named.to_resolved_formulae.each do |f|
ohai "Postinstalling #{f}"
f.install_etc_var
if f.post_install_defined?
fi = FormulaInstaller.new(f, **{ debug: args.debug?, quiet: args.quiet?, verbose: args.verbose? }.compact)
fi.post_install
else
opoo "#{f}: no `post_install` method was defined in the formula!"
sig { override.void }
def run
args.named.to_resolved_formulae.each do |f|
ohai "Postinstalling #{f}"
f.install_etc_var
if f.post_install_defined?
fi = FormulaInstaller.new(f, **{ debug: args.debug?, quiet: args.quiet?, verbose: args.verbose? }.compact)
fi.post_install
else
opoo "#{f}: no `post_install` method was defined in the formula!"
end
end
end
end
end

View File

@ -1,68 +1,66 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "formula"
module Homebrew
module_function
module Cmd
class PyenvSync < AbstractCommand
cmd_args do
description <<~EOS
Create symlinks for Homebrew's installed Python versions in `~/.pyenv/versions`.
sig { returns(CLI::Parser) }
def pyenv_sync_args
Homebrew::CLI::Parser.new do
description <<~EOS
Create symlinks for Homebrew's installed Python versions in `~/.pyenv/versions`.
Note that older patch version symlinks will be created and linked to the minor
version so e.g. Python 3.11.0 will also be symlinked to 3.11.3.
EOS
Note that older patch version symlinks will be created and linked to the minor
version so e.g. Python 3.11.0 will also be symlinked to 3.11.3.
EOS
named_args :none
end
named_args :none
end
end
sig { override.void }
def run
pyenv_root = Pathname(ENV.fetch("HOMEBREW_PYENV_ROOT", Pathname(Dir.home)/".pyenv"))
sig { void }
def pyenv_sync
pyenv_root = Pathname(ENV.fetch("HOMEBREW_PYENV_ROOT", Pathname(Dir.home)/".pyenv"))
# Don't run multiple times at once.
pyenv_sync_running = pyenv_root/".pyenv_sync_running"
return if pyenv_sync_running.exist?
# Don't run multiple times at once.
pyenv_sync_running = pyenv_root/".pyenv_sync_running"
return if pyenv_sync_running.exist?
begin
pyenv_versions = pyenv_root/"versions"
pyenv_versions.mkpath
FileUtils.touch pyenv_sync_running
HOMEBREW_CELLAR.glob("python{,@*}")
.flat_map(&:children)
.each { |path| link_pyenv_versions(path, pyenv_versions) }
begin
pyenv_versions = pyenv_root/"versions"
pyenv_versions.mkpath
FileUtils.touch pyenv_sync_running
pyenv_versions.children
.select(&:symlink?)
.reject(&:exist?)
.each { |path| FileUtils.rm_f path }
ensure
pyenv_sync_running.unlink if pyenv_sync_running.exist?
end
end
pyenv_sync_args.parse
private
HOMEBREW_CELLAR.glob("python{,@*}")
.flat_map(&:children)
.each { |path| link_pyenv_versions(path, pyenv_versions) }
sig { params(path: Pathname, pyenv_versions: Pathname).void }
def link_pyenv_versions(path, pyenv_versions)
pyenv_versions.mkpath
pyenv_versions.children
.select(&:symlink?)
.reject(&:exist?)
.each { |path| FileUtils.rm_f path }
ensure
pyenv_sync_running.unlink if pyenv_sync_running.exist?
end
end
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i
patch_version = version.patch.to_i
sig { params(path: Pathname, pyenv_versions: Pathname).void }
def link_pyenv_versions(path, pyenv_versions)
pyenv_versions.mkpath
(0..patch_version).each do |patch|
link_path = pyenv_versions/"#{major_version}.#{minor_version}.#{patch}"
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i
patch_version = version.patch.to_i
(0..patch_version).each do |patch|
link_path = pyenv_versions/"#{major_version}.#{minor_version}.#{patch}"
FileUtils.rm_f link_path
FileUtils.ln_sf path, link_path
FileUtils.rm_f link_path
FileUtils.ln_sf path, link_path
end
end
end
end
end

View File

@ -1,68 +1,67 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "formula"
module Homebrew
module_function
module Cmd
class RbenvSync < AbstractCommand
cmd_args do
description <<~EOS
Create symlinks for Homebrew's installed Ruby versions in `~/.rbenv/versions`.
sig { returns(CLI::Parser) }
def rbenv_sync_args
Homebrew::CLI::Parser.new do
description <<~EOS
Create symlinks for Homebrew's installed Ruby versions in `~/.rbenv/versions`.
Note that older version symlinks will also be created so e.g. Ruby 3.2.1 will
also be symlinked to 3.2.0.
EOS
Note that older version symlinks will also be created so e.g. Ruby 3.2.1 will
also be symlinked to 3.2.0.
EOS
named_args :none
end
named_args :none
end
end
sig { override.void }
def run
rbenv_root = Pathname(ENV.fetch("HOMEBREW_RBENV_ROOT", Pathname(Dir.home)/".rbenv"))
sig { void }
def rbenv_sync
rbenv_root = Pathname(ENV.fetch("HOMEBREW_RBENV_ROOT", Pathname(Dir.home)/".rbenv"))
# Don't run multiple times at once.
rbenv_sync_running = rbenv_root/".rbenv_sync_running"
return if rbenv_sync_running.exist?
# Don't run multiple times at once.
rbenv_sync_running = rbenv_root/".rbenv_sync_running"
return if rbenv_sync_running.exist?
begin
rbenv_versions = rbenv_root/"versions"
rbenv_versions.mkpath
FileUtils.touch rbenv_sync_running
begin
rbenv_versions = rbenv_root/"versions"
rbenv_versions.mkpath
FileUtils.touch rbenv_sync_running
HOMEBREW_CELLAR.glob("ruby{,@*}")
.flat_map(&:children)
.each { |path| link_rbenv_versions(path, rbenv_versions) }
rbenv_sync_args.parse
rbenv_versions.children
.select(&:symlink?)
.reject(&:exist?)
.each { |path| FileUtils.rm_f path }
ensure
rbenv_sync_running.unlink if rbenv_sync_running.exist?
end
end
HOMEBREW_CELLAR.glob("ruby{,@*}")
.flat_map(&:children)
.each { |path| link_rbenv_versions(path, rbenv_versions) }
private
rbenv_versions.children
.select(&:symlink?)
.reject(&:exist?)
.each { |path| FileUtils.rm_f path }
ensure
rbenv_sync_running.unlink if rbenv_sync_running.exist?
end
end
sig { params(path: Pathname, rbenv_versions: Pathname).void }
def link_rbenv_versions(path, rbenv_versions)
rbenv_versions.mkpath
sig { params(path: Pathname, rbenv_versions: Pathname).void }
def link_rbenv_versions(path, rbenv_versions)
rbenv_versions.mkpath
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i
patch_version = version.patch.to_i || 0
version = Keg.new(path).version
major_version = version.major.to_i
minor_version = version.minor.to_i
patch_version = version.patch.to_i || 0
(0..patch_version).each do |patch|
link_path = rbenv_versions/"#{major_version}.#{minor_version}.#{patch}"
(0..patch_version).each do |patch|
link_path = rbenv_versions/"#{major_version}.#{minor_version}.#{patch}"
FileUtils.rm_f link_path
FileUtils.ln_sf path, link_path
FileUtils.rm_f link_path
FileUtils.ln_sf path, link_path
end
end
end
end
end

View File

@ -1,69 +1,67 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "readall"
require "cli/parser"
require "env_config"
module Homebrew
module_function
module Cmd
class ReadallCmd < AbstractCommand
cmd_args do
description <<~EOS
Import all items from the specified <tap>, or from all installed taps if none is provided.
This can be useful for debugging issues across all items when making
significant changes to `formula.rb`, testing the performance of loading
all items or checking if any current formulae/casks have Ruby issues.
EOS
flag "--os=",
description: "Read using the given operating system. (Pass `all` to simulate all operating systems.)"
flag "--arch=",
description: "Read using the given CPU architecture. (Pass `all` to simulate all architectures.)"
switch "--aliases",
description: "Verify any alias symlinks in each tap."
switch "--syntax",
description: "Syntax-check all of Homebrew's Ruby files (if no <tap> is passed)."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not. " \
"Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--no-simulate",
description: "Don't simulate other system configurations when checking formulae and casks."
sig { returns(CLI::Parser) }
def readall_args
Homebrew::CLI::Parser.new do
description <<~EOS
Import all items from the specified <tap>, or from all installed taps if none is provided.
This can be useful for debugging issues across all items when making
significant changes to `formula.rb`, testing the performance of loading
all items or checking if any current formulae/casks have Ruby issues.
EOS
flag "--os=",
description: "Read using the given operating system. (Pass `all` to simulate all operating systems.)"
flag "--arch=",
description: "Read using the given CPU architecture. (Pass `all` to simulate all architectures.)"
switch "--aliases",
description: "Verify any alias symlinks in each tap."
switch "--syntax",
description: "Syntax-check all of Homebrew's Ruby files (if no <tap> is passed)."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not. " \
"Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--no-simulate",
description: "Don't simulate other system configurations when checking formulae and casks."
named_args :tap
end
end
def readall
args = readall_args.parse
Homebrew.with_no_api_env do
if args.syntax? && args.no_named?
scan_files = "#{HOMEBREW_LIBRARY_PATH}/**/*.rb"
ruby_files = Dir.glob(scan_files).grep_v(%r{/(vendor)/})
Homebrew.failed = true unless Readall.valid_ruby_syntax?(ruby_files)
named_args :tap
end
options = {
aliases: args.aliases?,
no_simulate: args.no_simulate?,
}
options[:os_arch_combinations] = args.os_arch_combinations if args.os || args.arch
sig { override.void }
def run
Homebrew.with_no_api_env do
if args.syntax? && args.no_named?
scan_files = "#{HOMEBREW_LIBRARY_PATH}/**/*.rb"
ruby_files = Dir.glob(scan_files).grep_v(%r{/(vendor)/})
taps = if args.no_named?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
raise UsageError, "`brew readall` needs a tap or `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
Homebrew.failed = true unless Readall.valid_ruby_syntax?(ruby_files)
end
options = {
aliases: args.aliases?,
no_simulate: args.no_simulate?,
}
options[:os_arch_combinations] = args.os_arch_combinations if args.os || args.arch
taps = if args.no_named?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
raise UsageError, "`brew readall` needs a tap or `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
end
Tap.installed
else
args.named.to_installed_taps
end
taps.each do |tap|
Homebrew.failed = true unless Readall.valid_tap?(tap, **options)
end
end
Tap.installed
else
args.named.to_installed_taps
end
taps.each do |tap|
Homebrew.failed = true unless Readall.valid_tap?(tap, **options)
end
end
end

View File

@ -1,12 +1,12 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula_installer"
require "development_tools"
require "messages"
require "install"
require "reinstall"
require "cli/parser"
require "cleanup"
require "cask/utils"
require "cask/macos"
@ -15,172 +15,174 @@ require "upgrade"
require "api"
module Homebrew
sig { returns(CLI::Parser) }
def self.reinstall_args
Homebrew::CLI::Parser.new do
description <<~EOS
Uninstall and then reinstall a <formula> or <cask> using the same options it was
originally installed with, plus any appended options specific to a <formula>.
module Cmd
class Reinstall < AbstractCommand
cmd_args do
description <<~EOS
Uninstall and then reinstall a <formula> or <cask> using the same options it was
originally installed with, plus any appended options specific to a <formula>.
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_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
reinstalled 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 "-f", "--force",
description: "Install without checking for previously installed keg-only or " \
"non-migrated versions."
switch "-v", "--verbose",
description: "Print the verification and post-install steps."
[
[:switch, "--formula", "--formulae", { description: "Treat all named arguments as 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, "--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, "--display-times", {
env: :display_install_times,
description: "Print install times for each formula at the end of the run.",
}],
[:switch, "-g", "--git", {
description: "Create a Git repository, useful for creating patches to the software.",
}],
].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." }],
[: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,
}],
[:switch, "--adopt", {
description: "Adopt existing artifacts in the destination that are identical to those being installed. " \
"Cannot be combined with `--force`.",
}],
[:switch, "--skip-cask-deps", {
description: "Skip installing cask dependencies.",
}],
[:switch, "--zap", {
description: "For use with `brew reinstall --cask`. Remove all files associated with a cask. " \
"*May remove files which are shared between applications.*",
}],
].each do |args|
options = args.pop
send(*args, **options)
conflicts "--formula", args.last
end
cask_options
Unless `HOMEBREW_NO_INSTALL_CLEANUP` is set, `brew cleanup` will then be run for the
reinstalled 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 "-f", "--force",
description: "Install without checking for previously installed keg-only or " \
"non-migrated versions."
switch "-v", "--verbose",
description: "Print the verification and post-install steps."
[
[:switch, "--formula", "--formulae", { description: "Treat all named arguments as 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, "--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, "--display-times", {
env: :display_install_times,
description: "Print install times for each formula at the end of the run.",
}],
[:switch, "-g", "--git", {
description: "Create a Git repository, useful for creating patches to the software.",
}],
].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." }],
[: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,
}],
[:switch, "--adopt", {
description: "Adopt existing artifacts in the destination that are identical to those being installed. " \
"Cannot be combined with `--force`.",
}],
[:switch, "--skip-cask-deps", {
description: "Skip installing cask dependencies.",
}],
[:switch, "--zap", {
description: "For use with `brew reinstall --cask`. Remove all files associated with a cask. " \
"*May remove files which are shared between applications.*",
}],
].each do |args|
options = args.pop
send(*args, **options)
conflicts "--formula", args.last
end
cask_options
conflicts "--build-from-source", "--force-bottle"
conflicts "--build-from-source", "--force-bottle"
named_args [:formula, :cask], min: 1
end
end
def self.reinstall
args = reinstall_args.parse
formulae, casks = args.named.to_formulae_and_casks(method: :resolve)
.partition { |o| o.is_a?(Formula) }
if args.build_from_source?
unless DevelopmentTools.installed?
raise BuildFlagsError.new(["--build-from-source"], bottled: formulae.all?(&:bottled?))
named_args [:formula, :cask], min: 1
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!"
sig { override.void }
def run
formulae, casks = T.cast(
args.named.to_formulae_and_casks(method: :resolve).partition { _1.is_a?(Formula) },
[T::Array[Formula], T::Array[Cask::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
Install.perform_preinstall_checks
formulae.each do |formula|
if formula.pinned?
onoe "#{formula.full_name} is pinned. You must unpin it to reinstall."
next
end
Migrator.migrate_if_needed(formula, force: args.force?)
Homebrew.reinstall_formula(
formula,
flags: args.flags_only,
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?,
git: args.git?,
)
Cleanup.install_formula_clean!(formula)
end
Upgrade.check_installed_dependents(
formulae,
flags: args.flags_only,
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?,
)
if casks.any?
Cask::Reinstall.reinstall_casks(
*casks,
binaries: args.binaries?,
verbose: args.verbose?,
force: args.force?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
quarantine: args.quarantine?,
zap: args.zap?,
)
end
Cleanup.periodic_clean!
Homebrew.messages.display_messages(display_times: args.display_times?)
end
end
Install.perform_preinstall_checks
formulae.each do |formula|
if formula.pinned?
onoe "#{formula.full_name} is pinned. You must unpin it to reinstall."
next
end
Migrator.migrate_if_needed(formula, force: args.force?)
reinstall_formula(
formula,
flags: args.flags_only,
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?,
git: args.git?,
)
Cleanup.install_formula_clean!(formula)
end
Upgrade.check_installed_dependents(
formulae,
flags: args.flags_only,
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?,
)
if casks.any?
Cask::Reinstall.reinstall_casks(
*casks,
binaries: args.binaries?,
verbose: args.verbose?,
force: args.force?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
quarantine: args.quarantine?,
zap: args.zap?,
)
end
Cleanup.periodic_clean!
Homebrew.messages.display_messages(display_times: args.display_times?)
end
end

View File

@ -1,165 +1,165 @@
# typed: true
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "missing_formula"
require "descriptions"
require "cli/parser"
require "search"
module Homebrew
module_function
module Cmd
class SearchCmd < AbstractCommand
PACKAGE_MANAGERS = {
repology: ->(query) { "https://repology.org/projects/?search=#{query}" },
macports: ->(query) { "https://ports.macports.org/search/?q=#{query}" },
fink: ->(query) { "https://pdb.finkproject.org/pdb/browse.php?summary=#{query}" },
opensuse: ->(query) { "https://software.opensuse.org/search?q=#{query}" },
fedora: ->(query) { "https://packages.fedoraproject.org/search?query=#{query}" },
archlinux: ->(query) { "https://archlinux.org/packages/?q=#{query}" },
debian: lambda { |query|
"https://packages.debian.org/search?keywords=#{query}&searchon=names&suite=all&section=all"
},
ubuntu: lambda { |query|
"https://packages.ubuntu.com/search?keywords=#{query}&searchon=names&suite=all&section=all"
},
}.freeze
PACKAGE_MANAGERS = {
repology: ->(query) { "https://repology.org/projects/?search=#{query}" },
macports: ->(query) { "https://ports.macports.org/search/?q=#{query}" },
fink: ->(query) { "https://pdb.finkproject.org/pdb/browse.php?summary=#{query}" },
opensuse: ->(query) { "https://software.opensuse.org/search?q=#{query}" },
fedora: ->(query) { "https://packages.fedoraproject.org/search?query=#{query}" },
archlinux: ->(query) { "https://archlinux.org/packages/?q=#{query}" },
debian: lambda { |query|
"https://packages.debian.org/search?keywords=#{query}&searchon=names&suite=all&section=all"
},
ubuntu: lambda { |query|
"https://packages.ubuntu.com/search?keywords=#{query}&searchon=names&suite=all&section=all"
},
}.freeze
cmd_args do
description <<~EOS
Perform a substring search of cask tokens and formula names for <text>. If <text>
is flanked by slashes, it is interpreted as a regular expression.
EOS
switch "--formula", "--formulae",
description: "Search for formulae."
switch "--cask", "--casks",
description: "Search for casks."
switch "--desc",
description: "Search for formulae with a description matching <text> and casks with " \
"a name or description matching <text>."
switch "--eval-all",
depends_on: "--desc",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--pull-request",
description: "Search for GitHub pull requests containing <text>."
switch "--open",
depends_on: "--pull-request",
description: "Search for only open GitHub pull requests."
switch "--closed",
depends_on: "--pull-request",
description: "Search for only closed GitHub pull requests."
package_manager_switches = PACKAGE_MANAGERS.keys.map { |name| "--#{name}" }
package_manager_switches.each do |s|
switch s,
description: "Search for <text> in the given database."
end
sig { returns(CLI::Parser) }
def search_args
Homebrew::CLI::Parser.new do
description <<~EOS
Perform a substring search of cask tokens and formula names for <text>. If <text>
is flanked by slashes, it is interpreted as a regular expression.
EOS
switch "--formula", "--formulae",
description: "Search for formulae."
switch "--cask", "--casks",
description: "Search for casks."
switch "--desc",
description: "Search for formulae with a description matching <text> and casks with " \
"a name or description matching <text>."
switch "--eval-all",
depends_on: "--desc",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--pull-request",
description: "Search for GitHub pull requests containing <text>."
switch "--open",
depends_on: "--pull-request",
description: "Search for only open GitHub pull requests."
switch "--closed",
depends_on: "--pull-request",
description: "Search for only closed GitHub pull requests."
package_manager_switches = PACKAGE_MANAGERS.keys.map { |name| "--#{name}" }
package_manager_switches.each do |s|
switch s,
description: "Search for <text> in the given database."
conflicts "--desc", "--pull-request"
conflicts "--open", "--closed"
conflicts(*package_manager_switches)
named_args :text_or_regex, min: 1
end
conflicts "--desc", "--pull-request"
conflicts "--open", "--closed"
conflicts(*package_manager_switches)
sig { override.void }
def run
return if search_package_manager
named_args :text_or_regex, min: 1
end
end
query = args.named.join(" ")
string_or_regex = Search.query_regexp(query)
def search
args = search_args.parse
if args.desc?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
raise UsageError, "`brew search --desc` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
end
return if search_package_manager(args)
Search.search_descriptions(string_or_regex, args)
elsif args.pull_request?
search_pull_requests(query)
else
formulae, casks = Search.search_names(string_or_regex, args)
print_results(formulae, casks, query)
end
query = args.named.join(" ")
string_or_regex = Search.query_regexp(query)
puts "Use `brew desc` to list packages with a short description." if args.verbose?
if args.desc?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
raise UsageError, "`brew search --desc` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
print_regex_help
end
Search.search_descriptions(string_or_regex, args)
elsif args.pull_request?
search_pull_requests(query, args)
else
formulae, casks = Search.search_names(string_or_regex, args)
print_results(formulae, casks, query)
end
private
puts "Use `brew desc` to list packages with a short description." if args.verbose?
def print_regex_help
return unless $stdout.tty?
print_regex_help(args)
end
metacharacters = %w[\\ | ( ) [ ] { } ^ $ * + ?].freeze
return unless metacharacters.any? do |char|
args.named.any? do |arg|
arg.include?(char) && !arg.start_with?("/")
end
end
def print_regex_help(args)
return unless $stdout.tty?
opoo <<~EOS
Did you mean to perform a regular expression search?
Surround your query with /slashes/ to search locally by regex.
EOS
end
metacharacters = %w[\\ | ( ) [ ] { } ^ $ * + ?].freeze
return unless metacharacters.any? do |char|
args.named.any? do |arg|
arg.include?(char) && !arg.start_with?("/")
def search_package_manager
package_manager = PACKAGE_MANAGERS.find { |name,| args[:"#{name}?"] }
return false if package_manager.nil?
_, url = package_manager
exec_browser url.call(URI.encode_www_form_component(args.named.join(" ")))
true
end
def search_pull_requests(query)
only = if args.open? && !args.closed?
"open"
elsif args.closed? && !args.open?
"closed"
end
GitHub.print_pull_requests_matching(query, only)
end
def print_results(all_formulae, all_casks, query)
count = all_formulae.size + all_casks.size
if all_formulae.any?
if $stdout.tty?
ohai "Formulae", Formatter.columns(all_formulae)
else
puts all_formulae
end
end
puts if all_formulae.any? && all_casks.any?
if all_casks.any?
if $stdout.tty?
ohai "Casks", Formatter.columns(all_casks)
else
puts all_casks
end
end
print_missing_formula_help(query, count.positive?) if all_casks.exclude?(query)
odie "No formulae or casks found for #{query.inspect}." if count.zero?
end
def print_missing_formula_help(query, found_matches)
return unless $stdout.tty?
reason = MissingFormula.reason(query, silent: true)
return if reason.nil?
if found_matches
puts
puts "If you meant #{query.inspect} specifically:"
end
puts reason
end
end
opoo <<~EOS
Did you mean to perform a regular expression search?
Surround your query with /slashes/ to search locally by regex.
EOS
end
def search_package_manager(args)
package_manager = PACKAGE_MANAGERS.find { |name,| args[:"#{name}?"] }
return false if package_manager.nil?
_, url = package_manager
exec_browser url.call(URI.encode_www_form_component(args.named.join(" ")))
true
end
def search_pull_requests(query, args)
only = if args.open? && !args.closed?
"open"
elsif args.closed? && !args.open?
"closed"
end
GitHub.print_pull_requests_matching(query, only)
end
def print_results(all_formulae, all_casks, query)
count = all_formulae.size + all_casks.size
if all_formulae.any?
if $stdout.tty?
ohai "Formulae", Formatter.columns(all_formulae)
else
puts all_formulae
end
end
puts if all_formulae.any? && all_casks.any?
if all_casks.any?
if $stdout.tty?
ohai "Casks", Formatter.columns(all_casks)
else
puts all_casks
end
end
print_missing_formula_help(query, count.positive?) if all_casks.exclude?(query)
odie "No formulae or casks found for #{query.inspect}." if count.zero?
end
def print_missing_formula_help(query, found_matches)
return unless $stdout.tty?
reason = MissingFormula.reason(query, silent: true)
return if reason.nil?
if found_matches
puts
puts "If you meant #{query.inspect} specifically:"
end
puts reason
end
end

View File

@ -1,88 +1,88 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
module Homebrew
module_function
module Cmd
class TapInfo < AbstractCommand
cmd_args do
description <<~EOS
Show detailed information about one or more <tap>s.
If no <tap> names are provided, display brief statistics for all installed taps.
EOS
switch "--installed",
description: "Show information on each installed tap."
flag "--json",
description: "Print a JSON representation of <tap>. Currently the default and only accepted " \
"value for <version> is `v1`. See the docs for examples of using the JSON " \
"output: <https://docs.brew.sh/Querying-Brew>"
sig { returns(CLI::Parser) }
def tap_info_args
Homebrew::CLI::Parser.new do
description <<~EOS
Show detailed information about one or more <tap>s.
If no <tap> names are provided, display brief statistics for all installed taps.
EOS
switch "--installed",
description: "Show information on each installed tap."
flag "--json",
description: "Print a JSON representation of <tap>. Currently the default and only accepted " \
"value for <version> is `v1`. See the docs for examples of using the JSON " \
"output: <https://docs.brew.sh/Querying-Brew>"
named_args :tap
end
end
def tap_info
args = tap_info_args.parse
taps = if args.installed?
Tap
else
args.named.to_taps
end
if args.json
raise UsageError, "invalid JSON version: #{args.json}" unless ["v1", true].include? args.json
print_tap_json(taps.sort_by(&:to_s))
else
print_tap_info(taps.sort_by(&:to_s))
end
end
def print_tap_info(taps)
if taps.none?
tap_count = 0
formula_count = 0
command_count = 0
private_count = 0
Tap.installed.each do |tap|
tap_count += 1
formula_count += tap.formula_files.size
command_count += tap.command_files.size
private_count += 1 if tap.private?
named_args :tap
end
info = Utils.pluralize("tap", tap_count, include_count: true)
info += ", #{private_count} private"
info += ", #{Utils.pluralize("formula", formula_count, plural: "e", include_count: true)}"
info += ", #{Utils.pluralize("command", command_count, include_count: true)}"
info += ", #{Tap::TAP_DIRECTORY.dup.abv}" if Tap::TAP_DIRECTORY.directory?
puts info
else
info = ""
taps.each_with_index do |tap, i|
puts unless i.zero?
info = "#{tap}: "
if tap.installed?
info += if (contents = tap.contents).blank?
"no commands/casks/formulae"
else
contents.join(", ")
end
info += ", private" if tap.private?
info += "\n#{tap.path} (#{tap.path.abv})"
info += "\nFrom: #{tap.remote.presence || "N/A"}"
sig { override.void }
def run
taps = if args.installed?
Tap
else
info += "Not installed"
args.named.to_taps
end
puts info
if args.json
raise UsageError, "invalid JSON version: #{args.json}" unless ["v1", true].include? args.json
print_tap_json(taps.sort_by(&:to_s))
else
print_tap_info(taps.sort_by(&:to_s))
end
end
private
def print_tap_info(taps)
if taps.none?
tap_count = 0
formula_count = 0
command_count = 0
private_count = 0
Tap.installed.each do |tap|
tap_count += 1
formula_count += tap.formula_files.size
command_count += tap.command_files.size
private_count += 1 if tap.private?
end
info = Utils.pluralize("tap", tap_count, include_count: true)
info += ", #{private_count} private"
info += ", #{Utils.pluralize("formula", formula_count, plural: "e", include_count: true)}"
info += ", #{Utils.pluralize("command", command_count, include_count: true)}"
info += ", #{Tap::TAP_DIRECTORY.dup.abv}" if Tap::TAP_DIRECTORY.directory?
puts info
else
info = ""
taps.each_with_index do |tap, i|
puts unless i.zero?
info = "#{tap}: "
if tap.installed?
info += if (contents = tap.contents).blank?
"no commands/casks/formulae"
else
contents.join(", ")
end
info += ", private" if tap.private?
info += "\n#{tap.path} (#{tap.path.abv})"
info += "\nFrom: #{tap.remote.presence || "N/A"}"
else
info += "Not installed"
end
puts info
end
end
end
def print_tap_json(taps)
puts JSON.pretty_generate(taps.map(&:to_hash))
end
end
end
def print_tap_json(taps)
puts JSON.pretty_generate(taps.map(&:to_hash))
end
end

View File

@ -1,77 +1,75 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
module Homebrew
module_function
module Cmd
class TapCmd < AbstractCommand
cmd_args do
usage_banner "`tap` [<options>] [<user>`/`<repo>] [<URL>]"
description <<~EOS
Tap a formula repository.
If no arguments are provided, list all installed taps.
sig { returns(CLI::Parser) }
def tap_args
Homebrew::CLI::Parser.new do
usage_banner "`tap` [<options>] [<user>`/`<repo>] [<URL>]"
description <<~EOS
Tap a formula repository.
If no arguments are provided, list all installed taps.
With <URL> unspecified, tap a formula repository from GitHub using HTTPS.
Since so many taps are hosted on GitHub, this command is a shortcut for
`brew tap` <user>`/`<repo> `https://github.com/`<user>`/homebrew-`<repo>.
With <URL> unspecified, tap a formula repository from GitHub using HTTPS.
Since so many taps are hosted on GitHub, this command is a shortcut for
`brew tap` <user>`/`<repo> `https://github.com/`<user>`/homebrew-`<repo>.
With <URL> specified, tap a formula repository from anywhere, using
any transport protocol that `git`(1) handles. The one-argument form of `tap`
simplifies but also limits. This two-argument command makes no
assumptions, so taps can be cloned from places other than GitHub and
using protocols other than HTTPS, e.g. SSH, git, HTTP, FTP(S), rsync.
EOS
switch "--full",
description: "Convert a shallow clone to a full clone without untapping. Taps are only cloned as " \
"shallow clones if `--shallow` was originally passed.",
replacement: false,
disable: true
switch "--shallow",
description: "Fetch tap as a shallow clone rather than a full clone. Useful for continuous " \
"integration.",
replacement: false,
disable: true
switch "--[no-]force-auto-update",
hidden: true
switch "--custom-remote",
description: "Install or change a tap with a custom remote. Useful for mirrors."
switch "--repair",
description: "Migrate tapped formulae from symlink-based to directory-based structure."
switch "--eval-all",
description: "Evaluate all the formulae, casks and aliases in the new tap to check validity. " \
"Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--force",
description: "Force install core taps even under API mode."
With <URL> specified, tap a formula repository from anywhere, using
any transport protocol that `git`(1) handles. The one-argument form of `tap`
simplifies but also limits. This two-argument command makes no
assumptions, so taps can be cloned from places other than GitHub and
using protocols other than HTTPS, e.g. SSH, git, HTTP, FTP(S), rsync.
EOS
switch "--full",
description: "Convert a shallow clone to a full clone without untapping. Taps are only cloned as " \
"shallow clones if `--shallow` was originally passed.",
replacement: false,
disable: true
switch "--shallow",
description: "Fetch tap as a shallow clone rather than a full clone. Useful for continuous integration.",
replacement: false,
disable: true
switch "--[no-]force-auto-update",
hidden: true
switch "--custom-remote",
description: "Install or change a tap with a custom remote. Useful for mirrors."
switch "--repair",
description: "Migrate tapped formulae from symlink-based to directory-based structure."
switch "--eval-all",
description: "Evaluate all the formulae, casks and aliases in the new tap to check validity. " \
"Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--force",
description: "Force install core taps even under API mode."
named_args :tap, max: 2
end
end
sig { void }
def tap
args = tap_args.parse
if args.repair?
Tap.installed.each do |tap|
tap.link_completions_and_manpages
tap.fix_remote_configuration
named_args :tap, max: 2
end
elsif args.no_named?
puts Tap.installed.sort_by(&:name)
else
tap = Tap.fetch(args.named.first)
begin
tap.install clone_target: args.named.second,
custom_remote: args.custom_remote?,
quiet: args.quiet?,
verify: args.eval_all? || Homebrew::EnvConfig.eval_all?,
force: args.force?
rescue TapRemoteMismatchError, TapNoCustomRemoteError => e
odie e
rescue TapAlreadyTappedError
nil
sig { override.void }
def run
if args.repair?
Tap.installed.each do |tap|
tap.link_completions_and_manpages
tap.fix_remote_configuration
end
elsif args.no_named?
puts Tap.installed.sort_by(&:name)
else
tap = Tap.fetch(args.named.first)
begin
tap.install clone_target: args.named.second,
custom_remote: args.custom_remote?,
quiet: args.quiet?,
verify: args.eval_all? || Homebrew::EnvConfig.eval_all?,
force: args.force?
rescue TapRemoteMismatchError, TapNoCustomRemoteError => e
odie e
rescue TapAlreadyTappedError
nil
end
end
end
end
end

View File

@ -1,11 +1,11 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "keg"
require "formula"
require "diagnostic"
require "migrator"
require "cli/parser"
require "cask/cask_loader"
require "cask/exceptions"
require "cask/installer"
@ -13,74 +13,72 @@ require "cask/uninstall"
require "uninstall"
module Homebrew
module_function
module Cmd
class UninstallCmd < AbstractCommand
cmd_args do
description <<~EOS
Uninstall a <formula> or <cask>.
EOS
switch "-f", "--force",
description: "Delete all installed versions of <formula>. Uninstall even if <cask> is not " \
"installed, overwrite existing files and ignore errors when removing files."
switch "--zap",
description: "Remove all files associated with a <cask>. " \
"*May remove files which are shared between applications.*"
switch "--ignore-dependencies",
description: "Don't fail uninstall, even if <formula> is a dependency of any installed " \
"formulae."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
sig { returns(CLI::Parser) }
def uninstall_args
Homebrew::CLI::Parser.new do
description <<~EOS
Uninstall a <formula> or <cask>.
EOS
switch "-f", "--force",
description: "Delete all installed versions of <formula>. Uninstall even if <cask> is not " \
"installed, overwrite existing files and ignore errors when removing files."
switch "--zap",
description: "Remove all files associated with a <cask>. " \
"*May remove files which are shared between applications.*"
switch "--ignore-dependencies",
description: "Don't fail uninstall, even if <formula> is a dependency of any installed " \
"formulae."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask"
conflicts "--formula", "--zap"
conflicts "--formula", "--cask"
conflicts "--formula", "--zap"
named_args [:installed_formula, :installed_cask], min: 1
end
end
def uninstall
args = uninstall_args.parse
all_kegs, casks = args.named.to_kegs_to_casks(
ignore_unavailable: args.force?,
all_kegs: args.force?,
)
# If ignore_unavailable is true and the named args
# are a series of invalid kegs and casks,
# #to_kegs_to_casks will return empty arrays.
return if all_kegs.blank? && casks.blank?
kegs_by_rack = all_kegs.group_by(&:rack)
Uninstall.uninstall_kegs(
kegs_by_rack,
casks:,
force: args.force?,
ignore_dependencies: args.ignore_dependencies?,
named_args: args.named,
)
if args.zap?
casks.each do |cask|
odebug "Zapping Cask #{cask}"
raise Cask::CaskNotInstalledError, cask if !cask.installed? && !args.force?
Cask::Installer.new(cask, verbose: args.verbose?, force: args.force?).zap
named_args [:installed_formula, :installed_cask], min: 1
end
else
Cask::Uninstall.uninstall_casks(
*casks,
verbose: args.verbose?,
force: args.force?,
)
end
Cleanup.autoremove if Homebrew::EnvConfig.autoremove?
sig { override.void }
def run
all_kegs, casks = args.named.to_kegs_to_casks(
ignore_unavailable: args.force?,
all_kegs: args.force?,
)
# If ignore_unavailable is true and the named args
# are a series of invalid kegs and casks,
# #to_kegs_to_casks will return empty arrays.
return if all_kegs.blank? && casks.blank?
kegs_by_rack = all_kegs.group_by(&:rack)
Uninstall.uninstall_kegs(
kegs_by_rack,
casks:,
force: args.force?,
ignore_dependencies: args.ignore_dependencies?,
named_args: args.named,
)
if args.zap?
casks.each do |cask|
odebug "Zapping Cask #{cask}"
raise Cask::CaskNotInstalledError, cask if !cask.installed? && !args.force?
Cask::Installer.new(cask, verbose: args.verbose?, force: args.force?).zap
end
else
Cask::Uninstall.uninstall_casks(
*casks,
verbose: args.verbose?,
force: args.force?,
)
end
Cleanup.autoremove if Homebrew::EnvConfig.autoremove?
end
end
end
end

View File

@ -1,41 +1,39 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "unlink"
module Homebrew
module_function
module Cmd
class UnlinkCmd < AbstractCommand
cmd_args do
description <<~EOS
Remove symlinks for <formula> from Homebrew's prefix. This can be useful
for temporarily disabling a formula:
`brew unlink` <formula> `&&` <commands> `&& brew link` <formula>
EOS
switch "-n", "--dry-run",
description: "List files which would be unlinked without actually unlinking or " \
"deleting any files."
sig { returns(CLI::Parser) }
def unlink_args
Homebrew::CLI::Parser.new do
description <<~EOS
Remove symlinks for <formula> from Homebrew's prefix. This can be useful
for temporarily disabling a formula:
`brew unlink` <formula> `&&` <commands> `&& brew link` <formula>
EOS
switch "-n", "--dry-run",
description: "List files which would be unlinked without actually unlinking or " \
"deleting any files."
named_args :installed_formula, min: 1
end
end
def unlink
args = unlink_args.parse
options = { dry_run: args.dry_run?, verbose: args.verbose? }
args.named.to_default_kegs.each do |keg|
if args.dry_run?
puts "Would remove:"
keg.unlink(**options)
next
named_args :installed_formula, min: 1
end
Unlink.unlink(keg, dry_run: args.dry_run?, verbose: args.verbose?)
sig { override.void }
def run
options = { dry_run: args.dry_run?, verbose: args.verbose? }
args.named.to_default_kegs.each do |keg|
if args.dry_run?
puts "Would remove:"
keg.unlink(**options)
next
end
Unlink.unlink(keg, dry_run: args.dry_run?, verbose: args.verbose?)
end
end
end
end
end

View File

@ -1,34 +1,32 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "cli/parser"
module Homebrew
module_function
module Cmd
class Unpin < AbstractCommand
cmd_args do
description <<~EOS
Unpin <formula>, allowing them to be upgraded by `brew upgrade` <formula>.
See also `pin`.
EOS
sig { returns(CLI::Parser) }
def unpin_args
Homebrew::CLI::Parser.new do
description <<~EOS
Unpin <formula>, allowing them to be upgraded by `brew upgrade` <formula>.
See also `pin`.
EOS
named_args :installed_formula, min: 1
end
named_args :installed_formula, min: 1
end
end
def unpin
args = unpin_args.parse
args.named.to_resolved_formulae.each do |f|
if f.pinned?
f.unpin
elsif !f.pinnable?
onoe "#{f.name} not installed"
else
opoo "#{f.name} not pinned"
sig { override.void }
def run
args.named.to_resolved_formulae.each do |f|
if f.pinned?
f.unpin
elsif !f.pinnable?
onoe "#{f.name} not installed"
else
opoo "#{f.name} not pinned"
end
end
end
end
end

View File

@ -1,51 +1,50 @@
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "untap"
module Homebrew
sig { returns(CLI::Parser) }
def self.untap_args
Homebrew::CLI::Parser.new do
description <<~EOS
Remove a tapped formula repository.
EOS
switch "-f", "--force",
description: "Untap even if formulae or casks from this tap are currently installed."
module Cmd
class UntapCmd < AbstractCommand
cmd_args do
description <<~EOS
Remove a tapped formula repository.
EOS
switch "-f", "--force",
description: "Untap even if formulae or casks from this tap are currently installed."
named_args :tap, min: 1
end
end
sig { void }
def self.untap
args = untap_args.parse
args.named.to_installed_taps.each do |tap|
odie "Untapping #{tap} is not allowed" if tap.core_tap? && Homebrew::EnvConfig.no_install_from_api?
if Homebrew::EnvConfig.no_install_from_api? || (!tap.core_tap? && !tap.core_cask_tap?)
installed_tap_formulae = Untap.installed_formulae_for(tap:)
installed_tap_casks = Untap.installed_casks_for(tap:)
if installed_tap_formulae.present? || installed_tap_casks.present?
installed_names = (installed_tap_formulae + installed_tap_casks.map(&:token)).join("\n")
if args.force? || Homebrew::EnvConfig.developer?
opoo <<~EOS
Untapping #{tap} even though it contains the following installed formulae or casks:
#{installed_names}
EOS
else
odie <<~EOS
Refusing to untap #{tap} because it contains the following installed formulae or casks:
#{installed_names}
EOS
end
end
named_args :tap, min: 1
end
tap.uninstall manual: true
sig { override.void }
def run
args.named.to_installed_taps.each do |tap|
odie "Untapping #{tap} is not allowed" if tap.core_tap? && Homebrew::EnvConfig.no_install_from_api?
if Homebrew::EnvConfig.no_install_from_api? || (!tap.core_tap? && !tap.core_cask_tap?)
installed_tap_formulae = Untap.installed_formulae_for(tap:)
installed_tap_casks = Untap.installed_casks_for(tap:)
if installed_tap_formulae.present? || installed_tap_casks.present?
installed_names = (installed_tap_formulae + installed_tap_casks.map(&:token)).join("\n")
if args.force? || Homebrew::EnvConfig.developer?
opoo <<~EOS
Untapping #{tap} even though it contains the following installed formulae or casks:
#{installed_names}
EOS
else
odie <<~EOS
Refusing to untap #{tap} because it contains the following installed formulae or casks:
#{installed_names}
EOS
end
end
end
tap.uninstall manual: true
end
end
end
end
end

View File

@ -1,6 +1,7 @@
# typed: true
# frozen_string_literal: true
require "abstract_command"
require "migrator"
require "formulary"
require "cask/cask_loader"
@ -8,405 +9,406 @@ require "cask/migrator"
require "descriptions"
require "cleanup"
require "description_cache_store"
require "cli/parser"
require "settings"
require "linuxbrew-core-migration"
module Homebrew
module_function
def auto_update_header(args:)
@auto_update_header ||= begin
ohai "Auto-updated Homebrew!" if args.auto_update?
true
end
end
sig { returns(CLI::Parser) }
def update_report_args
Homebrew::CLI::Parser.new do
description <<~EOS
The Ruby implementation of `brew update`. Never called manually.
EOS
switch "--auto-update", "--preinstall",
description: "Run in 'auto-update' mode (faster, less output)."
switch "-f", "--force",
description: "Treat installed and updated formulae as if they are from " \
"the same taps and migrate them anyway."
hide_from_man_page!
end
end
def update_report
return output_update_report if $stdout.tty?
redirect_stdout($stderr) do
output_update_report
end
end
def output_update_report
args = update_report_args.parse
# Run `brew update` (again) if we've got a linuxbrew-core CoreTap
if CoreTap.instance.installed? && CoreTap.instance.linuxbrew_core? &&
ENV["HOMEBREW_LINUXBREW_CORE_MIGRATION"].blank?
ohai "Re-running `brew update` for linuxbrew-core migration"
if Homebrew::EnvConfig.core_git_remote != HOMEBREW_CORE_DEFAULT_GIT_REMOTE
opoo <<~EOS
HOMEBREW_CORE_GIT_REMOTE was set: #{Homebrew::EnvConfig.core_git_remote}.
It has been unset for the migration.
You may need to change this from a linuxbrew-core mirror to a homebrew-core one.
module Cmd
class UpdateReport < AbstractCommand
cmd_args do
description <<~EOS
The Ruby implementation of `brew update`. Never called manually.
EOS
switch "--auto-update", "--preinstall",
description: "Run in 'auto-update' mode (faster, less output)."
switch "-f", "--force",
description: "Treat installed and updated formulae as if they are from " \
"the same taps and migrate them anyway."
hide_from_man_page!
end
ENV.delete("HOMEBREW_CORE_GIT_REMOTE")
if Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN
opoo <<~EOS
HOMEBREW_BOTTLE_DOMAIN was set: #{Homebrew::EnvConfig.bottle_domain}.
It has been unset for the migration.
You may need to change this from a Linuxbrew package mirror to a Homebrew one.
sig { override.void }
def run
return output_update_report if $stdout.tty?
EOS
redirect_stdout($stderr) do
output_update_report
end
end
ENV.delete("HOMEBREW_BOTTLE_DOMAIN")
ENV["HOMEBREW_LINUXBREW_CORE_MIGRATION"] = "1"
FileUtils.rm_f HOMEBREW_LOCKS/"update"
private
update_args = []
update_args << "--auto-update" if args.auto_update?
update_args << "--force" if args.force?
exec HOMEBREW_BREW_FILE, "update", *update_args
end
if ENV["HOMEBREW_ADDITIONAL_GOOGLE_ANALYTICS_ID"].present?
opoo "HOMEBREW_ADDITIONAL_GOOGLE_ANALYTICS_ID is now a no-op so can be unset."
puts "All Homebrew Google Analytics code and data was destroyed."
end
if ENV["HOMEBREW_NO_GOOGLE_ANALYTICS"].present?
opoo "HOMEBREW_NO_GOOGLE_ANALYTICS is now a no-op so can be unset."
puts "All Homebrew Google Analytics code and data was destroyed."
end
unless args.quiet?
analytics_message
donation_message
install_from_api_message
end
tap_or_untap_core_taps_if_necessary
updated = false
new_tag = nil
initial_revision = ENV["HOMEBREW_UPDATE_BEFORE"].to_s
current_revision = ENV["HOMEBREW_UPDATE_AFTER"].to_s
odie "update-report should not be called directly!" if initial_revision.empty? || current_revision.empty?
if initial_revision != current_revision
auto_update_header(args:)
updated = true
old_tag = Settings.read "latesttag"
new_tag = Utils.popen_read(
"git", "-C", HOMEBREW_REPOSITORY, "tag", "--list", "--sort=-version:refname", "*.*"
).lines.first.chomp
Settings.write "latesttag", new_tag if new_tag != old_tag
if new_tag == old_tag
ohai "Updated Homebrew from #{shorten_revision(initial_revision)} to #{shorten_revision(current_revision)}."
elsif old_tag.blank?
ohai "Updated Homebrew from #{shorten_revision(initial_revision)} " \
"to #{new_tag} (#{shorten_revision(current_revision)})."
else
ohai "Updated Homebrew from #{old_tag} (#{shorten_revision(initial_revision)}) " \
"to #{new_tag} (#{shorten_revision(current_revision)})."
def auto_update_header
@auto_update_header ||= begin
ohai "Auto-updated Homebrew!" if args.auto_update?
true
end
end
end
# Check if we can parse the JSON and do any Ruby-side follow-up.
unless Homebrew::EnvConfig.no_install_from_api?
Homebrew::API::Formula.write_names_and_aliases
Homebrew::API::Cask.write_names
end
def output_update_report
# Run `brew update` (again) if we've got a linuxbrew-core CoreTap
if CoreTap.instance.installed? && CoreTap.instance.linuxbrew_core? &&
ENV["HOMEBREW_LINUXBREW_CORE_MIGRATION"].blank?
ohai "Re-running `brew update` for linuxbrew-core migration"
Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
return if Homebrew::EnvConfig.disable_load_formula?
if Homebrew::EnvConfig.core_git_remote != HOMEBREW_CORE_DEFAULT_GIT_REMOTE
opoo <<~EOS
HOMEBREW_CORE_GIT_REMOTE was set: #{Homebrew::EnvConfig.core_git_remote}.
It has been unset for the migration.
You may need to change this from a linuxbrew-core mirror to a homebrew-core one.
migrate_gcc_dependents_if_needed
hub = ReporterHub.new
updated_taps = []
Tap.installed.each do |tap|
next if !tap.git? || tap.git_repo.origin_url.nil?
next if (tap.core_tap? || tap.core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api?
if ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"].present? && tap.core_tap? &&
Settings.read("linuxbrewmigrated") != "true"
ohai "Migrating formulae from linuxbrew-core to homebrew-core"
LINUXBREW_CORE_MIGRATION_LIST.each do |name|
begin
formula = Formula[name]
rescue FormulaUnavailableError
next
EOS
end
next unless formula.any_version_installed?
ENV.delete("HOMEBREW_CORE_GIT_REMOTE")
keg = formula.installed_kegs.last
tab = Tab.for_keg(keg)
# force a `brew upgrade` from the linuxbrew-core version to the homebrew-core version (even if lower)
tab.source["versions"]["version_scheme"] = -1
tab.write
if Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN
opoo <<~EOS
HOMEBREW_BOTTLE_DOMAIN was set: #{Homebrew::EnvConfig.bottle_domain}.
It has been unset for the migration.
You may need to change this from a Linuxbrew package mirror to a Homebrew one.
EOS
end
ENV.delete("HOMEBREW_BOTTLE_DOMAIN")
ENV["HOMEBREW_LINUXBREW_CORE_MIGRATION"] = "1"
FileUtils.rm_f HOMEBREW_LOCKS/"update"
update_args = []
update_args << "--auto-update" if args.auto_update?
update_args << "--force" if args.force?
exec HOMEBREW_BREW_FILE, "update", *update_args
end
Settings.write "linuxbrewmigrated", true
end
if ENV["HOMEBREW_ADDITIONAL_GOOGLE_ANALYTICS_ID"].present?
opoo "HOMEBREW_ADDITIONAL_GOOGLE_ANALYTICS_ID is now a no-op so can be unset."
puts "All Homebrew Google Analytics code and data was destroyed."
end
begin
reporter = Reporter.new(tap)
rescue Reporter::ReporterRevisionUnsetError => e
onoe "#{e.message}\n#{Utils::Backtrace.clean(e)&.join("\n")}" if Homebrew::EnvConfig.developer?
next
end
if reporter.updated?
updated_taps << tap.name
hub.add(reporter, auto_update: args.auto_update?)
end
end
if ENV["HOMEBREW_NO_GOOGLE_ANALYTICS"].present?
opoo "HOMEBREW_NO_GOOGLE_ANALYTICS is now a no-op so can be unset."
puts "All Homebrew Google Analytics code and data was destroyed."
end
# If we're installing from the API: we cannot use Git to check for #
# differences in packages so instead use {formula,cask}_names.txt to do so.
# The first time this runs: we won't yet have a base state
# ({formula,cask}_names.before.txt) to compare against so we don't output a
# anything and just copy the files for next time.
unless Homebrew::EnvConfig.no_install_from_api?
api_cache = Homebrew::API::HOMEBREW_CACHE_API
core_tap = CoreTap.instance
cask_tap = CoreCaskTap.instance
[
[:formula, core_tap, core_tap.formula_dir],
[:cask, cask_tap, cask_tap.cask_dir],
].each do |type, tap, dir|
names_txt = api_cache/"#{type}_names.txt"
next unless names_txt.exist?
unless args.quiet?
analytics_message
donation_message
install_from_api_message
end
names_before_txt = api_cache/"#{type}_names.before.txt"
if names_before_txt.exist?
reporter = Reporter.new(
tap,
api_names_txt: names_txt,
api_names_before_txt: names_before_txt,
api_dir_prefix: dir,
)
tap_or_untap_core_taps_if_necessary
updated = false
new_tag = nil
initial_revision = ENV["HOMEBREW_UPDATE_BEFORE"].to_s
current_revision = ENV["HOMEBREW_UPDATE_AFTER"].to_s
odie "update-report should not be called directly!" if initial_revision.empty? || current_revision.empty?
if initial_revision != current_revision
auto_update_header
updated = true
old_tag = Settings.read "latesttag"
new_tag = Utils.popen_read(
"git", "-C", HOMEBREW_REPOSITORY, "tag", "--list", "--sort=-version:refname", "*.*"
).lines.first.chomp
Settings.write "latesttag", new_tag if new_tag != old_tag
if new_tag == old_tag
ohai "Updated Homebrew from #{shorten_revision(initial_revision)} " \
"to #{shorten_revision(current_revision)}."
elsif old_tag.blank?
ohai "Updated Homebrew from #{shorten_revision(initial_revision)} " \
"to #{new_tag} (#{shorten_revision(current_revision)})."
else
ohai "Updated Homebrew from #{old_tag} (#{shorten_revision(initial_revision)}) " \
"to #{new_tag} (#{shorten_revision(current_revision)})."
end
end
# Check if we can parse the JSON and do any Ruby-side follow-up.
unless Homebrew::EnvConfig.no_install_from_api?
Homebrew::API::Formula.write_names_and_aliases
Homebrew::API::Cask.write_names
end
Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
return if Homebrew::EnvConfig.disable_load_formula?
migrate_gcc_dependents_if_needed
hub = ReporterHub.new
updated_taps = []
Tap.installed.each do |tap|
next if !tap.git? || tap.git_repo.origin_url.nil?
next if (tap.core_tap? || tap.core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api?
if ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"].present? && tap.core_tap? &&
Settings.read("linuxbrewmigrated") != "true"
ohai "Migrating formulae from linuxbrew-core to homebrew-core"
LINUXBREW_CORE_MIGRATION_LIST.each do |name|
begin
formula = Formula[name]
rescue FormulaUnavailableError
next
end
next unless formula.any_version_installed?
keg = formula.installed_kegs.last
tab = Tab.for_keg(keg)
# force a `brew upgrade` from the linuxbrew-core version to the homebrew-core version (even if lower)
tab.source["versions"]["version_scheme"] = -1
tab.write
end
Settings.write "linuxbrewmigrated", true
end
begin
reporter = Reporter.new(tap)
rescue Reporter::ReporterRevisionUnsetError => e
onoe "#{e.message}\n#{Utils::Backtrace.clean(e)&.join("\n")}" if Homebrew::EnvConfig.developer?
next
end
if reporter.updated?
updated_taps << tap.name
hub.add(reporter, auto_update: args.auto_update?)
end
else
FileUtils.cp names_txt, names_before_txt
end
end
end
unless updated_taps.empty?
auto_update_header(args:)
puts "Updated #{Utils.pluralize("tap", updated_taps.count, include_count: true)} (#{updated_taps.to_sentence})."
updated = true
end
if updated
if hub.empty?
puts no_changes_message unless args.quiet?
else
if ENV.fetch("HOMEBREW_UPDATE_REPORT_ONLY_INSTALLED", false)
opoo "HOMEBREW_UPDATE_REPORT_ONLY_INSTALLED is now the default behaviour, " \
"so you can unset it from your environment."
end
hub.dump(auto_update: args.auto_update?) unless args.quiet?
hub.reporters.each(&:migrate_tap_migration)
hub.reporters.each(&:migrate_cask_rename)
hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) }
# If we're installing from the API: we cannot use Git to check for #
# differences in packages so instead use {formula,cask}_names.txt to do so.
# The first time this runs: we won't yet have a base state
# ({formula,cask}_names.before.txt) to compare against so we don't output a
# anything and just copy the files for next time.
unless Homebrew::EnvConfig.no_install_from_api?
api_cache = Homebrew::API::HOMEBREW_CACHE_API
core_tap = CoreTap.instance
cask_tap = CoreCaskTap.instance
[
[:formula, core_tap, core_tap.formula_dir],
[:cask, cask_tap, cask_tap.cask_dir],
].each do |type, tap, dir|
names_txt = api_cache/"#{type}_names.txt"
next unless names_txt.exist?
CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db)
.update_from_report!(hub)
end
CacheStoreDatabase.use(:cask_descriptions) do |db|
CaskDescriptionCacheStore.new(db)
.update_from_report!(hub)
end
end
puts if args.auto_update?
elsif !args.auto_update? && !ENV["HOMEBREW_UPDATE_FAILED"] && !ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"]
puts "Already up-to-date." unless args.quiet?
end
Commands.rebuild_commands_completion_list
link_completions_manpages_and_docs
Tap.installed.each(&:link_completions_and_manpages)
failed_fetch_dirs = ENV["HOMEBREW_MISSING_REMOTE_REF_DIRS"]&.split("\n")
if failed_fetch_dirs.present?
failed_fetch_taps = failed_fetch_dirs.map { |dir| Tap.from_path(dir) }
ofail <<~EOS
Some taps failed to update!
The following taps can not read their remote branches:
#{failed_fetch_taps.join("\n ")}
This is happening because the remote branch was renamed or deleted.
Reset taps to point to the correct remote branches by running `brew tap --repair`
EOS
end
return if new_tag.blank? || new_tag == old_tag || args.quiet?
puts
new_major_version, new_minor_version, new_patch_version = new_tag.split(".").map(&:to_i)
old_major_version, old_minor_version = (old_tag.split(".")[0, 2]).map(&:to_i) if old_tag.present?
if old_tag.blank? || new_major_version > old_major_version || new_minor_version > old_minor_version
puts <<~EOS
The #{new_major_version}.#{new_minor_version}.0 release notes are available on the Homebrew Blog:
#{Formatter.url("https://brew.sh/blog/#{new_major_version}.#{new_minor_version}.0")}
EOS
end
return if new_patch_version.zero?
puts <<~EOS
The #{new_tag} changelog can be found at:
#{Formatter.url("https://github.com/Homebrew/brew/releases/tag/#{new_tag}")}
EOS
end
def no_changes_message
"No changes to formulae or casks."
end
def shorten_revision(revision)
Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp
end
def tap_or_untap_core_taps_if_necessary
return if ENV["HOMEBREW_UPDATE_TEST"]
if Homebrew::EnvConfig.no_install_from_api?
return if Homebrew::EnvConfig.automatically_set_no_install_from_api?
core_tap = CoreTap.instance
return if core_tap.installed?
core_tap.ensure_installed!
revision = CoreTap.instance.git_head
ENV["HOMEBREW_UPDATE_BEFORE_HOMEBREW_HOMEBREW_CORE"] = revision
ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision
else
return if Homebrew::EnvConfig.developer? || ENV["HOMEBREW_DEV_CMD_RUN"]
return if ENV["HOMEBREW_GITHUB_HOSTED_RUNNER"] || ENV["GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED"]
return if (HOMEBREW_PREFIX/".homebrewdocker").exist?
tap_output_header_printed = T.let(false, T::Boolean)
[CoreTap.instance, CoreCaskTap.instance].each do |tap|
next unless tap.installed?
if tap.git_branch == "master" &&
(Date.parse(T.must(tap.git_repo.last_commit_date)) <= Date.today.prev_month)
ohai "#{tap.name} is old and unneeded, untapping to save space..."
tap.uninstall
else
unless tap_output_header_printed
puts "Installing from the API is now the default behaviour!"
puts "You can save space and time by running:"
tap_output_header_printed = true
names_before_txt = api_cache/"#{type}_names.before.txt"
if names_before_txt.exist?
reporter = Reporter.new(
tap,
api_names_txt: names_txt,
api_names_before_txt: names_before_txt,
api_dir_prefix: dir,
)
if reporter.updated?
updated_taps << tap.name
hub.add(reporter, auto_update: args.auto_update?)
end
else
FileUtils.cp names_txt, names_before_txt
end
end
end
unless updated_taps.empty?
auto_update_header
puts "Updated #{Utils.pluralize("tap", updated_taps.count,
include_count: true)} (#{updated_taps.to_sentence})."
updated = true
end
if updated
if hub.empty?
puts no_changes_message unless args.quiet?
else
if ENV.fetch("HOMEBREW_UPDATE_REPORT_ONLY_INSTALLED", false)
opoo "HOMEBREW_UPDATE_REPORT_ONLY_INSTALLED is now the default behaviour, " \
"so you can unset it from your environment."
end
hub.dump(auto_update: args.auto_update?) unless args.quiet?
hub.reporters.each(&:migrate_tap_migration)
hub.reporters.each(&:migrate_cask_rename)
hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) }
CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db)
.update_from_report!(hub)
end
CacheStoreDatabase.use(:cask_descriptions) do |db|
CaskDescriptionCacheStore.new(db)
.update_from_report!(hub)
end
end
puts if args.auto_update?
elsif !args.auto_update? && !ENV["HOMEBREW_UPDATE_FAILED"] && !ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"]
puts "Already up-to-date." unless args.quiet?
end
Commands.rebuild_commands_completion_list
link_completions_manpages_and_docs
Tap.installed.each(&:link_completions_and_manpages)
failed_fetch_dirs = ENV["HOMEBREW_MISSING_REMOTE_REF_DIRS"]&.split("\n")
if failed_fetch_dirs.present?
failed_fetch_taps = failed_fetch_dirs.map { |dir| Tap.from_path(dir) }
ofail <<~EOS
Some taps failed to update!
The following taps can not read their remote branches:
#{failed_fetch_taps.join("\n ")}
This is happening because the remote branch was renamed or deleted.
Reset taps to point to the correct remote branches by running `brew tap --repair`
EOS
end
return if new_tag.blank? || new_tag == old_tag || args.quiet?
puts
new_major_version, new_minor_version, new_patch_version = new_tag.split(".").map(&:to_i)
old_major_version, old_minor_version = (old_tag.split(".")[0, 2]).map(&:to_i) if old_tag.present?
if old_tag.blank? || new_major_version > old_major_version || new_minor_version > old_minor_version
puts <<~EOS
The #{new_major_version}.#{new_minor_version}.0 release notes are available on the Homebrew Blog:
#{Formatter.url("https://brew.sh/blog/#{new_major_version}.#{new_minor_version}.0")}
EOS
end
return if new_patch_version.zero?
puts <<~EOS
The #{new_tag} changelog can be found at:
#{Formatter.url("https://github.com/Homebrew/brew/releases/tag/#{new_tag}")}
EOS
end
def no_changes_message
"No changes to formulae or casks."
end
def shorten_revision(revision)
Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp
end
def tap_or_untap_core_taps_if_necessary
return if ENV["HOMEBREW_UPDATE_TEST"]
if Homebrew::EnvConfig.no_install_from_api?
return if Homebrew::EnvConfig.automatically_set_no_install_from_api?
core_tap = CoreTap.instance
return if core_tap.installed?
core_tap.ensure_installed!
revision = CoreTap.instance.git_head
ENV["HOMEBREW_UPDATE_BEFORE_HOMEBREW_HOMEBREW_CORE"] = revision
ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision
else
return if Homebrew::EnvConfig.developer? || ENV["HOMEBREW_DEV_CMD_RUN"]
return if ENV["HOMEBREW_GITHUB_HOSTED_RUNNER"] || ENV["GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED"]
return if (HOMEBREW_PREFIX/".homebrewdocker").exist?
tap_output_header_printed = T.let(false, T::Boolean)
[CoreTap.instance, CoreCaskTap.instance].each do |tap|
next unless tap.installed?
if tap.git_branch == "master" &&
(Date.parse(T.must(tap.git_repo.last_commit_date)) <= Date.today.prev_month)
ohai "#{tap.name} is old and unneeded, untapping to save space..."
tap.uninstall
else
unless tap_output_header_printed
puts "Installing from the API is now the default behaviour!"
puts "You can save space and time by running:"
tap_output_header_printed = true
end
puts " brew untap #{tap.name}"
end
end
puts " brew untap #{tap.name}"
end
end
def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY)
command = "brew update"
Utils::Link.link_completions(repository, command)
Utils::Link.link_manpages(repository, command)
Utils::Link.link_docs(repository, command)
rescue => e
ofail <<~EOS
Failed to link all completions, docs and manpages:
#{e}
EOS
end
def migrate_gcc_dependents_if_needed
# do nothing
end
def analytics_message
return if Utils::Analytics.messages_displayed?
return if Utils::Analytics.no_message_output?
if Utils::Analytics.disabled? && !Utils::Analytics.influx_message_displayed?
ohai "Homebrew's analytics have entirely moved to our InfluxDB instance in the EU."
puts "We gather less data than before and have destroyed all Google Analytics data:"
puts " #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}"
puts "Please reconsider re-enabling analytics to help our volunteer maintainers with:"
puts " brew analytics on"
elsif !Utils::Analytics.disabled?
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1"
# Use the shell's audible bell.
print "\a"
# Use an extra newline and bold to avoid this being missed.
ohai "Homebrew collects anonymous analytics."
puts <<~EOS
#{Tty.bold}Read the analytics documentation (and how to opt-out) here:
#{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}
No analytics have been recorded yet (nor will be during this `brew` run).
EOS
end
# Consider the messages possibly missed if not a TTY.
Utils::Analytics.messages_displayed! if $stdout.tty?
end
def donation_message
return if Settings.read("donationmessage") == "true"
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#donations")}\n\n"
# Consider the message possibly missed if not a TTY.
Settings.write "donationmessage", true if $stdout.tty?
end
def install_from_api_message
return if Settings.read("installfromapimessage") == "true"
no_install_from_api_set = Homebrew::EnvConfig.no_install_from_api? &&
!Homebrew::EnvConfig.automatically_set_no_install_from_api?
return unless no_install_from_api_set
ohai "You have HOMEBREW_NO_INSTALL_FROM_API set"
puts "Homebrew >=4.1.0 is dramatically faster and less error-prone when installing"
puts "from the JSON API. Please consider unsetting HOMEBREW_NO_INSTALL_FROM_API."
puts "This message will only be printed once."
puts "\n\n"
# Consider the message possibly missed if not a TTY.
Settings.write "installfromapimessage", true if $stdout.tty?
end
end
end
def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY)
command = "brew update"
Utils::Link.link_completions(repository, command)
Utils::Link.link_manpages(repository, command)
Utils::Link.link_docs(repository, command)
rescue => e
ofail <<~EOS
Failed to link all completions, docs and manpages:
#{e}
EOS
end
def migrate_gcc_dependents_if_needed
# do nothing
end
def analytics_message
return if Utils::Analytics.messages_displayed?
return if Utils::Analytics.no_message_output?
if Utils::Analytics.disabled? && !Utils::Analytics.influx_message_displayed?
ohai "Homebrew's analytics have entirely moved to our InfluxDB instance in the EU."
puts "We gather less data than before and have destroyed all Google Analytics data:"
puts " #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}"
puts "Please reconsider re-enabling analytics to help our volunteer maintainers with:"
puts " brew analytics on"
elsif !Utils::Analytics.disabled?
ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1"
# Use the shell's audible bell.
print "\a"
# Use an extra newline and bold to avoid this being missed.
ohai "Homebrew collects anonymous analytics."
puts <<~EOS
#{Tty.bold}Read the analytics documentation (and how to opt-out) here:
#{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}
No analytics have been recorded yet (nor will be during this `brew` run).
EOS
end
# Consider the messages possibly missed if not a TTY.
Utils::Analytics.messages_displayed! if $stdout.tty?
end
def donation_message
return if Settings.read("donationmessage") == "true"
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#donations")}\n\n"
# Consider the message possibly missed if not a TTY.
Settings.write "donationmessage", true if $stdout.tty?
end
def install_from_api_message
return if Settings.read("installfromapimessage") == "true"
no_install_from_api_set = Homebrew::EnvConfig.no_install_from_api? &&
!Homebrew::EnvConfig.automatically_set_no_install_from_api?
return unless no_install_from_api_set
ohai "You have HOMEBREW_NO_INSTALL_FROM_API set"
puts "Homebrew >=4.1.0 is dramatically faster and less error-prone when installing"
puts "from the JSON API. Please consider unsetting HOMEBREW_NO_INSTALL_FROM_API."
puts "This message will only be printed once."
puts "\n\n"
# Consider the message possibly missed if not a TTY.
Settings.write "installfromapimessage", true if $stdout.tty?
end
end
require "extend/os/cmd/update-report"

View File

@ -1,7 +1,7 @@
# typed: true
# typed: strict
# frozen_string_literal: true
require "cli/parser"
require "abstract_command"
require "formula_installer"
require "install"
require "upgrade"
@ -11,268 +11,268 @@ require "cask/macos"
require "api"
module Homebrew
module_function
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`).
sig { returns(CLI::Parser) }
def upgrade_args
Homebrew::CLI::Parser.new 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_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 "-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, "--ignore-pinned", {
description: "Set a successful exit status even if pinned formulae are not upgraded.",
hidden: true,
}],
[: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, "--display-times", {
env: :display_install_times,
description: "Print install times for each package at the end of the run.",
}],
[: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
end
sig { void }
def upgrade
args = upgrade_args.parse
# Deprecated since this is now the default behavior.
odeprecated "`brew upgrade --ignore-pinned`" if args.ignore_pinned?
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?
upgrade_outdated_formulae(formulae, args:) unless only_upgrade_casks
upgrade_outdated_casks(casks, args:) unless only_upgrade_formulae
Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?)
end
sig { params(formulae: T::Array[Formula], args: T.untyped).returns(T::Boolean) }
def upgrade_outdated_formulae(formulae, args:)
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
Install.perform_preinstall_checks
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 `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 "-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, "--ignore-pinned", {
description: "Set a successful exit status even if pinned formulae are not upgraded.",
hidden: true,
}],
[: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, "--display-times", {
env: :display_install_times,
description: "Print install times for each package at the end of the run.",
}],
[: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
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}"
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
# Deprecated since this is now the default behavior.
odeprecated "`brew upgrade --ignore-pinned`" if args.ignore_pinned?
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?
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
Install.perform_preinstall_checks
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"
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
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?,
args:,
)
end
puts formulae_upgrades.join("\n")
end
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], args: T.untyped).returns(T::Boolean) }
def upgrade_outdated_casks(casks, args:)
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?,
args:,
)
end
end

View File

@ -1,166 +1,167 @@
# typed: true
# frozen_string_literal: true
# `brew uses foo bar` returns formulae that use both foo and bar
# If you want the union, run the command twice and concatenate the results.
# The intersection is harder to achieve with shell tools.
require "abstract_command"
require "formula"
require "cli/parser"
require "cask/caskroom"
require "dependencies_helpers"
require "ostruct"
module Homebrew
extend DependenciesHelpers
module Cmd
# `brew uses foo bar` returns formulae that use both foo and bar
# If you want the union, run the command twice and concatenate the results.
# The intersection is harder to achieve with shell tools.
class Uses < AbstractCommand
include DependenciesHelpers
sig { returns(CLI::Parser) }
def self.uses_args
Homebrew::CLI::Parser.new do
description <<~EOS
Show formulae and casks that specify <formula> as a dependency; that is, show dependents
of <formula>. When given multiple formula arguments, show the intersection
of formulae that use <formula>. By default, `uses` shows all formulae and casks that
specify <formula> as a required or recommended dependency for their stable builds.
cmd_args do
description <<~EOS
Show formulae and casks that specify <formula> as a dependency; that is, show dependents
of <formula>. When given multiple formula arguments, show the intersection
of formulae that use <formula>. By default, `uses` shows all formulae and casks that
specify <formula> as a required or recommended dependency for their stable builds.
*Note:* `--missing` and `--skip-recommended` have precedence over `--include-*`.
EOS
switch "--recursive",
description: "Resolve more than one level of dependencies."
switch "--installed",
description: "Only list formulae and casks that are currently installed."
switch "--missing",
description: "Only list formulae and casks that are not currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show " \
"their dependents."
switch "--include-build",
description: "Include formulae that specify <formula> as a `:build` dependency."
switch "--include-test",
description: "Include formulae that specify <formula> as a `:test` dependency."
switch "--include-optional",
description: "Include formulae that specify <formula> as an `:optional` dependency."
switch "--skip-recommended",
description: "Skip all formulae that specify <formula> as a `:recommended` dependency."
switch "--formula", "--formulae",
description: "Include only formulae."
switch "--cask", "--casks",
description: "Include only casks."
*Note:* `--missing` and `--skip-recommended` have precedence over `--include-*`.
EOS
switch "--recursive",
description: "Resolve more than one level of dependencies."
switch "--installed",
description: "Only list formulae and casks that are currently installed."
switch "--missing",
description: "Only list formulae and casks that are not currently installed."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to show " \
"their dependents."
switch "--include-build",
description: "Include formulae that specify <formula> as a `:build` dependency."
switch "--include-test",
description: "Include formulae that specify <formula> as a `:test` dependency."
switch "--include-optional",
description: "Include formulae that specify <formula> as an `:optional` dependency."
switch "--skip-recommended",
description: "Skip all formulae that specify <formula> as a `:recommended` dependency."
switch "--formula", "--formulae",
description: "Include only formulae."
switch "--cask", "--casks",
description: "Include only casks."
conflicts "--formula", "--cask"
conflicts "--installed", "--all"
conflicts "--missing", "--installed"
conflicts "--formula", "--cask"
conflicts "--installed", "--all"
conflicts "--missing", "--installed"
named_args :formula, min: 1
end
end
def self.uses
args = uses_args.parse
Formulary.enable_factory_cache!
used_formulae_missing = false
used_formulae = begin
args.named.to_formulae
rescue FormulaUnavailableError => e
opoo e
used_formulae_missing = true
# If the formula doesn't exist: fake the needed formula object name.
# This is a legacy use of OpenStruct that should be refactored.
# rubocop:disable Style/OpenStructUse
args.named.map { |name| OpenStruct.new name:, full_name: name }
# rubocop:enable Style/OpenStructUse
end
use_runtime_dependents = args.installed? &&
!used_formulae_missing &&
!args.include_build? &&
!args.include_test? &&
!args.include_optional? &&
!args.skip_recommended?
uses = intersection_of_dependents(use_runtime_dependents, used_formulae, args:)
return if uses.empty?
puts Formatter.columns(uses.map(&:full_name).sort)
odie "Missing formulae should not have dependents!" if used_formulae_missing
end
def self.intersection_of_dependents(use_runtime_dependents, used_formulae, args:)
recursive = args.recursive?
show_formulae_and_casks = !args.formula? && !args.cask?
includes, ignores = args_includes_ignores(args)
deps = []
if use_runtime_dependents
if show_formulae_and_casks || args.formula?
deps += used_formulae.map(&:runtime_installed_formula_dependents)
.reduce(&:&)
.select(&:any_version_installed?)
end
if show_formulae_and_casks || args.cask?
deps += select_used_dependents(
dependents(Cask::Caskroom.casks),
used_formulae, recursive, includes, ignores
)
named_args :formula, min: 1
end
deps
else
all = args.eval_all?
sig { override.void }
def run
Formulary.enable_factory_cache!
if !args.installed? && !(all || Homebrew::EnvConfig.eval_all?)
raise UsageError, "`brew uses` needs `--installed` or `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
used_formulae_missing = false
used_formulae = begin
args.named.to_formulae
rescue FormulaUnavailableError => e
opoo e
used_formulae_missing = true
# If the formula doesn't exist: fake the needed formula object name.
# This is a legacy use of OpenStruct that should be refactored.
# rubocop:disable Style/OpenStructUse
args.named.map { |name| OpenStruct.new name:, full_name: name }
# rubocop:enable Style/OpenStructUse
end
use_runtime_dependents = args.installed? &&
!used_formulae_missing &&
!args.include_build? &&
!args.include_test? &&
!args.include_optional? &&
!args.skip_recommended?
uses = intersection_of_dependents(use_runtime_dependents, used_formulae)
return if uses.empty?
puts Formatter.columns(uses.map(&:full_name).sort)
odie "Missing formulae should not have dependents!" if used_formulae_missing
end
if show_formulae_and_casks || args.formula?
deps += args.installed? ? Formula.installed : Formula.all(eval_all: args.eval_all?)
end
if show_formulae_and_casks || args.cask?
deps += args.installed? ? Cask::Caskroom.casks : Cask::Cask.all(eval_all: args.eval_all?)
private
def intersection_of_dependents(use_runtime_dependents, used_formulae)
recursive = args.recursive?
show_formulae_and_casks = !args.formula? && !args.cask?
includes, ignores = args_includes_ignores(args)
deps = []
if use_runtime_dependents
if show_formulae_and_casks || args.formula?
deps += used_formulae.map(&:runtime_installed_formula_dependents)
.reduce(&:&)
.select(&:any_version_installed?)
end
if show_formulae_and_casks || args.cask?
deps += select_used_dependents(
dependents(Cask::Caskroom.casks),
used_formulae, recursive, includes, ignores
)
end
deps
else
all = args.eval_all?
if !args.installed? && !(all || Homebrew::EnvConfig.eval_all?)
raise UsageError, "`brew uses` needs `--installed` or `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
end
if show_formulae_and_casks || args.formula?
deps += args.installed? ? Formula.installed : Formula.all(eval_all: args.eval_all?)
end
if show_formulae_and_casks || args.cask?
deps += args.installed? ? Cask::Caskroom.casks : Cask::Cask.all(eval_all: args.eval_all?)
end
if args.missing?
deps.reject! do |dep|
case dep
when Formula
dep.any_version_installed?
when Cask::Cask
dep.installed?
end
end
ignores.delete(:satisfied?)
end
select_used_dependents(dependents(deps), used_formulae, recursive, includes, ignores)
end
end
if args.missing?
deps.reject! do |dep|
case dep
when Formula
dep.any_version_installed?
when Cask::Cask
dep.installed?
def select_used_dependents(dependents, used_formulae, recursive, includes, ignores)
dependents.select do |d|
deps = if recursive
recursive_includes(Dependency, d, includes, ignores)
else
select_includes(d.deps, ignores, includes)
end
used_formulae.all? do |ff|
deps.any? do |dep|
match = begin
dep.to_formula.full_name == ff.full_name if dep.name.include?("/")
rescue
nil
end
next match unless match.nil?
dep.name == ff.name
end
rescue FormulaUnavailableError
# Silently ignore this case as we don't care about things used in
# taps that aren't currently tapped.
next
end
end
ignores.delete(:satisfied?)
end
select_used_dependents(dependents(deps), used_formulae, recursive, includes, ignores)
end
end
def self.select_used_dependents(dependents, used_formulae, recursive, includes, ignores)
dependents.select do |d|
deps = if recursive
recursive_includes(Dependency, d, includes, ignores)
else
select_includes(d.deps, ignores, includes)
end
used_formulae.all? do |ff|
deps.any? do |dep|
match = begin
dep.to_formula.full_name == ff.full_name if dep.name.include?("/")
rescue
nil
end
next match unless match.nil?
dep.name == ff.name
end
rescue FormulaUnavailableError
# Silently ignore this case as we don't care about things used in
# taps that aren't currently tapped.
next
end
end
end

View File

@ -98,7 +98,7 @@ module Homebrew
end
end
rescue Exception => e # rubocop:disable Lint/RescueException
retry if retry_test?(f, args:)
retry if retry_test?(f)
ofail "#{f.full_name}: failed"
$stderr.puts e, Utils::Backtrace.clean(e)
ensure
@ -109,7 +109,7 @@ module Homebrew
private
def retry_test?(formula, args:)
def retry_test?(formula)
@test_failed ||= Set.new
if args.retry? && @test_failed.add?(formula)
oh1 "Testing #{formula.full_name} (again)"

View File

@ -3,6 +3,8 @@
module Readall
class << self
undef valid_casks?
def valid_casks?(tap, os_name: nil, arch: Hardware::CPU.type)
return true if os_name == :linux

View File

@ -12,13 +12,13 @@ require "cli/parser"
require "cmd/postinstall"
begin
args = Homebrew.postinstall_args.parse
args = Homebrew::Cmd::Postinstall.new.args
error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)
error_pipe.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
trap("INT", old_trap)
formula = args.named.to_resolved_formulae.first
formula = T.must(args.named.to_resolved_formulae.first)
if args.debug?
require "debrew"
formula.extend(Debrew::Formula)

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/--cache"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew --cache" do
RSpec.describe Homebrew::Cmd::Cache do
it_behaves_like "parseable arguments"
it "prints all cache files for a given Formula", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/--caskroom"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew --caskroom" do
RSpec.describe Homebrew::Cmd::Caskroom do
it_behaves_like "parseable arguments"
it "prints Homebrew's Caskroom", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/--cellar"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew --cellar" do
RSpec.describe Homebrew::Cmd::Cellar do
it_behaves_like "parseable arguments"
it "prints Homebrew's Cellar", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/--env"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew --env" do
RSpec.describe Homebrew::Cmd::Env do
it_behaves_like "parseable arguments"
describe "--shell=bash", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/--prefix"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew --prefix" do
RSpec.describe Homebrew::Cmd::Prefix do
it_behaves_like "parseable arguments"
it "prints Homebrew's prefix", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/--repository"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew --repository" do
RSpec.describe Homebrew::Cmd::Repository do
it_behaves_like "parseable arguments"
it "prints Homebrew's repository", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/log"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew log" do
RSpec.describe Homebrew::Cmd::Log do
it_behaves_like "parseable arguments"
it "shows the Git log for a given Formula", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/migrate"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew migrate" do
RSpec.describe Homebrew::Cmd::Migrate do
it_behaves_like "parseable arguments"
it "migrates a renamed Formula", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/missing"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew missing" do
RSpec.describe Homebrew::Cmd::Missing do
it_behaves_like "parseable arguments"
it "prints missing dependencies", :integration_test do

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require "cmd/nodenv-sync"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::NodenvSync do
it_behaves_like "parseable arguments"
end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/options"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew options" do
RSpec.describe Homebrew::Cmd::OptionsCmd do
it_behaves_like "parseable arguments"
it "prints a given Formula's options", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/outdated"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew outdated" do
RSpec.describe Homebrew::Cmd::Outdated do
it_behaves_like "parseable arguments"
it "outputs JSON", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/pin"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew pin" do
RSpec.describe Homebrew::Cmd::Pin do
it_behaves_like "parseable arguments"
it "pins a Formula's version", :integration_test do

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require "cmd/postgresql-upgrade-database"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::PostgresqlUpgradeDatabase do
it_behaves_like "parseable arguments"
end

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
require "cmd/postinstall"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew postinstall" do
RSpec.describe Homebrew::Cmd::Postinstall do
it_behaves_like "parseable arguments"
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require "cmd/pyenv-sync"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::PyenvSync do
it_behaves_like "parseable arguments"
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require "cmd/rbenv-sync"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::RbenvSync do
it_behaves_like "parseable arguments"
end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/readall"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew readall" do
RSpec.describe Homebrew::Cmd::ReadallCmd do
it_behaves_like "parseable arguments"
it "imports all Formulae for a given Tap", :integration_test do

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true
require "extend/ENV"
require "cmd/reinstall"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew reinstall" do
RSpec.describe Homebrew::Cmd::Reinstall do
it_behaves_like "parseable arguments"
it "reinstalls a Formula", :integration_test do

View File

@ -3,7 +3,7 @@
require "cmd/search"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew search" do
RSpec.describe Homebrew::Cmd::SearchCmd do
it_behaves_like "parseable arguments"
it "finds formula in search", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/tap-info"
RSpec.describe "brew tap-info" do
RSpec.describe Homebrew::Cmd::TapInfo do
it_behaves_like "parseable arguments"
it "gets information for a given Tap", :integration_test, :needs_network do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/tap"
RSpec.describe "brew tap" do
RSpec.describe Homebrew::Cmd::TapCmd do
it_behaves_like "parseable arguments"
it "taps a given Tap", :integration_test do

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
require "cmd/uninstall"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew uninstall" do
RSpec.describe Homebrew::Cmd::UninstallCmd do
it_behaves_like "parseable arguments"
it "uninstalls a given Formula", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/unlink"
RSpec.describe "brew unlink" do
RSpec.describe Homebrew::Cmd::UnlinkCmd do
it_behaves_like "parseable arguments"
it "unlinks a Formula", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/unpin"
RSpec.describe "brew unpin" do
RSpec.describe Homebrew::Cmd::Unpin do
it_behaves_like "parseable arguments"
it "unpins a Formula's version", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/untap"
RSpec.describe "brew untap" do
RSpec.describe Homebrew::Cmd::UntapCmd do
it_behaves_like "parseable arguments"
it "untaps a given Tap", :integration_test do

View File

@ -5,7 +5,7 @@ require "formula_versions"
require "yaml"
require "cmd/shared_examples/args_parse"
RSpec.describe "brew update-report" do
RSpec.describe Homebrew::Cmd::UpdateReport do
it_behaves_like "parseable arguments"
describe Reporter do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/upgrade"
RSpec.describe "brew upgrade" do
RSpec.describe Homebrew::Cmd::UpgradeCmd do
it_behaves_like "parseable arguments"
it "upgrades a Formula and cleans up old versions", :integration_test do

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
require "cmd/uses"
require "fileutils"
RSpec.describe "brew uses" do
RSpec.describe Homebrew::Cmd::Uses do
include FileUtils
it_behaves_like "parseable arguments"