Merge pull request #16975 from Homebrew/ported-cmds

Begin porting non-dev commands to use AbstractCommand
This commit is contained in:
Mike McQuaid 2024-03-31 19:28:30 +01:00 committed by GitHub
commit 21dd3c263f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 2086 additions and 2109 deletions

View File

@ -1,12 +1,15 @@
# typed: strong # typed: strong
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser"
module Homebrew module Homebrew
# Subclass this to implement a `brew` command. This is preferred to declaring a named function in the `Homebrew` # Subclass this to implement a `brew` command. This is preferred to declaring a named function in the `Homebrew`
# module, because: # module, because:
# - Each Command lives in an isolated namespace. # - Each Command lives in an isolated namespace.
# - Each Command implements a defined interface. # - Each Command implements a defined interface.
# - `args` is available as an ivar, and thus does not need to be passed as an argument to helper methods. # - `args` is available as an ivar, and thus does not need to be passed as an argument to helper methods.
# - Subclasses no longer need to reference `CLI::Parser` or parse args explicitly.
# #
# To subclass, implement a `run` method and provide a `cmd_args` block to document the command and its allowed args. # To subclass, implement a `run` method and provide a `cmd_args` block to document the command and its allowed args.
# To generate method signatures for command args, run `brew typecheck --update`. # To generate method signatures for command args, run `brew typecheck --update`.

View File

@ -217,7 +217,7 @@ class Build
end end
begin begin
args = Homebrew.install_args.parse args = Homebrew::Cmd::InstallCmd.new.args
Context.current = args.context Context.current = args.context
error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io) error_pipe = UNIXSocket.open(ENV.fetch("HOMEBREW_ERROR_PIPE"), &:recv_io)

View File

@ -1,51 +1,48 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "abstract_command"
module Homebrew module Homebrew
module_function module Cmd
class Analytics < AbstractCommand
cmd_args do
description <<~EOS
Control Homebrew's anonymous aggregate user behaviour analytics.
Read more at <https://docs.brew.sh/Analytics>.
sig { returns(CLI::Parser) } `brew analytics` [`state`]:
def analytics_args Display the current state of Homebrew's analytics.
Homebrew::CLI::Parser.new do
description <<~EOS
Control Homebrew's anonymous aggregate user behaviour analytics.
Read more at <https://docs.brew.sh/Analytics>.
`brew analytics` [`state`]: `brew analytics` (`on`|`off`):
Display the current state of Homebrew's analytics. Turn Homebrew's analytics on or off respectively.
EOS
`brew analytics` (`on`|`off`): named_args %w[state on off regenerate-uuid], max: 1
Turn Homebrew's analytics on or off respectively. end
EOS
sig { override.void }
named_args %w[state on off regenerate-uuid], max: 1 def run
end case args.named.first
end when nil, "state"
if Utils::Analytics.disabled?
sig { void } puts "InfluxDB analytics are disabled."
def analytics else
args = analytics_args.parse puts "InfluxDB analytics are enabled."
end
case args.named.first puts "Google Analytics were destroyed."
when nil, "state" when "on"
if Utils::Analytics.disabled? Utils::Analytics.enable!
puts "InfluxDB analytics are disabled." when "off"
else Utils::Analytics.disable!
puts "InfluxDB analytics are enabled." when "regenerate-uuid"
Utils::Analytics.delete_uuid!
opoo "Homebrew no longer uses an analytics UUID so this has been deleted!"
puts "brew analytics regenerate-uuid is no longer necessary."
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end
end end
puts "Google Analytics were destroyed."
when "on"
Utils::Analytics.enable!
when "off"
Utils::Analytics.disable!
when "regenerate-uuid"
Utils::Analytics.delete_uuid!
opoo "Homebrew no longer uses an analytics UUID so this has been deleted!"
puts "brew analytics regenerate-uuid is no longer necessary."
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end end
end end
end end

View File

@ -1,27 +1,26 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "cleanup" require "cleanup"
require "cli/parser"
module Homebrew module Homebrew
sig { returns(CLI::Parser) } module Cmd
def self.autoremove_args class Autoremove < AbstractCommand
Homebrew::CLI::Parser.new do cmd_args do
description <<~EOS description <<~EOS
Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed. Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed.
EOS EOS
switch "-n", "--dry-run", switch "-n", "--dry-run",
description: "List what would be uninstalled, but do not actually uninstall anything." description: "List what would be uninstalled, but do not actually uninstall anything."
named_args :none named_args :none
end
sig { override.void }
def run
Cleanup.autoremove(dry_run: args.dry_run?)
end
end end
end end
sig { void }
def self.autoremove
args = autoremove_args.parse
Cleanup.autoremove(dry_run: args.dry_run?)
end
end end

View File

@ -1,75 +1,72 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "cleanup" require "cleanup"
require "cli/parser"
module Homebrew module Homebrew
module_function module Cmd
class CleanupCmd < AbstractCommand
cmd_args do
days = Homebrew::EnvConfig::ENVS[:HOMEBREW_CLEANUP_MAX_AGE_DAYS][:default]
description <<~EOS
Remove stale lock files and outdated downloads for all formulae and casks,
and remove old versions of installed formulae. If arguments are specified,
only do this for the given formulae and casks. Removes all downloads more than
#{days} days old. This can be adjusted with `HOMEBREW_CLEANUP_MAX_AGE_DAYS`.
EOS
flag "--prune=",
description: "Remove all cache files older than specified <days>. " \
"If you want to remove everything, use `--prune=all`."
switch "-n", "--dry-run",
description: "Show what would be removed, but do not actually remove anything."
switch "-s",
description: "Scrub the cache, including downloads for even the latest versions. " \
"Note that downloads for any installed formulae or casks will still not be deleted. " \
"If you want to delete those too: `rm -rf \"$(brew --cache)\"`"
switch "--prune-prefix",
description: "Only prune the symlinks and directories from the prefix and remove no other files."
sig { returns(CLI::Parser) } named_args [:formula, :cask]
def cleanup_args end
Homebrew::CLI::Parser.new do
days = Homebrew::EnvConfig::ENVS[:HOMEBREW_CLEANUP_MAX_AGE_DAYS][:default]
description <<~EOS
Remove stale lock files and outdated downloads for all formulae and casks,
and remove old versions of installed formulae. If arguments are specified,
only do this for the given formulae and casks. Removes all downloads more than
#{days} days old. This can be adjusted with `HOMEBREW_CLEANUP_MAX_AGE_DAYS`.
EOS
flag "--prune=",
description: "Remove all cache files older than specified <days>. " \
"If you want to remove everything, use `--prune=all`."
switch "-n", "--dry-run",
description: "Show what would be removed, but do not actually remove anything."
switch "-s",
description: "Scrub the cache, including downloads for even the latest versions. " \
"Note that downloads for any installed formulae or casks will still not be deleted. " \
"If you want to delete those too: `rm -rf \"$(brew --cache)\"`"
switch "--prune-prefix",
description: "Only prune the symlinks and directories from the prefix and remove no other files."
named_args [:formula, :cask] sig { override.void }
end def run
end days = args.prune.presence&.then do |prune|
case prune
when /\A\d+\Z/
prune.to_i
when "all"
0
else
raise UsageError, "`--prune` expects an integer or `all`."
end
end
sig { void } cleanup = Cleanup.new(*args.named, dry_run: args.dry_run?, scrub: args.s?, days:)
def cleanup if args.prune_prefix?
args = cleanup_args.parse cleanup.prune_prefix_symlinks_and_directories
return
end
days = args.prune.presence&.then do |prune| cleanup.clean!(quiet: args.quiet?, periodic: false)
case prune
when /\A\d+\Z/ unless cleanup.disk_cleanup_size.zero?
prune.to_i disk_space = disk_usage_readable(cleanup.disk_cleanup_size)
when "all" if args.dry_run?
0 ohai "This operation would free approximately #{disk_space} of disk space."
else else
raise UsageError, "`--prune` expects an integer or `all`." ohai "This operation has freed approximately #{disk_space} of disk space."
end
end
return if cleanup.unremovable_kegs.empty?
ofail <<~EOS
Could not cleanup old kegs! Fix your permissions on:
#{cleanup.unremovable_kegs.join "\n "}
EOS
end end
end end
cleanup = Cleanup.new(*args.named, dry_run: args.dry_run?, scrub: args.s?, days:)
if args.prune_prefix?
cleanup.prune_prefix_symlinks_and_directories
return
end
cleanup.clean!(quiet: args.quiet?, periodic: false)
unless cleanup.disk_cleanup_size.zero?
disk_space = disk_usage_readable(cleanup.disk_cleanup_size)
if args.dry_run?
ohai "This operation would free approximately #{disk_space} of disk space."
else
ohai "This operation has freed approximately #{disk_space} of disk space."
end
end
return if cleanup.unremovable_kegs.empty?
ofail <<~EOS
Could not cleanup old kegs! Fix your permissions on:
#{cleanup.unremovable_kegs.join "\n "}
EOS
end end
end end

View File

@ -1,49 +1,46 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "abstract_command"
module Homebrew module Homebrew
module_function module Cmd
class CommandsCmd < AbstractCommand
cmd_args do
description <<~EOS
Show lists of built-in and external commands.
EOS
switch "-q", "--quiet",
description: "List only the names of commands without category headers."
switch "--include-aliases",
depends_on: "--quiet",
description: "Include aliases of internal commands."
sig { returns(CLI::Parser) } named_args :none
def commands_args end
Homebrew::CLI::Parser.new do
description <<~EOS
Show lists of built-in and external commands.
EOS
switch "-q", "--quiet",
description: "List only the names of commands without category headers."
switch "--include-aliases",
depends_on: "--quiet",
description: "Include aliases of internal commands."
named_args :none sig { override.void }
end def run
end if args.quiet?
puts Formatter.columns(Commands.commands(aliases: args.include_aliases?))
return
end
sig { void } prepend_separator = T.let(false, T::Boolean)
def commands
args = commands_args.parse
if args.quiet? {
puts Formatter.columns(Commands.commands(aliases: args.include_aliases?)) "Built-in commands" => Commands.internal_commands,
return "Built-in developer commands" => Commands.internal_developer_commands,
end "External commands" => Commands.external_commands,
}.each do |title, commands|
next if commands.blank?
prepend_separator = T.let(false, T::Boolean) puts if prepend_separator
ohai title, Formatter.columns(commands)
{ prepend_separator ||= true
"Built-in commands" => Commands.internal_commands, end
"Built-in developer commands" => Commands.internal_developer_commands, end
"External commands" => Commands.external_commands,
}.each do |title, commands|
next if commands.blank?
puts if prepend_separator
ohai title, Formatter.columns(commands)
prepend_separator ||= true
end end
end end
end end

View File

@ -1,49 +1,46 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "abstract_command"
require "completions" require "completions"
module Homebrew module Homebrew
module_function module Cmd
class CompletionsCmd < AbstractCommand
cmd_args do
description <<~EOS
Control whether Homebrew automatically links external tap shell completion files.
Read more at <https://docs.brew.sh/Shell-Completion>.
sig { returns(CLI::Parser) } `brew completions` [`state`]:
def completions_args Display the current state of Homebrew's completions.
Homebrew::CLI::Parser.new do
description <<~EOS
Control whether Homebrew automatically links external tap shell completion files.
Read more at <https://docs.brew.sh/Shell-Completion>.
`brew completions` [`state`]: `brew completions` (`link`|`unlink`):
Display the current state of Homebrew's completions. Link or unlink Homebrew's completions.
EOS
`brew completions` (`link`|`unlink`): named_args %w[state link unlink], max: 1
Link or unlink Homebrew's completions. end
EOS
sig { override.void }
named_args %w[state link unlink], max: 1 def run
end case args.named.first
end when nil, "state"
if Completions.link_completions?
sig { void } puts "Completions are linked."
def completions else
args = completions_args.parse puts "Completions are not linked."
end
case args.named.first when "link"
when nil, "state" Completions.link!
if Completions.link_completions? puts "Completions are now linked."
puts "Completions are linked." when "unlink"
else Completions.unlink!
puts "Completions are not linked." puts "Completions are no longer linked."
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end
end end
when "link"
Completions.link!
puts "Completions are now linked."
when "unlink"
Completions.unlink!
puts "Completions are no longer linked."
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end end
end end
end end

View File

@ -1,28 +1,25 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "system_config" require "system_config"
require "cli/parser"
module Homebrew module Homebrew
module_function module Cmd
class Config < AbstractCommand
cmd_args do
description <<~EOS
Show Homebrew and system configuration info useful for debugging. If you file
a bug report, you will be required to provide this information.
EOS
sig { returns(CLI::Parser) } named_args :none
def config_args end
Homebrew::CLI::Parser.new do
description <<~EOS
Show Homebrew and system configuration info useful for debugging. If you file
a bug report, you will be required to provide this information.
EOS
named_args :none sig { override.void }
def run
SystemConfig.dump_verbose_config
end
end end
end end
sig { void }
def config
config_args.parse
SystemConfig.dump_verbose_config
end
end end

View File

@ -1,340 +1,339 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "cli/parser"
require "cask/caskroom" require "cask/caskroom"
require "dependencies_helpers" require "dependencies_helpers"
module Homebrew module Homebrew
extend DependenciesHelpers module Cmd
class Deps < AbstractCommand
include DependenciesHelpers
cmd_args do
description <<~EOS
Show dependencies for <formula>. When given multiple formula arguments,
show the intersection of dependencies for each formula. By default, `deps`
shows all required and recommended dependencies.
sig { returns(CLI::Parser) } If any version of each formula argument is installed and no other options
def self.deps_args are passed, this command displays their actual runtime dependencies (similar
Homebrew::CLI::Parser.new do to `brew linkage`), which may differ from the current versions' stated
description <<~EOS dependencies if the installed versions are outdated.
Show dependencies for <formula>. When given multiple formula arguments,
show the intersection of dependencies for each formula. By default, `deps`
shows all required and recommended dependencies.
If any version of each formula argument is installed and no other options *Note:* `--missing` and `--skip-recommended` have precedence over `--include-*`.
are passed, this command displays their actual runtime dependencies (similar EOS
to `brew linkage`), which may differ from the current versions' stated switch "-n", "--topological",
dependencies if the installed versions are outdated. description: "Sort dependencies in topological order."
switch "-1", "--direct", "--declared", "--1",
description: "Show only the direct dependencies declared in the formula."
switch "--union",
description: "Show the union of dependencies for multiple <formula>, instead of the intersection."
switch "--full-name",
description: "List dependencies by their full name."
switch "--include-build",
description: "Include `:build` dependencies for <formula>."
switch "--include-optional",
description: "Include `:optional` dependencies for <formula>."
switch "--include-test",
description: "Include `:test` dependencies for <formula> (non-recursive)."
switch "--skip-recommended",
description: "Skip `:recommended` dependencies for <formula>."
switch "--include-requirements",
description: "Include requirements in addition to dependencies for <formula>."
switch "--tree",
description: "Show dependencies as a tree. When given multiple formula arguments, " \
"show individual trees for each formula."
switch "--graph",
description: "Show dependencies as a directed graph."
switch "--dot",
depends_on: "--graph",
description: "Show text-based graph description in DOT format."
switch "--annotate",
description: "Mark any build, test, implicit, optional, or recommended dependencies as " \
"such in the output."
switch "--installed",
description: "List dependencies for formulae that are currently installed. If <formula> is " \
"specified, list only its dependencies that are currently installed."
switch "--missing",
description: "Show only missing dependencies."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to list " \
"their dependencies."
switch "--for-each",
description: "Switch into the mode used by the `--eval-all` option, but only list dependencies " \
"for each provided <formula>, one formula per line. This is used for " \
"debugging the `--installed`/`--eval-all` display mode."
switch "--HEAD",
description: "Show dependencies for HEAD version instead of stable version."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
*Note:* `--missing` and `--skip-recommended` have precedence over `--include-*`. conflicts "--tree", "--graph"
EOS conflicts "--installed", "--missing"
switch "-n", "--topological", conflicts "--installed", "--eval-all"
description: "Sort dependencies in topological order." conflicts "--formula", "--cask"
switch "-1", "--direct", "--declared", "--1", formula_options
description: "Show only the direct dependencies declared in the formula."
switch "--union",
description: "Show the union of dependencies for multiple <formula>, instead of the intersection."
switch "--full-name",
description: "List dependencies by their full name."
switch "--include-build",
description: "Include `:build` dependencies for <formula>."
switch "--include-optional",
description: "Include `:optional` dependencies for <formula>."
switch "--include-test",
description: "Include `:test` dependencies for <formula> (non-recursive)."
switch "--skip-recommended",
description: "Skip `:recommended` dependencies for <formula>."
switch "--include-requirements",
description: "Include requirements in addition to dependencies for <formula>."
switch "--tree",
description: "Show dependencies as a tree. When given multiple formula arguments, " \
"show individual trees for each formula."
switch "--graph",
description: "Show dependencies as a directed graph."
switch "--dot",
depends_on: "--graph",
description: "Show text-based graph description in DOT format."
switch "--annotate",
description: "Mark any build, test, implicit, optional, or recommended dependencies as " \
"such in the output."
switch "--installed",
description: "List dependencies for formulae that are currently installed. If <formula> is " \
"specified, list only its dependencies that are currently installed."
switch "--missing",
description: "Show only missing dependencies."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to list " \
"their dependencies."
switch "--for-each",
description: "Switch into the mode used by the `--eval-all` option, but only list dependencies " \
"for each provided <formula>, one formula per line. This is used for " \
"debugging the `--installed`/`--eval-all` display mode."
switch "--HEAD",
description: "Show dependencies for HEAD version instead of stable version."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--tree", "--graph" named_args [:formula, :cask]
conflicts "--installed", "--missing" end
conflicts "--installed", "--eval-all"
conflicts "--formula", "--cask"
formula_options
named_args [:formula, :cask] sig { override.void }
end def run
end all = args.eval_all?
def self.deps Formulary.enable_factory_cache!
args = deps_args.parse
all = args.eval_all? recursive = !args.direct?
installed = args.installed? || dependents(args.named.to_formulae_and_casks).all?(&:any_version_installed?)
Formulary.enable_factory_cache! @use_runtime_dependencies = installed && recursive &&
!args.tree? &&
!args.graph? &&
!args.HEAD? &&
!args.include_build? &&
!args.include_test? &&
!args.include_optional? &&
!args.skip_recommended? &&
!args.missing?
recursive = !args.direct? if args.tree? || args.graph?
installed = args.installed? || dependents(args.named.to_formulae_and_casks).all?(&:any_version_installed?) dependents = if args.named.present?
sorted_dependents(args.named.to_formulae_and_casks)
elsif args.installed?
case args.only_formula_or_cask
when :formula
sorted_dependents(Formula.installed)
when :cask
sorted_dependents(Cask::Caskroom.casks)
else
sorted_dependents(Formula.installed + Cask::Caskroom.casks)
end
else
raise FormulaUnspecifiedError
end
@use_runtime_dependencies = installed && recursive && if args.graph?
!args.tree? && dot_code = dot_code(dependents, recursive:)
!args.graph? && if args.dot?
!args.HEAD? && puts dot_code
!args.include_build? && else
!args.include_test? && exec_browser "https://dreampuf.github.io/GraphvizOnline/##{ERB::Util.url_encode(dot_code)}"
!args.include_optional? && end
!args.skip_recommended? && return
!args.missing? end
if args.tree? || args.graph? puts_deps_tree(dependents, recursive:)
dependents = if args.named.present? return
sorted_dependents(args.named.to_formulae_and_casks) elsif all
elsif args.installed? puts_deps(sorted_dependents(
case args.only_formula_or_cask Formula.all(eval_all: args.eval_all?) + Cask::Cask.all(eval_all: args.eval_all?),
when :formula ), recursive:)
sorted_dependents(Formula.installed) return
when :cask elsif !args.no_named? && args.for_each?
sorted_dependents(Cask::Caskroom.casks) puts_deps(sorted_dependents(args.named.to_formulae_and_casks), recursive:)
return
end
if args.no_named?
raise FormulaUnspecifiedError unless args.installed?
sorted_dependents_formulae_and_casks = case args.only_formula_or_cask
when :formula
sorted_dependents(Formula.installed)
when :cask
sorted_dependents(Cask::Caskroom.casks)
else
sorted_dependents(Formula.installed + Cask::Caskroom.casks)
end
puts_deps(sorted_dependents_formulae_and_casks, recursive:)
return
end
dependents = dependents(args.named.to_formulae_and_casks)
check_head_spec(dependents) if args.HEAD?
all_deps = deps_for_dependents(dependents, recursive:, &(args.union? ? :| : :&))
condense_requirements(all_deps)
all_deps.map! { |d| dep_display_name(d) }
all_deps.uniq!
all_deps.sort! unless args.topological?
puts all_deps
end
private
def sorted_dependents(formulae_or_casks)
dependents(formulae_or_casks).sort_by(&:name)
end
def condense_requirements(deps)
deps.select! { |dep| dep.is_a?(Dependency) } unless args.include_requirements?
deps.select! { |dep| dep.is_a?(Requirement) || dep.installed? } if args.installed?
end
def dep_display_name(dep)
str = if dep.is_a? Requirement
if args.include_requirements?
":#{dep.display_s}"
else
# This shouldn't happen, but we'll put something here to help debugging
"::#{dep.name}"
end
elsif args.full_name?
dep.to_formula.full_name
else else
sorted_dependents(Formula.installed + Cask::Caskroom.casks) dep.name
end end
else
raise FormulaUnspecifiedError if args.annotate?
str = "#{str} " if args.tree?
str = "#{str} [build]" if dep.build?
str = "#{str} [test]" if dep.test?
str = "#{str} [optional]" if dep.optional?
str = "#{str} [recommended]" if dep.recommended?
str = "#{str} [implicit]" if dep.implicit?
end
str
end end
if args.graph? def deps_for_dependent(dependency, recursive: false)
dot_code = dot_code(dependents, recursive:, args:) includes, ignores = args_includes_ignores(args)
if args.dot?
puts dot_code deps = dependency.runtime_dependencies if @use_runtime_dependencies
if recursive
deps ||= recursive_includes(Dependency, dependency, includes, ignores)
reqs = recursive_includes(Requirement, dependency, includes, ignores)
else else
exec_browser "https://dreampuf.github.io/GraphvizOnline/##{ERB::Util.url_encode(dot_code)}" deps ||= select_includes(dependency.deps, ignores, includes)
reqs = select_includes(dependency.requirements, ignores, includes)
end end
return
deps + reqs.to_a
end end
puts_deps_tree(dependents, recursive:, args:) def deps_for_dependents(dependents, recursive: false, &block)
return dependents.map { |d| deps_for_dependent(d, recursive:) }.reduce(&block)
elsif all
puts_deps(sorted_dependents(
Formula.all(eval_all: args.eval_all?) + Cask::Cask.all(eval_all: args.eval_all?),
), recursive:, args:)
return
elsif !args.no_named? && args.for_each?
puts_deps(sorted_dependents(args.named.to_formulae_and_casks), recursive:, args:)
return
end
if args.no_named?
raise FormulaUnspecifiedError unless args.installed?
sorted_dependents_formulae_and_casks = case args.only_formula_or_cask
when :formula
sorted_dependents(Formula.installed)
when :cask
sorted_dependents(Cask::Caskroom.casks)
else
sorted_dependents(Formula.installed + Cask::Caskroom.casks)
end end
puts_deps(sorted_dependents_formulae_and_casks, recursive:, args:)
return
end
dependents = dependents(args.named.to_formulae_and_casks) def check_head_spec(dependents)
check_head_spec(dependents) if args.HEAD? headless = dependents.select { |d| d.is_a?(Formula) && d.active_spec_sym != :head }
.to_sentence two_words_connector: " or ", last_word_connector: " or "
all_deps = deps_for_dependents(dependents, recursive:, args:, &(args.union? ? :| : :&)) opoo "No head spec for #{headless}, using stable spec instead" unless headless.empty?
condense_requirements(all_deps, args:)
all_deps.map! { |d| dep_display_name(d, args:) }
all_deps.uniq!
all_deps.sort! unless args.topological?
puts all_deps
end
def self.sorted_dependents(formulae_or_casks)
dependents(formulae_or_casks).sort_by(&:name)
end
def self.condense_requirements(deps, args:)
deps.select! { |dep| dep.is_a?(Dependency) } unless args.include_requirements?
deps.select! { |dep| dep.is_a?(Requirement) || dep.installed? } if args.installed?
end
def self.dep_display_name(dep, args:)
str = if dep.is_a? Requirement
if args.include_requirements?
":#{dep.display_s}"
else
# This shouldn't happen, but we'll put something here to help debugging
"::#{dep.name}"
end end
elsif args.full_name?
dep.to_formula.full_name
else
dep.name
end
if args.annotate? def puts_deps(dependents, recursive: false)
str = "#{str} " if args.tree? check_head_spec(dependents) if args.HEAD?
str = "#{str} [build]" if dep.build? dependents.each do |dependent|
str = "#{str} [test]" if dep.test? deps = deps_for_dependent(dependent, recursive:)
str = "#{str} [optional]" if dep.optional? condense_requirements(deps)
str = "#{str} [recommended]" if dep.recommended? deps.sort_by!(&:name)
str = "#{str} [implicit]" if dep.implicit? deps.map! { |d| dep_display_name(d) }
end puts "#{dependent.full_name}: #{deps.join(" ")}"
str
end
def self.deps_for_dependent(dependency, args:, recursive: false)
includes, ignores = args_includes_ignores(args)
deps = dependency.runtime_dependencies if @use_runtime_dependencies
if recursive
deps ||= recursive_includes(Dependency, dependency, includes, ignores)
reqs = recursive_includes(Requirement, dependency, includes, ignores)
else
deps ||= select_includes(dependency.deps, ignores, includes)
reqs = select_includes(dependency.requirements, ignores, includes)
end
deps + reqs.to_a
end
def self.deps_for_dependents(dependents, args:, recursive: false, &block)
dependents.map { |d| deps_for_dependent(d, recursive:, args:) }.reduce(&block)
end
def self.check_head_spec(dependents)
headless = dependents.select { |d| d.is_a?(Formula) && d.active_spec_sym != :head }
.to_sentence two_words_connector: " or ", last_word_connector: " or "
opoo "No head spec for #{headless}, using stable spec instead" unless headless.empty?
end
def self.puts_deps(dependents, args:, recursive: false)
check_head_spec(dependents) if args.HEAD?
dependents.each do |dependent|
deps = deps_for_dependent(dependent, recursive:, args:)
condense_requirements(deps, args:)
deps.sort_by!(&:name)
deps.map! { |d| dep_display_name(d, args:) }
puts "#{dependent.full_name}: #{deps.join(" ")}"
end
end
def self.dot_code(dependents, recursive:, args:)
dep_graph = {}
dependents.each do |d|
graph_deps(d, dep_graph:, recursive:, args:)
end
dot_code = dep_graph.map do |d, deps|
deps.map do |dep|
attributes = []
attributes << "style = dotted" if dep.build?
attributes << "arrowhead = empty" if dep.test?
if dep.optional?
attributes << "color = red"
elsif dep.recommended?
attributes << "color = green"
end end
comment = " # #{dep.tags.map(&:inspect).join(", ")}" if dep.tags.any?
" \"#{d.name}\" -> \"#{dep}\"#{" [#{attributes.join(", ")}]" if attributes.any?}#{comment}"
end end
end.flatten.join("\n")
"digraph {\n#{dot_code}\n}"
end
def self.graph_deps(formula, dep_graph:, recursive:, args:) def dot_code(dependents, recursive:)
return if dep_graph.key?(formula) dep_graph = {}
dependents.each do |d|
graph_deps(d, dep_graph:, recursive:)
end
dependables = dependables(formula, args:) dot_code = dep_graph.map do |d, deps|
dep_graph[formula] = dependables deps.map do |dep|
return unless recursive attributes = []
attributes << "style = dotted" if dep.build?
attributes << "arrowhead = empty" if dep.test?
if dep.optional?
attributes << "color = red"
elsif dep.recommended?
attributes << "color = green"
end
comment = " # #{dep.tags.map(&:inspect).join(", ")}" if dep.tags.any?
" \"#{d.name}\" -> \"#{dep}\"#{" [#{attributes.join(", ")}]" if attributes.any?}#{comment}"
end
end.flatten.join("\n")
"digraph {\n#{dot_code}\n}"
end
dependables.each do |dep| def graph_deps(formula, dep_graph:, recursive:)
next unless dep.is_a? Dependency return if dep_graph.key?(formula)
graph_deps(Formulary.factory(dep.name), dependables = dependables(formula)
dep_graph:, dep_graph[formula] = dependables
recursive: true, return unless recursive
args:)
dependables.each do |dep|
next unless dep.is_a? Dependency
graph_deps(Formulary.factory(dep.name),
dep_graph:,
recursive: true)
end
end
def puts_deps_tree(dependents, recursive: false)
check_head_spec(dependents) if args.HEAD?
dependents.each do |d|
puts d.full_name
recursive_deps_tree(d, dep_stack: [], prefix: "", recursive:)
puts
end
end
def dependables(formula)
includes, ignores = args_includes_ignores(args)
deps = @use_runtime_dependencies ? formula.runtime_dependencies : formula.deps
deps = select_includes(deps, ignores, includes)
reqs = select_includes(formula.requirements, ignores, includes) if args.include_requirements?
reqs ||= []
reqs + deps
end
def recursive_deps_tree(formula, dep_stack:, prefix:, recursive:)
dependables = dependables(formula)
max = dependables.length - 1
dep_stack.push formula.name
dependables.each_with_index do |dep, i|
tree_lines = if i == max
"└──"
else
"├──"
end
display_s = "#{tree_lines} #{dep_display_name(dep)}"
# Detect circular dependencies and consider them a failure if present.
is_circular = dep_stack.include?(dep.name)
if is_circular
display_s = "#{display_s} (CIRCULAR DEPENDENCY)"
Homebrew.failed = true
end
puts "#{prefix}#{display_s}"
next if !recursive || is_circular
prefix_addition = if i == max
" "
else
""
end
next unless dep.is_a? Dependency
recursive_deps_tree(Formulary.factory(dep.name),
dep_stack:,
prefix: prefix + prefix_addition,
recursive: true)
end
dep_stack.pop
end
end end
end end
def self.puts_deps_tree(dependents, args:, recursive: false)
check_head_spec(dependents) if args.HEAD?
dependents.each do |d|
puts d.full_name
recursive_deps_tree(d, dep_stack: [], prefix: "", recursive:, args:)
puts
end
end
def self.dependables(formula, args:)
includes, ignores = args_includes_ignores(args)
deps = @use_runtime_dependencies ? formula.runtime_dependencies : formula.deps
deps = select_includes(deps, ignores, includes)
reqs = select_includes(formula.requirements, ignores, includes) if args.include_requirements?
reqs ||= []
reqs + deps
end
def self.recursive_deps_tree(formula, dep_stack:, prefix:, recursive:, args:)
dependables = dependables(formula, args:)
max = dependables.length - 1
dep_stack.push formula.name
dependables.each_with_index do |dep, i|
tree_lines = if i == max
"└──"
else
"├──"
end
display_s = "#{tree_lines} #{dep_display_name(dep, args:)}"
# Detect circular dependencies and consider them a failure if present.
is_circular = dep_stack.include?(dep.name)
if is_circular
display_s = "#{display_s} (CIRCULAR DEPENDENCY)"
Homebrew.failed = true
end
puts "#{prefix}#{display_s}"
next if !recursive || is_circular
prefix_addition = if i == max
" "
else
""
end
next unless dep.is_a? Dependency
recursive_deps_tree(Formulary.factory(dep.name),
dep_stack:,
prefix: prefix + prefix_addition,
recursive: true,
args:)
end
dep_stack.pop
end
end end

View File

@ -1,74 +1,75 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "descriptions" require "descriptions"
require "search" require "search"
require "description_cache_store" require "description_cache_store"
require "cli/parser"
module Homebrew module Homebrew
module_function module Cmd
class Desc < AbstractCommand
cmd_args do
description <<~EOS
Display <formula>'s name and one-line description.
The cache is created on the first search, making that search slower than subsequent ones.
EOS
switch "-s", "--search",
description: "Search both names and descriptions for <text>. If <text> is flanked by " \
"slashes, it is interpreted as a regular expression."
switch "-n", "--name",
description: "Search just names for <text>. If <text> is flanked by slashes, it is " \
"interpreted as a regular expression."
switch "-d", "--description",
description: "Search just descriptions for <text>. If <text> is flanked by slashes, " \
"it is interpreted as a regular expression."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
sig { returns(CLI::Parser) } conflicts "--search", "--name", "--description"
def desc_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display <formula>'s name and one-line description.
The cache is created on the first search, making that search slower than subsequent ones.
EOS
switch "-s", "--search",
description: "Search both names and descriptions for <text>. If <text> is flanked by " \
"slashes, it is interpreted as a regular expression."
switch "-n", "--name",
description: "Search just names for <text>. If <text> is flanked by slashes, it is " \
"interpreted as a regular expression."
switch "-d", "--description",
description: "Search just descriptions for <text>. If <text> is flanked by slashes, " \
"it is interpreted as a regular expression."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to search their " \
"descriptions. Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--search", "--name", "--description" named_args [:formula, :cask, :text_or_regex], min: 1
end
named_args [:formula, :cask, :text_or_regex], min: 1 sig { override.void }
end def run
end if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
raise UsageError, "`brew desc` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
end
def desc search_type = if args.search?
args = desc_args.parse :either
elsif args.name?
:name
elsif args.description?
:desc
end
if !args.eval_all? && !Homebrew::EnvConfig.eval_all? if search_type.blank?
raise UsageError, "`brew desc` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!" desc = {}
end args.named.to_formulae_and_casks.each do |formula_or_cask|
case formula_or_cask
search_type = if args.search? when Formula
:either desc[formula_or_cask.full_name] = formula_or_cask.desc
elsif args.name? when Cask::Cask
:name description = formula_or_cask.desc.presence || Formatter.warning("[no description]")
elsif args.description? desc[formula_or_cask.full_name] = "(#{formula_or_cask.name.join(", ")}) #{description}"
:desc else
end raise TypeError, "Unsupported formula_or_cask type: #{formula_or_cask.class}"
end
if search_type.blank? end
desc = {} Descriptions.new(desc).print
args.named.to_formulae_and_casks.each do |formula_or_cask|
if formula_or_cask.is_a? Formula
desc[formula_or_cask.full_name] = formula_or_cask.desc
else else
description = formula_or_cask.desc.presence || Formatter.warning("[no description]") query = args.named.join(" ")
desc[formula_or_cask.full_name] = "(#{formula_or_cask.name.join(", ")}) #{description}" string_or_regex = Search.query_regexp(query)
Search.search_descriptions(string_or_regex, args, search_type:)
end end
end end
Descriptions.new(desc).print
else
query = args.named.join(" ")
string_or_regex = Search.query_regexp(query)
Search.search_descriptions(string_or_regex, args, search_type:)
end end
end end
end end

View File

@ -1,56 +1,55 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "abstract_command"
module Homebrew module Homebrew
module_function module Cmd
class Developer < AbstractCommand
cmd_args do
description <<~EOS
Control Homebrew's developer mode. When developer mode is enabled,
`brew update` will update Homebrew to the latest commit on the `master`
branch instead of the latest stable version along with some other behaviour changes.
sig { returns(CLI::Parser) } `brew developer` [`state`]:
def developer_args Display the current state of Homebrew's developer mode.
Homebrew::CLI::Parser.new do
description <<~EOS
Control Homebrew's developer mode. When developer mode is enabled,
`brew update` will update Homebrew to the latest commit on the `master`
branch instead of the latest stable version along with some other behaviour changes.
`brew developer` [`state`]: `brew developer` (`on`|`off`):
Display the current state of Homebrew's developer mode. Turn Homebrew's developer mode on or off respectively.
EOS
`brew developer` (`on`|`off`): named_args %w[state on off], max: 1
Turn Homebrew's developer mode on or off respectively. end
EOS
sig { override.void }
named_args %w[state on off], max: 1 def run
end env_vars = []
end env_vars << "HOMEBREW_DEVELOPER" if Homebrew::EnvConfig.developer?
env_vars << "HOMEBREW_UPDATE_TO_TAG" if Homebrew::EnvConfig.update_to_tag?
def developer env_vars.map! do |var|
args = developer_args.parse "#{Tty.bold}#{var}#{Tty.reset}"
end
env_vars = []
env_vars << "HOMEBREW_DEVELOPER" if Homebrew::EnvConfig.developer? case args.named.first
env_vars << "HOMEBREW_UPDATE_TO_TAG" if Homebrew::EnvConfig.update_to_tag? when nil, "state"
env_vars.map! do |var| if env_vars.any?
"#{Tty.bold}#{var}#{Tty.reset}" verb = (env_vars.count == 1) ? "is" : "are"
end puts "Developer mode is enabled because #{env_vars.to_sentence} #{verb} set."
elsif Homebrew::Settings.read("devcmdrun") == "true"
case args.named.first puts "Developer mode is enabled."
when nil, "state" else
if env_vars.any? puts "Developer mode is disabled."
puts "Developer mode is enabled because #{env_vars.to_sentence} #{(env_vars.count == 1) ? "is" : "are"} set." end
elsif Homebrew::Settings.read("devcmdrun") == "true" when "on"
puts "Developer mode is enabled." Homebrew::Settings.write "devcmdrun", true
else when "off"
puts "Developer mode is disabled." Homebrew::Settings.delete "devcmdrun"
puts "To fully disable developer mode, you must unset #{env_vars.to_sentence}." if env_vars.any?
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end
end end
when "on"
Homebrew::Settings.write "devcmdrun", true
when "off"
Homebrew::Settings.delete "devcmdrun"
puts "To fully disable developer mode, you must unset #{env_vars.to_sentence}." if env_vars.any?
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end end
end end
end end

View File

@ -1,22 +1,21 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "abstract_command"
module Homebrew module Homebrew
module_function module Cmd
class Docs < AbstractCommand
cmd_args do
description <<~EOS
Open Homebrew's online documentation at <#{HOMEBREW_DOCS_WWW}> in a browser.
EOS
end
sig { returns(CLI::Parser) } sig { override.void }
def docs_args def run
Homebrew::CLI::Parser.new do exec_browser HOMEBREW_DOCS_WWW
description <<~EOS end
Open Homebrew's online documentation at <#{HOMEBREW_DOCS_WWW}> in a browser.
EOS
end end
end end
sig { void }
def docs
exec_browser HOMEBREW_DOCS_WWW
end
end end

View File

@ -1,80 +1,80 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "diagnostic" require "diagnostic"
require "cli/parser"
require "cask/caskroom" require "cask/caskroom"
module Homebrew module Homebrew
sig { returns(CLI::Parser) } module Cmd
def self.doctor_args class Doctor < AbstractCommand
Homebrew::CLI::Parser.new do cmd_args do
description <<~EOS description <<~EOS
Check your system for potential problems. Will exit with a non-zero status Check your system for potential problems. Will exit with a non-zero status
if any potential problems are found. if any potential problems are found.
Please note that these warnings are just used to help the Homebrew maintainers Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for with debugging if you file an issue. If everything you use Homebrew for
is working fine: please don't worry or file an issue; just ignore this. is working fine: please don't worry or file an issue; just ignore this.
EOS
switch "--list-checks",
description: "List all audit methods, which can be run individually " \
"if provided as arguments."
switch "-D", "--audit-debug",
description: "Enable debugging and profiling of audit methods."
named_args :diagnostic_check
end
end
def self.doctor
args = doctor_args.parse
inject_dump_stats!(Diagnostic::Checks, /^check_*/) if args.audit_debug?
checks = Diagnostic::Checks.new(verbose: args.verbose?)
if args.list_checks?
puts checks.all
return
end
if args.no_named?
slow_checks = %w[
check_for_broken_symlinks
check_missing_deps
]
methods = (checks.all - slow_checks) + slow_checks
methods -= checks.cask_checks unless Cask::Caskroom.any_casks_installed?
else
methods = args.named
end
first_warning = T.let(true, T::Boolean)
methods.each do |method|
$stderr.puts Formatter.headline("Checking #{method}", color: :magenta) if args.debug?
unless checks.respond_to?(method)
ofail "No check available by the name: #{method}"
next
end
out = checks.send(method)
next if out.blank?
if first_warning
$stderr.puts <<~EOS
#{Tty.bold}Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!#{Tty.reset}
EOS EOS
switch "--list-checks",
description: "List all audit methods, which can be run individually " \
"if provided as arguments."
switch "-D", "--audit-debug",
description: "Enable debugging and profiling of audit methods."
named_args :diagnostic_check
end end
$stderr.puts sig { override.void }
opoo out def run
Homebrew.failed = true Homebrew.inject_dump_stats!(Diagnostic::Checks, /^check_*/) if args.audit_debug?
first_warning = false
end
puts "Your system is ready to brew." if !Homebrew.failed? && !args.quiet? checks = Diagnostic::Checks.new(verbose: args.verbose?)
if args.list_checks?
puts checks.all
return
end
if args.no_named?
slow_checks = %w[
check_for_broken_symlinks
check_missing_deps
]
methods = (checks.all - slow_checks) + slow_checks
methods -= checks.cask_checks unless Cask::Caskroom.any_casks_installed?
else
methods = args.named
end
first_warning = T.let(true, T::Boolean)
methods.each do |method|
$stderr.puts Formatter.headline("Checking #{method}", color: :magenta) if args.debug?
unless checks.respond_to?(method)
ofail "No check available by the name: #{method}"
next
end
out = checks.send(method)
next if out.blank?
if first_warning
$stderr.puts <<~EOS
#{Tty.bold}Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!#{Tty.reset}
EOS
end
$stderr.puts
opoo out
Homebrew.failed = true
first_warning = false
end
puts "Your system is ready to brew." if !Homebrew.failed? && !args.quiet?
end
end
end end
end end

View File

@ -1,256 +1,257 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "fetch" require "fetch"
require "cli/parser"
require "cask/download" require "cask/download"
module Homebrew module Homebrew
extend Fetch module Cmd
class FetchCmd < AbstractCommand
include Fetch
FETCH_MAX_TRIES = 5
FETCH_MAX_TRIES = 5 cmd_args do
description <<~EOS
Download a bottle (if available) or source packages for <formula>e
and binaries for <cask>s. For files, also print SHA-256 checksums.
EOS
flag "--os=",
description: "Download for the given operating system. " \
"(Pass `all` to download for all operating systems.)"
flag "--arch=",
description: "Download for the given CPU architecture. " \
"(Pass `all` to download for all architectures.)"
flag "--bottle-tag=",
description: "Download a bottle for given tag."
switch "--HEAD",
description: "Fetch HEAD version instead of stable version."
switch "-f", "--force",
description: "Remove a previously cached version and re-fetch."
switch "-v", "--verbose",
description: "Do a verbose VCS checkout, if the URL represents a VCS. This is useful for " \
"seeing if an existing VCS cache has been updated."
switch "--retry",
description: "Retry if downloading fails or re-download if the checksum of a previously cached " \
"version no longer matches. Tries at most #{FETCH_MAX_TRIES} times with " \
"exponential backoff."
switch "--deps",
description: "Also download dependencies for any listed <formula>."
switch "-s", "--build-from-source",
description: "Download source packages rather than a bottle."
switch "--build-bottle",
description: "Download source packages (for eventual bottling) rather than a bottle."
switch "--force-bottle",
description: "Download a bottle if it exists for the current or newest version of macOS, " \
"even if it would not be used during installation."
switch "--[no-]quarantine",
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
sig { returns(CLI::Parser) } conflicts "--build-from-source", "--build-bottle", "--force-bottle", "--bottle-tag"
def self.fetch_args conflicts "--cask", "--HEAD"
Homebrew::CLI::Parser.new do conflicts "--cask", "--deps"
description <<~EOS conflicts "--cask", "-s"
Download a bottle (if available) or source packages for <formula>e conflicts "--cask", "--build-bottle"
and binaries for <cask>s. For files, also print SHA-256 checksums. conflicts "--cask", "--force-bottle"
EOS conflicts "--cask", "--bottle-tag"
flag "--os=", conflicts "--formula", "--cask"
description: "Download for the given operating system. " \ conflicts "--os", "--bottle-tag"
"(Pass `all` to download for all operating systems.)" conflicts "--arch", "--bottle-tag"
flag "--arch=",
description: "Download for the given CPU architecture. " \
"(Pass `all` to download for all architectures.)"
flag "--bottle-tag=",
description: "Download a bottle for given tag."
switch "--HEAD",
description: "Fetch HEAD version instead of stable version."
switch "-f", "--force",
description: "Remove a previously cached version and re-fetch."
switch "-v", "--verbose",
description: "Do a verbose VCS checkout, if the URL represents a VCS. This is useful for " \
"seeing if an existing VCS cache has been updated."
switch "--retry",
description: "Retry if downloading fails or re-download if the checksum of a previously cached " \
"version no longer matches. Tries at most #{FETCH_MAX_TRIES} times with " \
"exponential backoff."
switch "--deps",
description: "Also download dependencies for any listed <formula>."
switch "-s", "--build-from-source",
description: "Download source packages rather than a bottle."
switch "--build-bottle",
description: "Download source packages (for eventual bottling) rather than a bottle."
switch "--force-bottle",
description: "Download a bottle if it exists for the current or newest version of macOS, " \
"even if it would not be used during installation."
switch "--[no-]quarantine",
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--build-from-source", "--build-bottle", "--force-bottle", "--bottle-tag" named_args [:formula, :cask], min: 1
conflicts "--cask", "--HEAD"
conflicts "--cask", "--deps"
conflicts "--cask", "-s"
conflicts "--cask", "--build-bottle"
conflicts "--cask", "--force-bottle"
conflicts "--cask", "--bottle-tag"
conflicts "--formula", "--cask"
conflicts "--os", "--bottle-tag"
conflicts "--arch", "--bottle-tag"
named_args [:formula, :cask], min: 1
end
end
def self.fetch
args = fetch_args.parse
Formulary.enable_factory_cache!
bucket = if args.deps?
args.named.to_formulae_and_casks.flat_map do |formula_or_cask|
case formula_or_cask
when Formula
formula = formula_or_cask
[formula, *formula.recursive_dependencies.map(&:to_formula)]
else
formula_or_cask
end
end end
else
args.named.to_formulae_and_casks
end.uniq
os_arch_combinations = args.os_arch_combinations sig { override.void }
def run
Formulary.enable_factory_cache!
puts "Fetching: #{bucket * ", "}" if bucket.size > 1 bucket = if args.deps?
bucket.each do |formula_or_cask| args.named.to_formulae_and_casks.flat_map do |formula_or_cask|
case formula_or_cask case formula_or_cask
when Formula when Formula
formula = T.cast(formula_or_cask, Formula) formula = formula_or_cask
ref = formula.loaded_from_api? ? formula.full_name : formula.path [formula, *formula.recursive_dependencies.map(&:to_formula)]
else
formula_or_cask
end
end
else
args.named.to_formulae_and_casks
end.uniq
os_arch_combinations.each do |os, arch| os_arch_combinations = args.os_arch_combinations
SimulateSystem.with(os:, arch:) do
formula = Formulary.factory(ref, args.HEAD? ? :head : :stable)
formula.print_tap_action verb: "Fetching" puts "Fetching: #{bucket * ", "}" if bucket.size > 1
bucket.each do |formula_or_cask|
case formula_or_cask
when Formula
formula = T.cast(formula_or_cask, Formula)
ref = formula.loaded_from_api? ? formula.full_name : formula.path
fetched_bottle = false os_arch_combinations.each do |os, arch|
if fetch_bottle?( SimulateSystem.with(os:, arch:) do
formula, formula = Formulary.factory(ref, args.HEAD? ? :head : :stable)
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,
)
begin
formula.clear_cache if args.force?
bottle_tag = if (bottle_tag = args.bottle_tag&.to_sym) formula.print_tap_action verb: "Fetching"
Utils::Bottles::Tag.from_symbol(bottle_tag)
else fetched_bottle = false
Utils::Bottles::Tag.new(system: 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,
)
begin
formula.clear_cache if args.force?
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."
next
end
begin
bottle.fetch_tab
rescue DownloadError
retry if retry_fetch?(bottle)
raise
end
fetch_formula(bottle)
rescue Interrupt
raise
rescue => e
raise if Homebrew::EnvConfig.developer?
fetched_bottle = false
onoe e.message
opoo "Bottle fetch failed, fetching the source instead."
else
fetched_bottle = true
end
end end
bottle = formula.bottle_for_tag(bottle_tag) next if fetched_bottle
if bottle.nil? fetch_formula(formula)
opoo "Bottle for tag #{bottle_tag.to_sym.inspect} is unavailable."
formula.resources.each do |r|
fetch_resource(r)
r.patches.each { |p| fetch_patch(p) if p.external? }
end
formula.patchlist.each { |p| fetch_patch(p) if p.external? }
end
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
SimulateSystem.with(os:, arch:) do
cask = Cask::CaskLoader.load(ref)
if cask.url.nil? || cask.sha256.nil?
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
next next
end end
begin quarantine = args.quarantine?
bottle.fetch_tab quarantine = true if quarantine.nil?
rescue DownloadError
retry if retry_fetch?(bottle, args:)
raise
end
fetch_formula(bottle, args:)
rescue Interrupt
raise
rescue => e
raise if Homebrew::EnvConfig.developer?
fetched_bottle = false download = Cask::Download.new(cask, quarantine:)
onoe e.message fetch_cask(download)
opoo "Bottle fetch failed, fetching the source instead."
else
fetched_bottle = true
end end
end end
next if fetched_bottle
fetch_formula(formula, args:)
formula.resources.each do |r|
fetch_resource(r, args:)
r.patches.each { |p| fetch_patch(p, args:) if p.external? }
end
formula.patchlist.each { |p| fetch_patch(p, args:) if p.external? }
end
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
SimulateSystem.with(os:, arch:) do
cask = Cask::CaskLoader.load(ref)
if cask.url.nil? || cask.sha256.nil?
opoo "Cask #{cask} is not supported on os #{os} and arch #{arch}"
next
end
quarantine = args.quarantine?
quarantine = true if quarantine.nil?
download = Cask::Download.new(cask, quarantine:)
fetch_cask(download, args:)
end end
end end
end end
private
def fetch_resource(resource)
puts "Resource: #{resource.name}"
fetch_fetchable resource
rescue ChecksumMismatchError => e
retry if retry_fetch?(resource)
opoo "Resource #{resource.name} reports different sha256: #{e.expected}"
end
def fetch_formula(formula)
fetch_fetchable(formula)
rescue ChecksumMismatchError => e
retry if retry_fetch?(formula)
opoo "Formula reports different sha256: #{e.expected}"
end
def fetch_cask(cask_download)
fetch_fetchable(cask_download)
rescue ChecksumMismatchError => e
retry if retry_fetch?(cask_download)
opoo "Cask reports different sha256: #{e.expected}"
end
def fetch_patch(patch)
fetch_fetchable(patch)
rescue ChecksumMismatchError => e
opoo "Patch reports different sha256: #{e.expected}"
Homebrew.failed = true
end
def retry_fetch?(formula)
@fetch_tries ||= Hash.new { |h, k| h[k] = 1 }
if args.retry? && (@fetch_tries[formula] < FETCH_MAX_TRIES)
wait = 2 ** @fetch_tries[formula]
remaining = FETCH_MAX_TRIES - @fetch_tries[formula]
what = Utils.pluralize("tr", remaining, plural: "ies", singular: "y")
ohai "Retrying download in #{wait}s... (#{remaining} #{what} left)"
sleep wait
formula.clear_cache
@fetch_tries[formula] += 1
true
else
Homebrew.failed = true
false
end
end
def fetch_fetchable(formula)
formula.clear_cache if args.force?
already_fetched = formula.cached_download.exist?
begin
download = formula.fetch(verify_download_integrity: false)
rescue DownloadError
retry if retry_fetch?(formula)
raise
end
return unless download.file?
puts "Downloaded to: #{download}" unless already_fetched
puts "SHA256: #{download.sha256}"
formula.verify_download_integrity(download)
end
end end
end end
def self.fetch_resource(resource, args:)
puts "Resource: #{resource.name}"
fetch_fetchable resource, args:
rescue ChecksumMismatchError => e
retry if retry_fetch?(resource, args:)
opoo "Resource #{resource.name} reports different sha256: #{e.expected}"
end
def self.fetch_formula(formula, args:)
fetch_fetchable(formula, args:)
rescue ChecksumMismatchError => e
retry if retry_fetch?(formula, args:)
opoo "Formula reports different sha256: #{e.expected}"
end
def self.fetch_cask(cask_download, args:)
fetch_fetchable(cask_download, args:)
rescue ChecksumMismatchError => e
retry if retry_fetch?(cask_download, args:)
opoo "Cask reports different sha256: #{e.expected}"
end
def self.fetch_patch(patch, args:)
fetch_fetchable(patch, args:)
rescue ChecksumMismatchError => e
opoo "Patch reports different sha256: #{e.expected}"
Homebrew.failed = true
end
def self.retry_fetch?(formula, args:)
@fetch_tries ||= Hash.new { |h, k| h[k] = 1 }
if args.retry? && (@fetch_tries[formula] < FETCH_MAX_TRIES)
wait = 2 ** @fetch_tries[formula]
remaining = FETCH_MAX_TRIES - @fetch_tries[formula]
what = Utils.pluralize("tr", remaining, plural: "ies", singular: "y")
ohai "Retrying download in #{wait}s... (#{remaining} #{what} left)"
sleep wait
formula.clear_cache
@fetch_tries[formula] += 1
true
else
Homebrew.failed = true
false
end
end
def self.fetch_fetchable(formula, args:)
formula.clear_cache if args.force?
already_fetched = formula.cached_download.exist?
begin
download = formula.fetch(verify_download_integrity: false)
rescue DownloadError
retry if retry_fetch?(formula, args:)
raise
end
return unless download.file?
puts "Downloaded to: #{download}" unless already_fetched
puts "SHA256: #{download.sha256}"
formula.verify_download_integrity(download)
end
end end

View File

@ -1,130 +1,132 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "install" require "install"
require "system_config" require "system_config"
require "stringio" require "stringio"
require "socket" require "socket"
require "cli/parser"
module Homebrew module Homebrew
extend Install module Cmd
class GistLogs < AbstractCommand
include Install
cmd_args do
description <<~EOS
Upload logs for a failed build of <formula> to a new Gist. Presents an
error message if no logs are found.
EOS
switch "--with-hostname",
description: "Include the hostname in the Gist."
switch "-n", "--new-issue",
description: "Automatically create a new issue in the appropriate GitHub repository " \
"after creating the Gist."
switch "-p", "--private",
description: "The Gist will be marked private and will not appear in listings but will " \
"be accessible with its link."
module_function named_args :formula, number: 1
end
sig { returns(CLI::Parser) } sig { override.void }
def gist_logs_args def run
Homebrew::CLI::Parser.new do Install.perform_preinstall_checks(all_fatal: true)
description <<~EOS Install.perform_build_from_source_checks(all_fatal: true)
Upload logs for a failed build of <formula> to a new Gist. Presents an gistify_logs(args.named.to_resolved_formulae.first)
error message if no logs are found. end
EOS
switch "--with-hostname",
description: "Include the hostname in the Gist."
switch "-n", "--new-issue",
description: "Automatically create a new issue in the appropriate GitHub repository " \
"after creating the Gist."
switch "-p", "--private",
description: "The Gist will be marked private and will not appear in listings but will " \
"be accessible with its link."
named_args :formula, number: 1 private
end
end
def gistify_logs(formula, args:) def gistify_logs(formula)
files = load_logs(formula.logs) files = load_logs(formula.logs)
build_time = formula.logs.ctime build_time = formula.logs.ctime
timestamp = build_time.strftime("%Y-%m-%d_%H-%M-%S") timestamp = build_time.strftime("%Y-%m-%d_%H-%M-%S")
s = StringIO.new s = StringIO.new
SystemConfig.dump_verbose_config s SystemConfig.dump_verbose_config s
# Dummy summary file, asciibetically first, to control display title of gist # Dummy summary file, asciibetically first, to control display title of gist
files["# #{formula.name} - #{timestamp}.txt"] = { files["# #{formula.name} - #{timestamp}.txt"] = {
content: brief_build_info(formula, with_hostname: args.with_hostname?), content: brief_build_info(formula, with_hostname: args.with_hostname?),
} }
files["00.config.out"] = { content: s.string } files["00.config.out"] = { content: s.string }
files["00.doctor.out"] = { content: Utils.popen_read("#{HOMEBREW_PREFIX}/bin/brew", "doctor", err: :out) } files["00.doctor.out"] = { content: Utils.popen_read("#{HOMEBREW_PREFIX}/bin/brew", "doctor", err: :out) }
unless formula.core_formula? unless formula.core_formula?
tap = <<~EOS tap = <<~EOS
Formula: #{formula.name} Formula: #{formula.name}
Tap: #{formula.tap} Tap: #{formula.tap}
Path: #{formula.path} Path: #{formula.path}
EOS EOS
files["00.tap.out"] = { content: tap } files["00.tap.out"] = { content: tap }
end
odie "`brew gist-logs` requires HOMEBREW_GITHUB_API_TOKEN to be set!" if GitHub::API.credentials_type == :none
# Description formatted to work well as page title when viewing gist
descr = if formula.core_formula?
"#{formula.name} on #{OS_VERSION} - Homebrew build logs"
else
"#{formula.name} (#{formula.full_name}) on #{OS_VERSION} - Homebrew build logs"
end
begin
url = GitHub.create_gist(files, descr, private: args.private?)
rescue GitHub::API::HTTPNotFoundError
odie <<~EOS
Your GitHub API token likely doesn't have the `gist` scope.
#{GitHub.pat_blurb(GitHub::CREATE_GIST_SCOPES)}
EOS
end
url = GitHub.create_issue(formula.tap, "#{formula.name} failed to build on #{OS_VERSION}", url) if args.new_issue?
puts url if url
end
def brief_build_info(formula, with_hostname:)
build_time_string = formula.logs.ctime.strftime("%Y-%m-%d %H:%M:%S")
string = +<<~EOS
Homebrew build logs for #{formula.full_name} on #{OS_VERSION}
EOS
if with_hostname
hostname = Socket.gethostname
string << "Host: #{hostname}\n"
end
string << "Build date: #{build_time_string}\n"
string.freeze
end
# Causes some terminals to display secure password entry indicators.
def noecho_gets
system "stty", "-echo"
result = $stdin.gets
system "stty", "echo"
puts
result
end
def load_logs(dir, basedir = dir)
logs = {}
if dir.exist?
dir.children.sort.each do |file|
if file.directory?
logs.merge! load_logs(file, basedir)
else
contents = file.size? ? file.read : "empty log"
# small enough to avoid GitHub "unicorn" page-load-timeout errors
max_file_size = 1_000_000
contents = truncate_text_to_approximate_size(contents, max_file_size, front_weight: 0.2)
logs[file.relative_path_from(basedir).to_s.tr("/", ":")] = { content: contents }
end end
odie "`brew gist-logs` requires HOMEBREW_GITHUB_API_TOKEN to be set!" if GitHub::API.credentials_type == :none
# Description formatted to work well as page title when viewing gist
descr = if formula.core_formula?
"#{formula.name} on #{OS_VERSION} - Homebrew build logs"
else
"#{formula.name} (#{formula.full_name}) on #{OS_VERSION} - Homebrew build logs"
end
begin
url = GitHub.create_gist(files, descr, private: args.private?)
rescue GitHub::API::HTTPNotFoundError
odie <<~EOS
Your GitHub API token likely doesn't have the `gist` scope.
#{GitHub.pat_blurb(GitHub::CREATE_GIST_SCOPES)}
EOS
end
if args.new_issue?
url = GitHub.create_issue(formula.tap, "#{formula.name} failed to build on #{OS_VERSION}",
url)
end
puts url if url
end
def brief_build_info(formula, with_hostname:)
build_time_string = formula.logs.ctime.strftime("%Y-%m-%d %H:%M:%S")
string = +<<~EOS
Homebrew build logs for #{formula.full_name} on #{OS_VERSION}
EOS
if with_hostname
hostname = Socket.gethostname
string << "Host: #{hostname}\n"
end
string << "Build date: #{build_time_string}\n"
string.freeze
end
# Causes some terminals to display secure password entry indicators.
def noecho_gets
system "stty", "-echo"
result = $stdin.gets
system "stty", "echo"
puts
result
end
def load_logs(dir, basedir = dir)
logs = {}
if dir.exist?
dir.children.sort.each do |file|
if file.directory?
logs.merge! load_logs(file, basedir)
else
contents = file.size? ? file.read : "empty log"
# small enough to avoid GitHub "unicorn" page-load-timeout errors
max_file_size = 1_000_000
contents = truncate_text_to_approximate_size(contents, max_file_size, front_weight: 0.2)
logs[file.relative_path_from(basedir).to_s.tr("/", ":")] = { content: contents }
end
end
end
odie "No logs." if logs.empty?
logs
end end
end end
odie "No logs." if logs.empty?
logs
end
def gist_logs
args = gist_logs_args.parse
Install.perform_preinstall_checks(all_fatal: true)
Install.perform_build_from_source_checks(all_fatal: true)
gistify_logs(args.named.to_resolved_formulae.first, args:)
end end
end end

View File

@ -1,11 +1,16 @@
# typed: strict # typed: strong
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "help" require "help"
module Homebrew module Homebrew
sig { returns(T.noreturn) } module Cmd
def help class HelpCmd < AbstractCommand
Help.help sig { override.void }
def run
Help.help
end
end
end end
end end

View File

@ -1,54 +1,53 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "abstract_command"
require "formula" require "formula"
module Homebrew module Homebrew
module_function module Cmd
class Home < AbstractCommand
cmd_args do
description <<~EOS
Open a <formula> or <cask>'s homepage in a browser, or open
Homebrew's own homepage if no argument is provided.
EOS
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
sig { returns(CLI::Parser) } conflicts "--formula", "--cask"
def home_args
Homebrew::CLI::Parser.new do
description <<~EOS
Open a <formula> or <cask>'s homepage in a browser, or open
Homebrew's own homepage if no argument is provided.
EOS
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask" named_args [:formula, :cask]
end
named_args [:formula, :cask] sig { override.void }
end def run
end if args.no_named?
exec_browser HOMEBREW_WWW
return
end
sig { void } # to_formulae_and_casks is typed to possibly return Kegs (but won't without explicitly asking)
def home formulae_or_casks = T.cast(args.named.to_formulae_and_casks, T::Array[T.any(Formula, Cask::Cask)])
args = home_args.parse homepages = formulae_or_casks.map do |formula_or_cask|
puts "Opening homepage for #{name_of(formula_or_cask)}"
formula_or_cask.homepage
end
if args.no_named? exec_browser(*T.unsafe(homepages))
exec_browser HOMEBREW_WWW end
return
end
# to_formulae_and_casks is typed to possibly return Kegs (but won't without explicitly asking) private
formulae_or_casks = T.cast(args.named.to_formulae_and_casks, T::Array[T.any(Formula, Cask::Cask)])
homepages = formulae_or_casks.map do |formula_or_cask|
puts "Opening homepage for #{name_of(formula_or_cask)}"
formula_or_cask.homepage
end
exec_browser(*T.unsafe(homepages)) def name_of(formula_or_cask)
end if formula_or_cask.is_a? Formula
"Formula #{formula_or_cask.name}"
def name_of(formula_or_cask) else
if formula_or_cask.is_a? Formula "Cask #{formula_or_cask.token}"
"Formula #{formula_or_cask.name}" end
else end
"Cask #{formula_or_cask.token}"
end end
end end
end end

View File

@ -1,9 +1,9 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "missing_formula" require "missing_formula"
require "caveats" require "caveats"
require "cli/parser"
require "options" require "options"
require "formula" require "formula"
require "keg" require "keg"
@ -14,364 +14,363 @@ require "deprecate_disable"
require "api" require "api"
module Homebrew module Homebrew
module_function module Cmd
class Info < AbstractCommand
VALID_DAYS = %w[30 90 365].freeze
VALID_FORMULA_CATEGORIES = %w[install install-on-request build-error].freeze
VALID_CATEGORIES = (VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze
VALID_DAYS = %w[30 90 365].freeze cmd_args do
VALID_FORMULA_CATEGORIES = %w[install install-on-request build-error].freeze description <<~EOS
VALID_CATEGORIES = (VALID_FORMULA_CATEGORIES + %w[cask-install os-version]).freeze Display brief statistics for your Homebrew installation.
If a <formula> or <cask> is provided, show summary of information about it.
EOS
switch "--analytics",
description: "List global Homebrew analytics data or, if specified, installation and " \
"build error data for <formula> (provided neither `HOMEBREW_NO_ANALYTICS` " \
"nor `HOMEBREW_NO_GITHUB_API` are set)."
flag "--days=",
depends_on: "--analytics",
description: "How many days of analytics data to retrieve. " \
"The value for <days> must be `30`, `90` or `365`. The default is `30`."
flag "--category=",
depends_on: "--analytics",
description: "Which type of analytics data to retrieve. " \
"The value for <category> must be `install`, `install-on-request` or `build-error`; " \
"`cask-install` or `os-version` may be specified if <formula> is not. " \
"The default is `install`."
switch "--github-packages-downloads",
description: "Scrape GitHub Packages download counts from HTML for a core formula.",
hidden: true
switch "--github",
description: "Open the GitHub source page for <formula> and <cask> in a browser. " \
"To view the history locally: `brew log -p` <formula> or <cask>"
flag "--json",
description: "Print a JSON representation. Currently the default value for <version> is `v1` for " \
"<formula>. For <formula> and <cask> use `v2`. See the docs for examples of using the " \
"JSON output: <https://docs.brew.sh/Querying-Brew>"
switch "--installed",
depends_on: "--json",
description: "Print JSON of formulae that are currently installed."
switch "--eval-all",
depends_on: "--json",
description: "Evaluate all available formulae and casks, whether installed or not, to print their " \
"JSON. Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--variations",
depends_on: "--json",
description: "Include the variations hash in each formula's JSON output."
switch "-v", "--verbose",
description: "Show more verbose analytics data for <formula>."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
sig { returns(CLI::Parser) } conflicts "--installed", "--eval-all"
def info_args conflicts "--installed", "--all"
Homebrew::CLI::Parser.new do conflicts "--formula", "--cask"
description <<~EOS
Display brief statistics for your Homebrew installation.
If a <formula> or <cask> is provided, show summary of information about it.
EOS
switch "--analytics",
description: "List global Homebrew analytics data or, if specified, installation and " \
"build error data for <formula> (provided neither `HOMEBREW_NO_ANALYTICS` " \
"nor `HOMEBREW_NO_GITHUB_API` are set)."
flag "--days=",
depends_on: "--analytics",
description: "How many days of analytics data to retrieve. " \
"The value for <days> must be `30`, `90` or `365`. The default is `30`."
flag "--category=",
depends_on: "--analytics",
description: "Which type of analytics data to retrieve. " \
"The value for <category> must be `install`, `install-on-request` or `build-error`; " \
"`cask-install` or `os-version` may be specified if <formula> is not. " \
"The default is `install`."
switch "--github-packages-downloads",
description: "Scrape GitHub Packages download counts from HTML for a core formula.",
hidden: true
switch "--github",
description: "Open the GitHub source page for <formula> and <cask> in a browser. " \
"To view the history locally: `brew log -p` <formula> or <cask>"
flag "--json",
description: "Print a JSON representation. Currently the default value for <version> is `v1` for " \
"<formula>. For <formula> and <cask> use `v2`. See the docs for examples of using the " \
"JSON output: <https://docs.brew.sh/Querying-Brew>"
switch "--installed",
depends_on: "--json",
description: "Print JSON of formulae that are currently installed."
switch "--eval-all",
depends_on: "--json",
description: "Evaluate all available formulae and casks, whether installed or not, to print their " \
"JSON. Implied if `HOMEBREW_EVAL_ALL` is set."
switch "--variations",
depends_on: "--json",
description: "Include the variations hash in each formula's JSON output."
switch "-v", "--verbose",
description: "Show more verbose analytics data for <formula>."
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--installed", "--eval-all" named_args [:formula, :cask]
conflicts "--installed", "--all"
conflicts "--formula", "--cask"
named_args [:formula, :cask]
end
end
sig { void }
def info
args = info_args.parse
if args.analytics?
if args.days.present? && VALID_DAYS.exclude?(args.days)
raise UsageError, "`--days` must be one of #{VALID_DAYS.join(", ")}."
end end
if args.category.present? sig { override.void }
if args.named.present? && VALID_FORMULA_CATEGORIES.exclude?(args.category) def run
raise UsageError, if args.analytics?
"`--category` must be one of #{VALID_FORMULA_CATEGORIES.join(", ")} when querying formulae." if args.days.present? && VALID_DAYS.exclude?(args.days)
end raise UsageError, "`--days` must be one of #{VALID_DAYS.join(", ")}."
end
unless VALID_CATEGORIES.include?(args.category) if args.category.present?
raise UsageError, "`--category` must be one of #{VALID_CATEGORIES.join(", ")}." if args.named.present? && VALID_FORMULA_CATEGORIES.exclude?(args.category)
raise UsageError,
"`--category` must be one of #{VALID_FORMULA_CATEGORIES.join(", ")} when querying formulae."
end
unless VALID_CATEGORIES.include?(args.category)
raise UsageError, "`--category` must be one of #{VALID_CATEGORIES.join(", ")}."
end
end
print_analytics
elsif args.json
all = args.eval_all?
print_json(all)
elsif args.github?
raise FormulaOrCaskUnspecifiedError if args.no_named?
exec_browser(*args.named.to_formulae_and_casks.map { |f| github_info(f) })
elsif args.no_named?
print_statistics
else
print_info
end end
end end
print_analytics(args:) def github_remote_path(remote, path)
elsif args.json if remote =~ %r{^(?:https?://|git(?:@|://))github\.com[:/](.+)/(.+?)(?:\.git)?$}
all = args.eval_all? "https://github.com/#{Regexp.last_match(1)}/#{Regexp.last_match(2)}/blob/HEAD/#{path}"
else
print_json(all, args:) "#{remote}/#{path}"
elsif args.github?
raise FormulaOrCaskUnspecifiedError if args.no_named?
exec_browser(*args.named.to_formulae_and_casks.map { |f| github_info(f) })
elsif args.no_named?
print_statistics
else
print_info(args:)
end
end
sig { void }
def print_statistics
return unless HOMEBREW_CELLAR.exist?
count = Formula.racks.length
puts "#{Utils.pluralize("keg", count, include_count: true)}, #{HOMEBREW_CELLAR.dup.abv}"
end
sig { params(args: CLI::Args).void }
def print_analytics(args:)
if args.no_named?
Utils::Analytics.output(args:)
return
end
args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
puts unless i.zero?
case obj
when Formula
Utils::Analytics.formula_output(obj, args:)
when Cask::Cask
Utils::Analytics.cask_output(obj, args:)
when FormulaOrCaskUnavailableError
Utils::Analytics.output(filter: obj.name, args:)
else
raise
end
end
end
sig { params(args: CLI::Args).void }
def print_info(args:)
args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
puts unless i.zero?
case obj
when Formula
info_formula(obj, args:)
when Cask::Cask
info_cask(obj, args:)
when FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError,
Cask::CaskUnreadableError
# We found the formula/cask, but failed to read it
$stderr.puts obj.backtrace if Homebrew::EnvConfig.developer?
ofail obj.message
when FormulaOrCaskUnavailableError
# The formula/cask could not be found
ofail obj.message
# No formula with this name, try a missing formula lookup
if (reason = MissingFormula.reason(obj.name, show_info: true))
$stderr.puts reason
end end
else
raise
end
end
end
def json_version(version)
version_hash = {
true => :default,
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
version_hash[version]
end
sig { params(all: T::Boolean, args: T.untyped).void }
def print_json(all, args:)
raise FormulaOrCaskUnspecifiedError if !(all || args.installed?) && args.no_named?
json = case json_version(args.json)
when :v1, :default
raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask?
formulae = if all
Formula.all(eval_all: args.eval_all?).sort
elsif args.installed?
Formula.installed.sort
else
args.named.to_formulae
end end
if args.variations? private
formulae.map(&:to_hash_with_variations)
else sig { void }
formulae.map(&:to_hash) def print_statistics
return unless HOMEBREW_CELLAR.exist?
count = Formula.racks.length
puts "#{Utils.pluralize("keg", count, include_count: true)}, #{HOMEBREW_CELLAR.dup.abv}"
end end
when :v2
formulae, casks = if all sig { void }
[ def print_analytics
Formula.all(eval_all: args.eval_all?).sort, if args.no_named?
Cask::Cask.all(eval_all: args.eval_all?).sort_by(&:full_name), Utils::Analytics.output(args:)
return
end
args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
puts unless i.zero?
case obj
when Formula
Utils::Analytics.formula_output(obj, args:)
when Cask::Cask
Utils::Analytics.cask_output(obj, args:)
when FormulaOrCaskUnavailableError
Utils::Analytics.output(filter: obj.name, args:)
else
raise
end
end
end
sig { void }
def print_info
args.named.to_formulae_and_casks_and_unavailable.each_with_index do |obj, i|
puts unless i.zero?
case obj
when Formula
info_formula(obj)
when Cask::Cask
info_cask(obj)
when FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError,
Cask::CaskUnreadableError
# We found the formula/cask, but failed to read it
$stderr.puts obj.backtrace if Homebrew::EnvConfig.developer?
ofail obj.message
when FormulaOrCaskUnavailableError
# The formula/cask could not be found
ofail obj.message
# No formula with this name, try a missing formula lookup
if (reason = MissingFormula.reason(obj.name, show_info: true))
$stderr.puts reason
end
else
raise
end
end
end
def json_version(version)
version_hash = {
true => :default,
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
version_hash[version]
end
sig { params(all: T::Boolean).void }
def print_json(all)
raise FormulaOrCaskUnspecifiedError if !(all || args.installed?) && args.no_named?
json = case json_version(args.json)
when :v1, :default
raise UsageError, "Cannot specify `--cask` when using `--json=v1`!" if args.cask?
formulae = if all
Formula.all(eval_all: args.eval_all?).sort
elsif args.installed?
Formula.installed.sort
else
args.named.to_formulae
end
if args.variations?
formulae.map(&:to_hash_with_variations)
else
formulae.map(&:to_hash)
end
when :v2
formulae, casks = if all
[
Formula.all(eval_all: args.eval_all?).sort,
Cask::Cask.all(eval_all: args.eval_all?).sort_by(&:full_name),
]
elsif args.installed?
[Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)]
else
args.named.to_formulae_to_casks
end
if args.variations?
{
"formulae" => formulae.map(&:to_hash_with_variations),
"casks" => casks.map(&:to_hash_with_variations),
}
else
{
"formulae" => formulae.map(&:to_hash),
"casks" => casks.map(&:to_h),
}
end
else
raise
end
puts JSON.pretty_generate(json)
end
def github_info(formula_or_cask)
return formula_or_cask.path if formula_or_cask.tap.blank? || formula_or_cask.tap.remote.blank?
path = case formula_or_cask
when Formula
formula = formula_or_cask
formula.path.relative_path_from(T.must(formula.tap).path)
when Cask::Cask
cask = formula_or_cask
if cask.sourcefile_path.blank?
return "#{cask.tap.default_remote}/blob/HEAD/#{cask.tap.relative_cask_path(cask.token)}"
end
cask.sourcefile_path.relative_path_from(cask.tap.path)
end
github_remote_path(formula_or_cask.tap.remote, path)
end
def info_formula(formula)
specs = []
if (stable = formula.stable)
string = "stable #{stable.version}"
string += " (bottled)" if stable.bottled? && formula.pour_bottle?
specs << string
end
specs << "HEAD" if formula.head
attrs = []
attrs << "pinned at #{formula.pinned_version}" if formula.pinned?
attrs << "keg-only" if formula.keg_only?
puts "#{oh1_title(formula.full_name)}: #{specs * ", "}#{" [#{attrs * ", "}]" unless attrs.empty?}"
puts formula.desc if formula.desc
puts Formatter.url(formula.homepage) if formula.homepage
deprecate_disable_info_string = DeprecateDisable.message(formula)
puts deprecate_disable_info_string.capitalize if deprecate_disable_info_string.present?
conflicts = formula.conflicts.map do |conflict|
reason = " (because #{conflict.reason})" if conflict.reason
"#{conflict.name}#{reason}"
end.sort!
unless conflicts.empty?
puts <<~EOS
Conflicts with:
#{conflicts.join("\n ")}
EOS
end
kegs = formula.installed_kegs
heads, versioned = kegs.partition { |k| k.version.head? }
kegs = [
*heads.sort_by { |k| -Tab.for_keg(k).time.to_i },
*Keg.sort(versioned),
] ]
elsif args.installed? if kegs.empty?
[Formula.installed.sort, Cask::Caskroom.casks.sort_by(&:full_name)] puts "Not installed"
else else
args.named.to_formulae_to_casks kegs.each do |keg|
puts "#{keg} (#{keg.abv})#{" *" if keg.linked?}"
tab = Tab.for_keg(keg).to_s
puts " #{tab}" unless tab.empty?
end
end
puts "From: #{Formatter.url(github_info(formula))}"
puts "License: #{SPDX.license_expression_to_string formula.license}" if formula.license.present?
unless formula.deps.empty?
ohai "Dependencies"
%w[build required recommended optional].map do |type|
deps = formula.deps.send(type).uniq
puts "#{type.capitalize}: #{decorate_dependencies deps}" unless deps.empty?
end
end
unless formula.requirements.to_a.empty?
ohai "Requirements"
%w[build required recommended optional].map do |type|
reqs = formula.requirements.select(&:"#{type}?")
next if reqs.to_a.empty?
puts "#{type.capitalize}: #{decorate_requirements(reqs)}"
end
end
if !formula.options.empty? || formula.head
ohai "Options"
Options.dump_for_formula formula
end
caveats = Caveats.new(formula)
ohai "Caveats", caveats.to_s unless caveats.empty?
Utils::Analytics.formula_output(formula, args:)
end end
if args.variations? def decorate_dependencies(dependencies)
{ deps_status = dependencies.map do |dep|
"formulae" => formulae.map(&:to_hash_with_variations), if dep.satisfied?([])
"casks" => casks.map(&:to_hash_with_variations), pretty_installed(dep_display_s(dep))
} else
else pretty_uninstalled(dep_display_s(dep))
{ end
"formulae" => formulae.map(&:to_hash), end
"casks" => casks.map(&:to_h), deps_status.join(", ")
}
end
else
raise
end
puts JSON.pretty_generate(json)
end
def github_remote_path(remote, path)
if remote =~ %r{^(?:https?://|git(?:@|://))github\.com[:/](.+)/(.+?)(?:\.git)?$}
"https://github.com/#{Regexp.last_match(1)}/#{Regexp.last_match(2)}/blob/HEAD/#{path}"
else
"#{remote}/#{path}"
end
end
def github_info(formula_or_cask)
return formula_or_cask.path if formula_or_cask.tap.blank? || formula_or_cask.tap.remote.blank?
path = case formula_or_cask
when Formula
formula = formula_or_cask
formula.path.relative_path_from(T.must(formula.tap).path)
when Cask::Cask
cask = formula_or_cask
if cask.sourcefile_path.blank?
return "#{cask.tap.default_remote}/blob/HEAD/#{cask.tap.relative_cask_path(cask.token)}"
end end
cask.sourcefile_path.relative_path_from(cask.tap.path) def decorate_requirements(requirements)
end req_status = requirements.map do |req|
req_s = req.display_s
req.satisfied? ? pretty_installed(req_s) : pretty_uninstalled(req_s)
end
req_status.join(", ")
end
github_remote_path(formula_or_cask.tap.remote, path) def dep_display_s(dep)
end return dep.name if dep.option_tags.empty?
def info_formula(formula, args:) "#{dep.name} #{dep.option_tags.map { |o| "--#{o}" }.join(" ")}"
specs = [] end
if (stable = formula.stable) def info_cask(cask)
string = "stable #{stable.version}" require "cask/info"
string += " (bottled)" if stable.bottled? && formula.pour_bottle?
specs << string
end
specs << "HEAD" if formula.head Cask::Info.info(cask)
attrs = []
attrs << "pinned at #{formula.pinned_version}" if formula.pinned?
attrs << "keg-only" if formula.keg_only?
puts "#{oh1_title(formula.full_name)}: #{specs * ", "}#{" [#{attrs * ", "}]" unless attrs.empty?}"
puts formula.desc if formula.desc
puts Formatter.url(formula.homepage) if formula.homepage
deprecate_disable_info_string = DeprecateDisable.message(formula)
puts deprecate_disable_info_string.capitalize if deprecate_disable_info_string.present?
conflicts = formula.conflicts.map do |conflict|
reason = " (because #{conflict.reason})" if conflict.reason
"#{conflict.name}#{reason}"
end.sort!
unless conflicts.empty?
puts <<~EOS
Conflicts with:
#{conflicts.join("\n ")}
EOS
end
kegs = formula.installed_kegs
heads, versioned = kegs.partition { |k| k.version.head? }
kegs = [
*heads.sort_by { |k| -Tab.for_keg(k).time.to_i },
*Keg.sort(versioned),
]
if kegs.empty?
puts "Not installed"
else
kegs.each do |keg|
puts "#{keg} (#{keg.abv})#{" *" if keg.linked?}"
tab = Tab.for_keg(keg).to_s
puts " #{tab}" unless tab.empty?
end end
end end
puts "From: #{Formatter.url(github_info(formula))}"
puts "License: #{SPDX.license_expression_to_string formula.license}" if formula.license.present?
unless formula.deps.empty?
ohai "Dependencies"
%w[build required recommended optional].map do |type|
deps = formula.deps.send(type).uniq
puts "#{type.capitalize}: #{decorate_dependencies deps}" unless deps.empty?
end
end
unless formula.requirements.to_a.empty?
ohai "Requirements"
%w[build required recommended optional].map do |type|
reqs = formula.requirements.select(&:"#{type}?")
next if reqs.to_a.empty?
puts "#{type.capitalize}: #{decorate_requirements(reqs)}"
end
end
if !formula.options.empty? || formula.head
ohai "Options"
Options.dump_for_formula formula
end
caveats = Caveats.new(formula)
ohai "Caveats", caveats.to_s unless caveats.empty?
Utils::Analytics.formula_output(formula, args:)
end
def decorate_dependencies(dependencies)
deps_status = dependencies.map do |dep|
if dep.satisfied?([])
pretty_installed(dep_display_s(dep))
else
pretty_uninstalled(dep_display_s(dep))
end
end
deps_status.join(", ")
end
def decorate_requirements(requirements)
req_status = requirements.map do |req|
req_s = req.display_s
req.satisfied? ? pretty_installed(req_s) : pretty_uninstalled(req_s)
end
req_status.join(", ")
end
def dep_display_s(dep)
return dep.name if dep.option_tags.empty?
"#{dep.name} #{dep.option_tags.map { |o| "--#{o}" }.join(" ")}"
end
def info_cask(cask, args:)
require "cask/info"
Cask::Info.info(cask)
end end
end end

View File

@ -1,6 +1,7 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "cask/config" require "cask/config"
require "cask/installer" require "cask/installer"
require "cask_dependent" require "cask_dependent"
@ -9,400 +10,402 @@ require "formula_installer"
require "development_tools" require "development_tools"
require "install" require "install"
require "cleanup" require "cleanup"
require "cli/parser"
require "upgrade" require "upgrade"
module Homebrew module Homebrew
sig { returns(CLI::Parser) } module Cmd
def self.install_args class InstallCmd < AbstractCommand
Homebrew::CLI::Parser.new do cmd_args do
description <<~EOS description <<~EOS
Install a <formula> or <cask>. Additional options specific to a <formula> may be Install a <formula> or <cask>. Additional options specific to a <formula> may be
appended to the command. appended to the command.
Unless `HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK` is set, `brew upgrade` or `brew reinstall` will be run for 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. outdated dependents and dependents with broken linkage, respectively.
Unless `HOMEBREW_NO_INSTALL_CLEANUP` is set, `brew cleanup` will then be run for Unless `HOMEBREW_NO_INSTALL_CLEANUP` is set, `brew cleanup` will then be run for
the installed formulae or, every 30 days, for all formulae. the installed formulae or, every 30 days, for all formulae.
Unless `HOMEBREW_NO_INSTALL_UPGRADE` is set, `brew install` <formula> will upgrade <formula> if it Unless `HOMEBREW_NO_INSTALL_UPGRADE` is set, `brew install` <formula> will upgrade <formula> if it
is already installed but outdated. is already installed but outdated.
EOS EOS
switch "-d", "--debug", switch "-d", "--debug",
description: "If brewing fails, open an interactive debugging session with access to IRB " \ description: "If brewing fails, open an interactive debugging session with access to IRB " \
"or a shell inside the temporary build directory." "or a shell inside the temporary build directory."
switch "-f", "--force", switch "-f", "--force",
description: "Install formulae without checking for previously installed keg-only or " \ description: "Install formulae without checking for previously installed keg-only or " \
"non-migrated versions. When installing casks, overwrite existing files " \ "non-migrated versions. When installing casks, overwrite existing files " \
"(binaries and symlinks are excluded, unless originally from the same cask)." "(binaries and symlinks are excluded, unless originally from the same cask)."
switch "-v", "--verbose", switch "-v", "--verbose",
description: "Print the verification and post-install steps." description: "Print the verification and post-install steps."
switch "-n", "--dry-run", switch "-n", "--dry-run",
description: "Show what would be installed, but do not actually install anything." description: "Show what would be installed, but do not actually install anything."
[ [
[:switch, "--formula", "--formulae", { [:switch, "--formula", "--formulae", {
description: "Treat all named arguments as formulae.", description: "Treat all named arguments as formulae.",
}], }],
[:flag, "--env=", { [:flag, "--env=", {
description: "Disabled other than for internal Homebrew use.", description: "Disabled other than for internal Homebrew use.",
hidden: true, hidden: true,
}], }],
[:switch, "--ignore-dependencies", { [:switch, "--ignore-dependencies", {
description: "An unsupported Homebrew development option to skip installing any dependencies of any " \ description: "An unsupported Homebrew development option to skip installing any dependencies of any " \
"kind. If the dependencies are not already present, the formula will have issues. If you're " \ "kind. If the dependencies are not already present, the formula will have issues. If " \
"not developing Homebrew, consider adjusting your PATH rather than using this option.", "you're not developing Homebrew, consider adjusting your PATH rather than using this " \
}], "option.",
[:switch, "--only-dependencies", { }],
description: "Install the dependencies with specified options but do not install the " \ [:switch, "--only-dependencies", {
"formula itself.", description: "Install the dependencies with specified options but do not install the " \
}], "formula itself.",
[:flag, "--cc=", { }],
description: "Attempt to compile using the specified <compiler>, which should be the name of the " \ [:flag, "--cc=", {
"compiler's executable, e.g. `gcc-7` for GCC 7. In order to use LLVM's clang, specify " \ description: "Attempt to compile using the specified <compiler>, which should be the name of the " \
"`llvm_clang`. To use the Apple-provided clang, specify `clang`. This option will only " \ "compiler's executable, e.g. `gcc-7` for GCC 7. In order to use LLVM's clang, specify " \
"accept compilers that are provided by Homebrew or bundled with macOS. Please do not " \ "`llvm_clang`. To use the Apple-provided clang, specify `clang`. This option will only " \
"file issues if you encounter errors while using this option.", "accept compilers that are provided by Homebrew or bundled with macOS. Please do not " \
}], "file issues if you encounter errors while using this option.",
[:switch, "-s", "--build-from-source", { }],
description: "Compile <formula> from source even if a bottle is provided. " \ [:switch, "-s", "--build-from-source", {
"Dependencies will still be installed from bottles if they are available.", description: "Compile <formula> from source even if a bottle is provided. " \
}], "Dependencies will still be installed from bottles if they are available.",
[:switch, "--force-bottle", { }],
description: "Install from a bottle if it exists for the current or newest version of " \ [:switch, "--force-bottle", {
"macOS, even if it would not normally be used for installation.", 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, "--include-test", { }],
description: "Install testing dependencies required to run `brew test` <formula>.", [:switch, "--include-test", {
}], description: "Install testing dependencies required to run `brew test` <formula>.",
[:switch, "--HEAD", { }],
description: "If <formula> defines it, install the HEAD version, aka. main, trunk, unstable, master.", [:switch, "--HEAD", {
}], description: "If <formula> defines it, install the HEAD version, aka. main, trunk, unstable, master.",
[:switch, "--fetch-HEAD", { }],
description: "Fetch the upstream repository to detect if the HEAD installation of the " \ [:switch, "--fetch-HEAD", {
"formula is outdated. Otherwise, the repository's HEAD will only be checked for " \ description: "Fetch the upstream repository to detect if the HEAD installation of the " \
"updates when a new stable or development version has been released.", "formula is outdated. Otherwise, the repository's HEAD will only be checked for " \
}], "updates when a new stable or development version has been released.",
[:switch, "--keep-tmp", { }],
description: "Retain the temporary files created during installation.", [:switch, "--keep-tmp", {
}], description: "Retain the temporary files created during installation.",
[:switch, "--debug-symbols", { }],
depends_on: "--build-from-source", [:switch, "--debug-symbols", {
description: "Generate debug symbols on build. Source will be retained in a cache directory.", depends_on: "--build-from-source",
}], description: "Generate debug symbols on build. Source will be retained in a cache directory.",
[:switch, "--build-bottle", { }],
description: "Prepare the formula for eventual bottling during installation, skipping any " \ [:switch, "--build-bottle", {
"post-install steps.", description: "Prepare the formula for eventual bottling during installation, skipping any " \
}], "post-install steps.",
[:switch, "--skip-post-install", { }],
description: "Install but skip any post-install steps.", [:switch, "--skip-post-install", {
}], description: "Install but skip any post-install steps.",
[:flag, "--bottle-arch=", { }],
depends_on: "--build-bottle", [:flag, "--bottle-arch=", {
description: "Optimise bottles for the specified architecture rather than the oldest " \ depends_on: "--build-bottle",
"architecture supported by the version of macOS the bottles are built on.", description: "Optimise bottles for the specified architecture rather than the oldest " \
}], "architecture supported by the version of macOS the bottles are built on.",
[:switch, "--display-times", { }],
env: :display_install_times, [:switch, "--display-times", {
description: "Print install times for each package at the end of the run.", env: :display_install_times,
}], description: "Print install times for each package at the end of the run.",
[:switch, "-i", "--interactive", { }],
description: "Download and patch <formula>, then open a shell. This allows the user to " \ [:switch, "-i", "--interactive", {
"run `./configure --help` and otherwise determine how to turn the software " \ description: "Download and patch <formula>, then open a shell. This allows the user to " \
"package into a Homebrew package.", "run `./configure --help` and otherwise determine how to turn the software " \
}], "package into a Homebrew package.",
[:switch, "-g", "--git", { }],
description: "Create a Git repository, useful for creating patches to the software.", [:switch, "-g", "--git", {
}], description: "Create a Git repository, useful for creating patches to the software.",
[:switch, "--overwrite", { }],
description: "Delete files that already exist in the prefix while linking.", [:switch, "--overwrite", {
}], description: "Delete files that already exist in the prefix while linking.",
].each do |args| }],
options = args.pop ].each do |args|
send(*args, **options) options = args.pop
conflicts "--cask", args.last send(*args, **options)
end conflicts "--cask", args.last
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 "--ignore-dependencies", "--only-dependencies"
conflicts "--build-from-source", "--build-bottle", "--force-bottle"
conflicts "--adopt", "--force"
named_args [:formula, :cask], min: 1
end
end
def self.install
args = install_args.parse
if args.env.present?
# Can't use `replacement: false` because `install_args` are used by
# `build.rb`. Instead, `hide_from_man_page` and don't do anything with
# this argument here.
# This odisabled should stick around indefinitely.
odisabled "brew install --env", "`env :std` in specific formula files"
end
args.named.each do |name|
if (tap_with_name = Tap.with_formula_name(name))
tap, = tap_with_name
elsif (tap_with_token = Tap.with_cask_token(name))
tap, = tap_with_token
end
tap&.ensure_installed!
end
if args.ignore_dependencies?
opoo <<~EOS
#{Tty.bold}`--ignore-dependencies` is an unsupported Homebrew developer option!#{Tty.reset}
Adjust your PATH to put any preferred versions of applications earlier in the
PATH rather than using this unsupported option!
EOS
end
begin
formulae, casks = args.named.to_formulae_and_casks(warn: false)
.partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
rescue FormulaOrCaskUnavailableError, Cask::CaskUnavailableError
cask_tap = CoreCaskTap.instance
if !cask_tap.installed? && (args.cask? || Tap.untapped_official_taps.exclude?(cask_tap.name))
cask_tap.ensure_installed!
retry if cask_tap.installed?
end
raise
end
if casks.any?
if args.dry_run?
if (casks_to_install = casks.reject(&:installed?).presence)
ohai "Would install #{::Utils.pluralize("cask", casks_to_install.count, include_count: true)}:"
puts casks_to_install.map(&:full_name).join(" ")
end end
casks.each do |cask| formula_options
dep_names = CaskDependent.new(cask) [
.runtime_dependencies [:switch, "--cask", "--casks", { description: "Treat all named arguments as casks." }],
.reject(&:installed?) [:switch, "--[no-]binaries", {
.map(&:to_formula) description: "Disable/enable linking of helper executables (default: enabled).",
.map(&:name) env: :cask_opts_binaries,
next if dep_names.blank? }],
[: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
ohai "Would install #{::Utils.pluralize("dependenc", dep_names.count, plural: "ies", singular: "y", conflicts "--ignore-dependencies", "--only-dependencies"
conflicts "--build-from-source", "--build-bottle", "--force-bottle"
conflicts "--adopt", "--force"
named_args [:formula, :cask], min: 1
end
sig { override.void }
def run
if args.env.present?
# Can't use `replacement: false` because `install_args` are used by
# `build.rb`. Instead, `hide_from_man_page` and don't do anything with
# this argument here.
# This odisabled should stick around indefinitely.
odisabled "brew install --env", "`env :std` in specific formula files"
end
args.named.each do |name|
if (tap_with_name = Tap.with_formula_name(name))
tap, = tap_with_name
elsif (tap_with_token = Tap.with_cask_token(name))
tap, = tap_with_token
end
tap&.ensure_installed!
end
if args.ignore_dependencies?
opoo <<~EOS
#{Tty.bold}`--ignore-dependencies` is an unsupported Homebrew developer option!#{Tty.reset}
Adjust your PATH to put any preferred versions of applications earlier in the
PATH rather than using this unsupported option!
EOS
end
begin
formulae, casks = T.cast(
args.named.to_formulae_and_casks(warn: false).partition { _1.is_a?(Formula) },
[T::Array[Formula], T::Array[Cask::Cask]],
)
rescue FormulaOrCaskUnavailableError, Cask::CaskUnavailableError
cask_tap = CoreCaskTap.instance
if !cask_tap.installed? && (args.cask? || Tap.untapped_official_taps.exclude?(cask_tap.name))
cask_tap.ensure_installed!
retry if cask_tap.installed?
end
raise
end
if casks.any?
if args.dry_run?
if (casks_to_install = casks.reject(&:installed?).presence)
ohai "Would install #{::Utils.pluralize("cask", casks_to_install.count, include_count: true)}:"
puts casks_to_install.map(&:full_name).join(" ")
end
casks.each do |cask|
dep_names = CaskDependent.new(cask)
.runtime_dependencies
.reject(&:installed?)
.map(&:to_formula)
.map(&:name)
next if dep_names.blank?
ohai "Would install #{::Utils.pluralize("dependenc", dep_names.count, plural: "ies", singular: "y",
include_count: true)} for #{cask.full_name}:" include_count: true)} for #{cask.full_name}:"
puts dep_names.join(" ") puts dep_names.join(" ")
end
return
end
require "cask/installer"
installed_casks, new_casks = casks.partition(&:installed?)
new_casks.each do |cask|
Cask::Installer.new(
cask,
binaries: args.binaries?,
verbose: args.verbose?,
force: args.force?,
adopt: args.adopt?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
quarantine: args.quarantine?,
quiet: args.quiet?,
).install
end
if !Homebrew::EnvConfig.no_install_upgrade? && installed_casks.any?
require "cask/upgrade"
Cask::Upgrade.upgrade_casks(
*installed_casks,
force: args.force?,
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 end
return
end
require "cask/installer" # if the user's flags will prevent bottle only-installations when no
# developer tools are available, we need to stop them early on
build_flags = []
unless DevelopmentTools.installed?
build_flags << "--HEAD" if args.HEAD?
build_flags << "--build-bottle" if args.build_bottle?
build_flags << "--build-from-source" if args.build_from_source?
installed_casks, new_casks = casks.partition(&:installed?) raise BuildFlagsError.new(build_flags, bottled: formulae.all?(&:bottled?)) if build_flags.present?
end
new_casks.each do |cask| if build_flags.present? && !Homebrew::EnvConfig.developer?
Cask::Installer.new( opoo "building from source is not supported!"
cask, puts "You're on your own. Failures are expected so don't create any issues, please!"
binaries: args.binaries?, end
verbose: args.verbose?,
force: args.force?,
adopt: args.adopt?,
require_sha: args.require_sha?,
skip_cask_deps: args.skip_cask_deps?,
quarantine: args.quarantine?,
quiet: args.quiet?,
).install
end
if !Homebrew::EnvConfig.no_install_upgrade? && installed_casks.any? installed_formulae = formulae.select do |f|
require "cask/upgrade" Install.install_formula?(
f,
head: args.HEAD?,
fetch_head: args.fetch_HEAD?,
only_dependencies: args.only_dependencies?,
force: args.force?,
quiet: args.quiet?,
)
end
Cask::Upgrade.upgrade_casks( return if installed_formulae.empty?
*installed_casks,
force: args.force?, Install.perform_preinstall_checks(cc: args.cc)
dry_run: args.dry_run?,
binaries: args.binaries?, Install.install_formulae(
quarantine: args.quarantine?, installed_formulae,
require_sha: args.require_sha?, build_bottle: args.build_bottle?,
skip_cask_deps: args.skip_cask_deps?, force_bottle: args.force_bottle?,
verbose: args.verbose?, bottle_arch: args.bottle_arch,
args:, ignore_deps: args.ignore_dependencies?,
only_deps: args.only_dependencies?,
include_test_formulae: args.include_test_formulae,
build_from_source_formulae: args.build_from_source_formulae,
cc: args.cc,
git: args.git?,
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?,
dry_run: args.dry_run?,
skip_post_install: args.skip_post_install?,
) )
Upgrade.check_installed_dependents(
installed_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?,
dry_run: args.dry_run?,
)
Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?)
rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e
# Need to rescue before `FormulaUnavailableError` (superclass of this)
# is handled, as searching for a formula doesn't make sense here (the
# formula was found, but there's a problem with its implementation).
$stderr.puts Utils::Backtrace.clean(e) if Homebrew::EnvConfig.developer?
ofail e.message
rescue FormulaOrCaskUnavailableError, Cask::CaskUnavailableError => e
Homebrew.failed = true
# formula name or cask token
name = case e
when FormulaOrCaskUnavailableError then e.name
when Cask::CaskUnavailableError then e.token
else T.absurd(e)
end
if name == "updog"
ofail "What's updog?"
return
end
opoo e
reason = MissingFormula.reason(name, silent: true)
if !args.cask? && reason
$stderr.puts reason
return
end
# We don't seem to get good search results when the tap is specified
# so we might as well return early.
return if name.include?("/")
require "search"
package_types = []
package_types << "formulae" unless args.cask?
package_types << "casks" unless args.formula?
ohai "Searching for similarly named #{package_types.join(" and ")}..."
# Don't treat formula/cask name as a regex
string_or_regex = name
all_formulae, all_casks = Search.search_names(string_or_regex, args)
if all_formulae.any?
ohai "Formulae", Formatter.columns(all_formulae)
first_formula = all_formulae.first.to_s
puts <<~EOS
To install #{first_formula}, run:
brew install #{first_formula}
EOS
end
puts if all_formulae.any? && all_casks.any?
if all_casks.any?
ohai "Casks", Formatter.columns(all_casks)
first_cask = all_casks.first.to_s
puts <<~EOS
To install #{first_cask}, run:
brew install --cask #{first_cask}
EOS
end
return if all_formulae.any? || all_casks.any?
odie "No #{package_types.join(" or ")} found for #{name}."
end end
end end
# if the user's flags will prevent bottle only-installations when no
# developer tools are available, we need to stop them early on
build_flags = []
unless DevelopmentTools.installed?
build_flags << "--HEAD" if args.HEAD?
build_flags << "--build-bottle" if args.build_bottle?
build_flags << "--build-from-source" if args.build_from_source?
raise BuildFlagsError.new(build_flags, bottled: formulae.all?(&:bottled?)) if build_flags.present?
end
if build_flags.present? && !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
installed_formulae = formulae.select do |f|
Install.install_formula?(
f,
head: args.HEAD?,
fetch_head: args.fetch_HEAD?,
only_dependencies: args.only_dependencies?,
force: args.force?,
quiet: args.quiet?,
)
end
return if installed_formulae.empty?
Install.perform_preinstall_checks(cc: args.cc)
Install.install_formulae(
installed_formulae,
build_bottle: args.build_bottle?,
force_bottle: args.force_bottle?,
bottle_arch: args.bottle_arch,
ignore_deps: args.ignore_dependencies?,
only_deps: args.only_dependencies?,
include_test_formulae: args.include_test_formulae,
build_from_source_formulae: args.build_from_source_formulae,
cc: args.cc,
git: args.git?,
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?,
dry_run: args.dry_run?,
skip_post_install: args.skip_post_install?,
)
Upgrade.check_installed_dependents(
installed_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?,
dry_run: args.dry_run?,
)
Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?)
rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e
# Need to rescue before `FormulaUnavailableError` (superclass of this)
# is handled, as searching for a formula doesn't make sense here (the
# formula was found, but there's a problem with its implementation).
$stderr.puts Utils::Backtrace.clean(e) if Homebrew::EnvConfig.developer?
ofail e.message
rescue FormulaOrCaskUnavailableError, Cask::CaskUnavailableError => e
Homebrew.failed = true
# formula name or cask token
name = case e
when FormulaOrCaskUnavailableError then e.name
when Cask::CaskUnavailableError then e.token
else T.absurd(e)
end
if name == "updog"
ofail "What's updog?"
return
end
opoo e
reason = MissingFormula.reason(name, silent: true)
if !args.cask? && reason
$stderr.puts reason
return
end
# We don't seem to get good search results when the tap is specified
# so we might as well return early.
return if name.include?("/")
require "search"
package_types = []
package_types << "formulae" unless args.cask?
package_types << "casks" unless args.formula?
ohai "Searching for similarly named #{package_types.join(" and ")}..."
# Don't treat formula/cask name as a regex
string_or_regex = name
all_formulae, all_casks = Search.search_names(string_or_regex, args)
if all_formulae.any?
ohai "Formulae", Formatter.columns(all_formulae)
first_formula = all_formulae.first.to_s
puts <<~EOS
To install #{first_formula}, run:
brew install #{first_formula}
EOS
end
puts if all_formulae.any? && all_casks.any?
if all_casks.any?
ohai "Casks", Formatter.columns(all_casks)
first_cask = all_casks.first.to_s
puts <<~EOS
To install #{first_cask}, run:
brew install --cask #{first_cask}
EOS
end
return if all_formulae.any? || all_casks.any?
odie "No #{package_types.join(" or ")} found for #{name}."
end end
end end

View File

@ -1,51 +1,51 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "cask_dependent" require "cask_dependent"
require "cli/parser"
module Homebrew module Homebrew
module_function module Cmd
class Leaves < AbstractCommand
cmd_args do
description <<~EOS
List installed formulae that are not dependencies of another installed formula or cask.
EOS
switch "-r", "--installed-on-request",
description: "Only list leaves that were manually installed."
switch "-p", "--installed-as-dependency",
description: "Only list leaves that were installed as dependencies."
sig { returns(CLI::Parser) } conflicts "--installed-on-request", "--installed-as-dependency"
def leaves_args
Homebrew::CLI::Parser.new do
description <<~EOS
List installed formulae that are not dependencies of another installed formula or cask.
EOS
switch "-r", "--installed-on-request",
description: "Only list leaves that were manually installed."
switch "-p", "--installed-as-dependency",
description: "Only list leaves that were installed as dependencies."
conflicts "--installed-on-request", "--installed-as-dependency" named_args :none
end
named_args :none sig { override.void }
def run
leaves_list = Formula.installed - Formula.installed.flat_map(&:runtime_formula_dependencies)
casks_runtime_dependencies = Cask::Caskroom.casks.flat_map do |cask|
CaskDependent.new(cask).runtime_dependencies.map(&:to_formula)
end
leaves_list -= casks_runtime_dependencies
leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request?
leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency?
leaves_list.map(&:full_name)
.sort
.each(&method(:puts))
end
private
def installed_on_request?(formula)
Tab.for_keg(formula.any_installed_keg).installed_on_request
end
def installed_as_dependency?(formula)
Tab.for_keg(formula.any_installed_keg).installed_as_dependency
end
end end
end end
def installed_on_request?(formula)
Tab.for_keg(formula.any_installed_keg).installed_on_request
end
def installed_as_dependency?(formula)
Tab.for_keg(formula.any_installed_keg).installed_as_dependency
end
def leaves
args = leaves_args.parse
leaves_list = Formula.installed - Formula.installed.flat_map(&:runtime_formula_dependencies)
casks_runtime_dependencies = Cask::Caskroom.casks.flat_map do |cask|
CaskDependent.new(cask).runtime_dependencies.map(&:to_formula)
end
leaves_list -= casks_runtime_dependencies
leaves_list.select!(&method(:installed_on_request?)) if args.installed_on_request?
leaves_list.select!(&method(:installed_as_dependency?)) if args.installed_as_dependency?
leaves_list.map(&:full_name)
.sort
.each(&method(:puts))
end
end end

View File

@ -1,135 +1,136 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "caveats" require "caveats"
require "cli/parser"
require "unlink" require "unlink"
module Homebrew module Homebrew
module_function module Cmd
class Link < AbstractCommand
sig { returns(CLI::Parser) } cmd_args do
def link_args description <<~EOS
Homebrew::CLI::Parser.new do Symlink all of <formula>'s installed files into Homebrew's prefix.
description <<~EOS This is done automatically when you install formulae but can be useful
Symlink all of <formula>'s installed files into Homebrew's prefix. for manual installations.
This is done automatically when you install formulae but can be useful
for manual installations.
EOS
switch "--overwrite",
description: "Delete files that already exist in the prefix while linking."
switch "-n", "--dry-run",
description: "List files which would be linked or deleted by " \
"`brew link --overwrite` without actually linking or deleting any files."
switch "-f", "--force",
description: "Allow keg-only formulae to be linked."
switch "--HEAD",
description: "Link the HEAD version of the formula if it is installed."
named_args :installed_formula, min: 1
end
end
def link
args = link_args.parse
options = {
overwrite: args.overwrite?,
dry_run: args.dry_run?,
verbose: args.verbose?,
}
kegs = if args.HEAD?
args.named.to_kegs.group_by(&:name).filter_map do |name, resolved_kegs|
head_keg = resolved_kegs.find { |keg| keg.version.head? }
next head_keg if head_keg.present?
opoo <<~EOS
No HEAD keg installed for #{name}
To install, run:
brew install --HEAD #{name}
EOS EOS
end switch "--overwrite",
else description: "Delete files that already exist in the prefix while linking."
args.named.to_latest_kegs switch "-n", "--dry-run",
end description: "List files which would be linked or deleted by " \
"`brew link --overwrite` without actually linking or deleting any files."
switch "-f", "--force",
description: "Allow keg-only formulae to be linked."
switch "--HEAD",
description: "Link the HEAD version of the formula if it is installed."
kegs.freeze.each do |keg| named_args :installed_formula, min: 1
keg_only = Formulary.keg_only?(keg.rack)
if keg.linked?
opoo "Already linked: #{keg}"
name_and_flag = "#{"--HEAD " if args.HEAD?}#{"--force " if keg_only}#{keg.name}"
puts <<~EOS
To relink, run:
brew unlink #{keg.name} && brew link #{name_and_flag}
EOS
next
end end
if args.dry_run? sig { override.void }
if args.overwrite? def run
puts "Would remove:" options = {
overwrite: args.overwrite?,
dry_run: args.dry_run?,
verbose: args.verbose?,
}
kegs = if args.HEAD?
args.named.to_kegs.group_by(&:name).filter_map do |name, resolved_kegs|
head_keg = resolved_kegs.find { |keg| keg.version.head? }
next head_keg if head_keg.present?
opoo <<~EOS
No HEAD keg installed for #{name}
To install, run:
brew install --HEAD #{name}
EOS
end
else else
puts "Would link:" args.named.to_latest_kegs
end
keg.link(**options)
puts_keg_only_path_message(keg) if keg_only
next
end
formula = begin
keg.to_formula
rescue FormulaUnavailableError
# Not all kegs may belong to formulae
nil
end
if keg_only
if HOMEBREW_PREFIX.to_s == HOMEBREW_DEFAULT_PREFIX && formula.present? && formula.keg_only_reason.by_macos?
caveats = Caveats.new(formula)
opoo <<~EOS
Refusing to link macOS provided/shadowed software: #{keg.name}
#{caveats.keg_only_text(skip_reason: true).strip}
EOS
next
end end
if !args.force? && (formula.blank? || !formula.keg_only_reason.versioned_formula?) kegs.freeze.each do |keg|
opoo "#{keg.name} is keg-only and must be linked with `--force`." keg_only = Formulary.keg_only?(keg.rack)
puts_keg_only_path_message(keg)
next if keg.linked?
opoo "Already linked: #{keg}"
name_and_flag = "#{"--HEAD " if args.HEAD?}#{"--force " if keg_only}#{keg.name}"
puts <<~EOS
To relink, run:
brew unlink #{keg.name} && brew link #{name_and_flag}
EOS
next
end
if args.dry_run?
if args.overwrite?
puts "Would remove:"
else
puts "Would link:"
end
keg.link(**options)
puts_keg_only_path_message(keg) if keg_only
next
end
formula = begin
keg.to_formula
rescue FormulaUnavailableError
# Not all kegs may belong to formulae
nil
end
if keg_only
if HOMEBREW_PREFIX.to_s == HOMEBREW_DEFAULT_PREFIX && formula.present? &&
formula.keg_only_reason.by_macos?
caveats = Caveats.new(formula)
opoo <<~EOS
Refusing to link macOS provided/shadowed software: #{keg.name}
#{caveats.keg_only_text(skip_reason: true).strip}
EOS
next
end
if !args.force? && (formula.blank? || !formula.keg_only_reason.versioned_formula?)
opoo "#{keg.name} is keg-only and must be linked with `--force`."
puts_keg_only_path_message(keg)
next
end
end
Unlink.unlink_versioned_formulae(formula, verbose: args.verbose?) if formula
keg.lock do
print "Linking #{keg}... "
puts if args.verbose?
begin
n = keg.link(**options)
rescue Keg::LinkError
puts
raise
else
puts "#{n} symlinks created."
end
puts_keg_only_path_message(keg) if keg_only && !Homebrew::EnvConfig.developer?
end
end end
end end
Unlink.unlink_versioned_formulae(formula, verbose: args.verbose?) if formula private
keg.lock do def puts_keg_only_path_message(keg)
print "Linking #{keg}... " bin = keg/"bin"
puts if args.verbose? sbin = keg/"sbin"
return if !bin.directory? && !sbin.directory?
begin opt = HOMEBREW_PREFIX/"opt/#{keg.name}"
n = keg.link(**options) puts "\nIf you need to have this software first in your PATH instead consider running:"
rescue Keg::LinkError puts " #{Utils::Shell.prepend_path_in_profile(opt/"bin")}" if bin.directory?
puts puts " #{Utils::Shell.prepend_path_in_profile(opt/"sbin")}" if sbin.directory?
raise
else
puts "#{n} symlinks created."
end
puts_keg_only_path_message(keg) if keg_only && !Homebrew::EnvConfig.developer?
end end
end end
end end
def puts_keg_only_path_message(keg)
bin = keg/"bin"
sbin = keg/"sbin"
return if !bin.directory? && !sbin.directory?
opt = HOMEBREW_PREFIX/"opt/#{keg.name}"
puts "\nIf you need to have this software first in your PATH instead consider running:"
puts " #{Utils::Shell.prepend_path_in_profile(opt/"bin")}" if bin.directory?
puts " #{Utils::Shell.prepend_path_in_profile(opt/"sbin")}" if sbin.directory?
end
end end

View File

@ -177,7 +177,10 @@ module Commands
external_commands_file.atomic_write("#{external_commands.sort.join("\n")}\n") external_commands_file.atomic_write("#{external_commands.sort.join("\n")}\n")
end end
sig { params(command: String).returns(T.nilable(T::Array[[String, String]])) }
def self.command_options(command) def self.command_options(command)
return if command == "help"
path = self.path(command) path = self.path(command)
return if path.blank? return if path.blank?
@ -207,7 +210,7 @@ module Commands
if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path)) if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
if short if short
cmd_parser.description.split(DESCRIPTION_SPLITTING_PATTERN).first cmd_parser.description&.split(DESCRIPTION_SPLITTING_PATTERN)&.first
else else
cmd_parser.description cmd_parser.description
end end

View File

@ -15,7 +15,6 @@ require "style"
require "date" require "date"
require "missing_formula" require "missing_formula"
require "digest" require "digest"
require "cli/parser"
require "json" require "json"
require "formula_auditor" require "formula_auditor"
require "tap_auditor" require "tap_auditor"

View File

@ -8,7 +8,6 @@ require "utils/bottles"
require "tab" require "tab"
require "keg" require "keg"
require "formula_versions" require "formula_versions"
require "cli/parser"
require "utils/inreplace" require "utils/inreplace"
require "erb" require "erb"
require "utils/gzip" require "utils/gzip"
@ -108,6 +107,110 @@ module Homebrew
end end
end end
def generate_sha256_line(tag, digest, cellar, tag_column, digest_column)
line = "sha256 "
tag_column += line.length
digest_column += line.length
if cellar.is_a?(Symbol)
line += "cellar: :#{cellar},"
elsif cellar_parameter_needed?(cellar)
line += %Q(cellar: "#{cellar}",)
end
line += " " * (tag_column - line.length)
line += "#{tag}:"
line += " " * (digest_column - line.length)
%Q(#{line}"#{digest}")
end
def bottle_output(bottle, root_url_using)
cellars = bottle.checksums.filter_map do |checksum|
cellar = checksum["cellar"]
next unless cellar_parameter_needed? cellar
case cellar
when String
%Q("#{cellar}")
when Symbol
":#{cellar}"
end
end
tag_column = cellars.empty? ? 0 : "cellar: #{cellars.max_by(&:length)}, ".length
tags = bottle.checksums.map { |checksum| checksum["tag"] }
# Start where the tag ends, add the max length of the tag, add two for the `: `
digest_column = tag_column + tags.max_by(&:length).length + 2
sha256_lines = bottle.checksums.map do |checksum|
generate_sha256_line(checksum["tag"], checksum["digest"], checksum["cellar"], tag_column, digest_column)
end
erb_binding = bottle.instance_eval { binding }
erb_binding.local_variable_set(:sha256_lines, sha256_lines)
erb_binding.local_variable_set(:root_url_using, root_url_using)
erb = ERB.new BOTTLE_ERB
erb.result(erb_binding).gsub(/^\s*$\n/, "")
end
def parse_json_files(filenames)
filenames.map do |filename|
JSON.parse(File.read(filename))
end
end
def merge_json_files(json_files)
json_files.reduce({}) do |hash, json_file|
json_file.each_value do |json_hash|
json_bottle = json_hash["bottle"]
cellar = json_bottle.delete("cellar")
json_bottle["tags"].each_value do |json_platform|
json_platform["cellar"] ||= cellar
end
end
hash.deep_merge(json_file)
end
end
def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
mismatches = []
checksums = []
new_values = {
root_url: new_bottle_hash["root_url"],
rebuild: new_bottle_hash["rebuild"],
}
skip_keys = [:sha256, :cellar]
old_keys.each do |key|
next if skip_keys.include?(key)
old_value = old_bottle_spec.send(key).to_s
new_value = new_values[key].to_s
next if old_value.present? && new_value == old_value
mismatches << "#{key}: old: #{old_value.inspect}, new: #{new_value.inspect}"
end
return [mismatches, checksums] if old_keys.exclude? :sha256
old_bottle_spec.collector.each_tag do |tag|
old_tag_spec = old_bottle_spec.collector.specification_for(tag)
old_hexdigest = old_tag_spec.checksum.hexdigest
old_cellar = old_tag_spec.cellar
new_value = new_bottle_hash.dig("tags", tag.to_s)
if new_value.present? && new_value["sha256"] != old_hexdigest
mismatches << "sha256 #{tag}: old: #{old_hexdigest.inspect}, new: #{new_value["sha256"].inspect}"
elsif new_value.present? && new_value["cellar"] != old_cellar.to_s
mismatches << "cellar #{tag}: old: #{old_cellar.to_s.inspect}, new: #{new_value["cellar"].inspect}"
else
checksums << { cellar: old_cellar, tag.to_sym => old_hexdigest }
end
end
[mismatches, checksums]
end
private
def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil) def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil)
@put_string_exists_header, @put_filenames = nil @put_string_exists_header, @put_filenames = nil
@ -186,49 +289,6 @@ module Homebrew
cellar.present? && default_cellars.exclude?(cellar) cellar.present? && default_cellars.exclude?(cellar)
end end
def generate_sha256_line(tag, digest, cellar, tag_column, digest_column)
line = "sha256 "
tag_column += line.length
digest_column += line.length
if cellar.is_a?(Symbol)
line += "cellar: :#{cellar},"
elsif cellar_parameter_needed?(cellar)
line += %Q(cellar: "#{cellar}",)
end
line += " " * (tag_column - line.length)
line += "#{tag}:"
line += " " * (digest_column - line.length)
%Q(#{line}"#{digest}")
end
def bottle_output(bottle, root_url_using)
cellars = bottle.checksums.filter_map do |checksum|
cellar = checksum["cellar"]
next unless cellar_parameter_needed? cellar
case cellar
when String
%Q("#{cellar}")
when Symbol
":#{cellar}"
end
end
tag_column = cellars.empty? ? 0 : "cellar: #{cellars.max_by(&:length)}, ".length
tags = bottle.checksums.map { |checksum| checksum["tag"] }
# Start where the tag ends, add the max length of the tag, add two for the `: `
digest_column = tag_column + tags.max_by(&:length).length + 2
sha256_lines = bottle.checksums.map do |checksum|
generate_sha256_line(checksum["tag"], checksum["digest"], checksum["cellar"], tag_column, digest_column)
end
erb_binding = bottle.instance_eval { binding }
erb_binding.local_variable_set(:sha256_lines, sha256_lines)
erb_binding.local_variable_set(:root_url_using, root_url_using)
erb = ERB.new BOTTLE_ERB
erb.result(erb_binding).gsub(/^\s*$\n/, "")
end
def sudo_purge def sudo_purge
return unless ENV["HOMEBREW_BOTTLE_SUDO_PURGE"] return unless ENV["HOMEBREW_BOTTLE_SUDO_PURGE"]
@ -601,25 +661,6 @@ module Homebrew
json_path.write(JSON.pretty_generate(json)) json_path.write(JSON.pretty_generate(json))
end end
def parse_json_files(filenames)
filenames.map do |filename|
JSON.parse(File.read(filename))
end
end
def merge_json_files(json_files)
json_files.reduce({}) do |hash, json_file|
json_file.each_value do |json_hash|
json_bottle = json_hash["bottle"]
cellar = json_bottle.delete("cellar")
json_bottle["tags"].each_value do |json_platform|
json_platform["cellar"] ||= cellar
end
end
hash.deep_merge(json_file)
end
end
def merge def merge
bottles_hash = merge_json_files(parse_json_files(args.named)) bottles_hash = merge_json_files(parse_json_files(args.named))
@ -774,46 +815,6 @@ module Homebrew
end end
end end
def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash)
mismatches = []
checksums = []
new_values = {
root_url: new_bottle_hash["root_url"],
rebuild: new_bottle_hash["rebuild"],
}
skip_keys = [:sha256, :cellar]
old_keys.each do |key|
next if skip_keys.include?(key)
old_value = old_bottle_spec.send(key).to_s
new_value = new_values[key].to_s
next if old_value.present? && new_value == old_value
mismatches << "#{key}: old: #{old_value.inspect}, new: #{new_value.inspect}"
end
return [mismatches, checksums] if old_keys.exclude? :sha256
old_bottle_spec.collector.each_tag do |tag|
old_tag_spec = old_bottle_spec.collector.specification_for(tag)
old_hexdigest = old_tag_spec.checksum.hexdigest
old_cellar = old_tag_spec.cellar
new_value = new_bottle_hash.dig("tags", tag.to_s)
if new_value.present? && new_value["sha256"] != old_hexdigest
mismatches << "sha256 #{tag}: old: #{old_hexdigest.inspect}, new: #{new_value["sha256"].inspect}"
elsif new_value.present? && new_value["cellar"] != old_cellar.to_s
mismatches << "cellar #{tag}: old: #{old_cellar.to_s.inspect}, new: #{new_value["cellar"].inspect}"
else
checksums << { cellar: old_cellar, tag.to_sym => old_hexdigest }
end
end
[mismatches, checksums]
end
def old_checksums(formula, formula_ast, bottle_hash) def old_checksums(formula, formula_ast, bottle_hash)
bottle_node = formula_ast.bottle_block bottle_node = formula_ast.bottle_block
return if bottle_node.nil? return if bottle_node.nil?

View File

@ -5,7 +5,6 @@ require "abstract_command"
require "bump_version_parser" require "bump_version_parser"
require "cask" require "cask"
require "cask/download" require "cask/download"
require "cli/parser"
require "utils/tar" require "utils/tar"
module Homebrew module Homebrew

View File

@ -4,7 +4,6 @@
require "abstract_command" require "abstract_command"
require "fileutils" require "fileutils"
require "formula" require "formula"
require "cli/parser"
require "utils/pypi" require "utils/pypi"
require "utils/tar" require "utils/tar"

View File

@ -3,7 +3,6 @@
require "abstract_command" require "abstract_command"
require "formula" require "formula"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -5,7 +5,6 @@ require "timeout"
require "cask/download" require "cask/download"
require "cask/installer" require "cask/installer"
require "cask/cask_loader" require "cask/cask_loader"
require "cli/parser"
require "system_command" require "system_command"
require "tap" require "tap"
require "unversioned_cask_checker" require "unversioned_cask_checker"

View File

@ -3,7 +3,6 @@
require "abstract_command" require "abstract_command"
require "bump_version_parser" require "bump_version_parser"
require "cli/parser"
require "livecheck/livecheck" require "livecheck/livecheck"
module Homebrew module Homebrew

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "fileutils" require "fileutils"
module Homebrew module Homebrew

View File

@ -3,7 +3,6 @@
require "abstract_command" require "abstract_command"
require "commands" require "commands"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "csv" require "csv"
module Homebrew module Homebrew

View File

@ -4,7 +4,6 @@
require "formula" require "formula"
require "formula_creator" require "formula_creator"
require "missing_formula" require "missing_formula"
require "cli/parser"
require "utils/pypi" require "utils/pypi"
require "cask/cask_loader" require "cask/cask_loader"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "test_runner_formula" require "test_runner_formula"
require "github_runner_matrix" require "github_runner_matrix"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/github" require "utils/github"
module Homebrew module Homebrew

View File

@ -3,7 +3,6 @@
require "abstract_command" require "abstract_command"
require "formula" require "formula"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/git" require "utils/git"
require "formulary" require "formulary"
require "software_spec" require "software_spec"

View File

@ -3,7 +3,6 @@
require "abstract_command" require "abstract_command"
require "formula" require "formula"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "cask/cask" require "cask/cask"
require "fileutils" require "fileutils"
require "formula" require "formula"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "fileutils" require "fileutils"
require "formula" require "formula"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -4,7 +4,6 @@
require "abstract_command" require "abstract_command"
require "formulary" require "formulary"
require "cask/cask_loader" require "cask/cask_loader"
require "cli/parser"
class String class String
def f(*args) def f(*args)

View File

@ -4,7 +4,6 @@
require "abstract_command" require "abstract_command"
require "cache_store" require "cache_store"
require "linkage_checker" require "linkage_checker"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "formula" require "formula"
require "livecheck/livecheck" require "livecheck/livecheck"
require "livecheck/strategy" require "livecheck/strategy"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/github" require "utils/github"
module Homebrew module Homebrew

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/github" require "utils/github"
module Homebrew module Homebrew

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "fileutils" require "fileutils"
require "utils/github" require "utils/github"
require "utils/github/artifacts" require "utils/github/artifacts"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "formula" require "formula"
require "github_packages" require "github_packages"
require "github_releases" require "github_releases"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -4,7 +4,6 @@
require "abstract_command" require "abstract_command"
require "extend/ENV" require "extend/ENV"
require "formula" require "formula"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -5,7 +5,6 @@ require "abstract_command"
require "json" require "json"
require "open3" require "open3"
require "style" require "style"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -4,7 +4,6 @@
require "abstract_command" require "abstract_command"
require "fileutils" require "fileutils"
require "tap" require "tap"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -5,7 +5,6 @@ require "abstract_command"
require "extend/ENV" require "extend/ENV"
require "sandbox" require "sandbox"
require "timeout" require "timeout"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "fileutils" require "fileutils"
require "system_command" require "system_command"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "fileutils" require "fileutils"
module Homebrew module Homebrew

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "formula" require "formula"
require "api" require "api"
require "os/mac/xcode" require "os/mac/xcode"

View File

@ -5,7 +5,6 @@ require "abstract_command"
require "fileutils" require "fileutils"
require "stringio" require "stringio"
require "formula" require "formula"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/spdx" require "utils/spdx"
require "system_command" require "system_command"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/github" require "utils/github"
require "manpages" require "manpages"
require "system_command" require "system_command"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/pypi" require "utils/pypi"
module Homebrew module Homebrew

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "utils/github" require "utils/github"
require "system_command" require "system_command"

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
require "fileutils" require "fileutils"
module Homebrew module Homebrew

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command" require "abstract_command"
require "cli/parser"
module Homebrew module Homebrew
module DevCmd module DevCmd

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/analytics"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew analytics" do RSpec.describe Homebrew::Cmd::Analytics do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "when HOMEBREW_NO_ANALYTICS is unset is disabled after running `brew analytics off`", :integration_test do it "when HOMEBREW_NO_ANALYTICS is unset is disabled after running `brew analytics off`", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/autoremove"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew autoremove" do RSpec.describe Homebrew::Cmd::Autoremove do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
describe "integration test" do describe "integration test" do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/cleanup"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew cleanup" do RSpec.describe Homebrew::Cmd::CleanupCmd do
before do before do
FileUtils.mkdir_p HOMEBREW_LIBRARY/"Homebrew/vendor/" FileUtils.mkdir_p HOMEBREW_LIBRARY/"Homebrew/vendor/"
FileUtils.touch HOMEBREW_LIBRARY/"Homebrew/vendor/portable-ruby-version" FileUtils.touch HOMEBREW_LIBRARY/"Homebrew/vendor/portable-ruby-version"

View File

@ -1,11 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/commands" require "cmd/commands"
require "fileutils"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew commands" do RSpec.describe Homebrew::Cmd::CommandsCmd do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "prints a list of all available commands", :integration_test do it "prints a list of all available commands", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/completions"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew completions" do RSpec.describe Homebrew::Cmd::CompletionsCmd do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "runs the status subcommand correctly", :integration_test do it "runs the status subcommand correctly", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/config"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew config" do RSpec.describe Homebrew::Cmd::Config do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "prints information about the current Homebrew configuration", :integration_test do it "prints information about the current Homebrew configuration", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/deps"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew deps" do RSpec.describe Homebrew::Cmd::Deps do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "outputs all of a Formula's dependencies and their dependencies on separate lines", :integration_test do it "outputs all of a Formula's dependencies and their dependencies on separate lines", :integration_test do

View File

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

View File

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

View File

@ -1,6 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe "brew docs" do require "cmd/docs"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::Docs do
it_behaves_like "parseable arguments"
it "opens the docs page", :integration_test do it "opens the docs page", :integration_test do
expect { brew "docs", "HOMEBREW_BROWSER" => "echo" } expect { brew "docs", "HOMEBREW_BROWSER" => "echo" }
.to output("https://docs.brew.sh\n").to_stdout .to output("https://docs.brew.sh\n").to_stdout

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/doctor"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew doctor" do RSpec.describe Homebrew::Cmd::Doctor do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
specify "check_integration_test", :integration_test do specify "check_integration_test", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/fetch"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew fetch" do RSpec.describe Homebrew::Cmd::FetchCmd do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "downloads the Formula's URL", :integration_test do it "downloads the Formula's URL", :integration_test do

View File

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

View File

@ -1,6 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe "brew", :integration_test do require "cmd/help"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::Cmd::HelpCmd, :integration_test do
it_behaves_like "parseable arguments"
describe "help" do describe "help" do
it "prints help for a documented Ruby command" do it "prints help for a documented Ruby command" do
expect { brew "help", "cat" } expect { brew "help", "cat" }

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/home"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew home" do RSpec.describe Homebrew::Cmd::Home do
let(:testballhome_homepage) do let(:testballhome_homepage) do
Formula["testballhome"].homepage Formula["testballhome"].homepage
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/info" require "cmd/info"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew info" do RSpec.describe Homebrew::Cmd::Info do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "prints as json with the --json=v1 flag", :integration_test do it "prints as json with the --json=v1 flag", :integration_test do
@ -25,23 +24,21 @@ RSpec.describe "brew info" do
.and be_a_success .and be_a_success
end end
describe Homebrew do describe "::github_remote_path" do
describe "::github_remote_path" do let(:remote) { "https://github.com/Homebrew/homebrew-core" }
let(:remote) { "https://github.com/Homebrew/homebrew-core" }
specify "returns correct URLs" do specify "returns correct URLs" do
expect(described_class.github_remote_path(remote, "Formula/git.rb")) expect(described_class.new([]).github_remote_path(remote, "Formula/git.rb"))
.to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/git.rb") .to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/git.rb")
expect(described_class.github_remote_path("#{remote}.git", "Formula/git.rb")) expect(described_class.new([]).github_remote_path("#{remote}.git", "Formula/git.rb"))
.to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/git.rb") .to eq("https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/git.rb")
expect(described_class.github_remote_path("git@github.com:user/repo", "foo.rb")) expect(described_class.new([]).github_remote_path("git@github.com:user/repo", "foo.rb"))
.to eq("https://github.com/user/repo/blob/HEAD/foo.rb") .to eq("https://github.com/user/repo/blob/HEAD/foo.rb")
expect(described_class.github_remote_path("https://mywebsite.com", "foo/bar.rb")) expect(described_class.new([]).github_remote_path("https://mywebsite.com", "foo/bar.rb"))
.to eq("https://mywebsite.com/foo/bar.rb") .to eq("https://mywebsite.com/foo/bar.rb")
end
end end
end end
end end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/install"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew install" do RSpec.describe Homebrew::Cmd::InstallCmd do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "installs formulae", :integration_test do it "installs formulae", :integration_test do

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/leaves"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew leaves" do RSpec.describe Homebrew::Cmd::Leaves do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
context "when there are no installed Formulae", :integration_test do context "when there are no installed Formulae", :integration_test do

View File

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

View File

@ -70,7 +70,7 @@ RSpec.describe FormulaInstaller do
# rubocop:disable RSpec/NoExpectationExample # rubocop:disable RSpec/NoExpectationExample
specify "basic bottle install" do specify "basic bottle install" do
allow(DevelopmentTools).to receive(:installed?).and_return(false) allow(DevelopmentTools).to receive(:installed?).and_return(false)
Homebrew.install_args.parse(["testball_bottle"]) Homebrew::Cmd::InstallCmd.new(["testball_bottle"])
temporarily_install_bottle(TestballBottle.new) do |f| temporarily_install_bottle(TestballBottle.new) do |f|
test_basic_formula_setup(f) test_basic_formula_setup(f)
end end
@ -79,7 +79,7 @@ RSpec.describe FormulaInstaller do
specify "basic bottle install with cellar information on sha256 line" do specify "basic bottle install with cellar information on sha256 line" do
allow(DevelopmentTools).to receive(:installed?).and_return(false) allow(DevelopmentTools).to receive(:installed?).and_return(false)
Homebrew.install_args.parse(["testball_bottle_cellar"]) Homebrew::Cmd::InstallCmd.new(["testball_bottle_cellar"])
temporarily_install_bottle(TestballBottleCellar.new) do |f| temporarily_install_bottle(TestballBottleCellar.new) do |f|
test_basic_formula_setup(f) test_basic_formula_setup(f)