Merge pull request #5619 from GauthamGoli/install-args

install: Use CLI::Parser to parse args
This commit is contained in:
Mike McQuaid 2019-01-29 14:11:22 +00:00 committed by GitHub
commit 618f75c071
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 289 additions and 247 deletions

View File

@ -99,22 +99,6 @@ class BuildOptions
@options - @args
end
# @private
def invalid_options
@args - @options - BuildOptions.formula_install_options
end
# @private
def invalid_option_names
invalid_options.map(&:flag).sort
end
def self.formula_install_options
@formula_install_options ||= ARGV.formula_install_option_names.map do |option_name|
Option.new option_name[2..-1]
end
end
private
def option_defined?(name)

View File

@ -2,7 +2,7 @@
#: Upload logs for a failed build of <formula> to a new Gist.
#:
#: <formula> is usually the name of the formula to install, but it can be specified
#: in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
#: in several different ways.
#:
#: If `--with-hostname` is passed, include the hostname in the Gist.
#:
@ -32,7 +32,7 @@ module Homebrew
Upload logs for a failed build of <formula> to a new Gist.
<formula> is usually the name of the formula to install, but it can be specified
in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
in several different ways.
If no logs are found, an error message is presented.
EOS

View File

@ -2,7 +2,7 @@
#: Install <formula>.
#:
#: <formula> is usually the name of the formula to install, but it can be specified
#: in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
#: in several different ways.
#:
#: If `--debug` (or `-d`) is passed and brewing fails, open an interactive debugging
#: session with access to IRB or a shell inside the temporary build directory.
@ -75,20 +75,98 @@ require "development_tools"
require "install"
require "search"
require "cleanup"
require "cli_parser"
module Homebrew
module_function
extend Search
def install
raise FormulaUnspecifiedError if ARGV.named.empty?
def install_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`install` [<options>] formula
if ARGV.include? "--head"
raise "Specify `--HEAD` in uppercase to build from trunk."
Install <formula>.
<formula> is usually the name of the formula to install, but it can be specified
in several different ways.
EOS
switch :debug,
description: "If brewing fails, open an interactive debugging session with access to IRB "\
"or a shell inside the temporary build directory"
flag "--env=",
description: "If `std` is passed, use the standard build environment instead of superenv."\
"If `super` is passed, use superenv even if the formula specifies the "\
"standard build environment."
switch "--ignore-dependencies",
description: "Skip installing any dependencies of any kind. If they are not already "\
"present, the formula will probably fail to install."
switch "--only-dependencies",
description: "Install the dependencies with specified options but do not install the "\
"specified formula."
flag "--cc=",
description: "Attempt to compile using provided <compiler>. <compiler> should be the "\
"name of the compiler's executable, for instance `gcc-7` for GCC 7. "\
"In order to use LLVM's clang, use `llvm_clang`. To specify the "\
"Apple-provided clang, use `clang`. This parameter will only accept "\
"compilers that are provided by Homebrew or bundled with macOS. "\
"Please do not file issues if you encounter errors while using this flag."
switch "-s", "--build-from-source",
description: "Compile the specified <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 "\
"macOS, even if it would not normally be used for installation."
switch "--include-test",
description: "Install testing dependencies required to run `brew test`."
switch "--devel",
description: "If <formula> defines it, install the development version."
switch "--HEAD",
description: "If <formula> defines it, install the HEAD version, aka master, trunk, unstable."
switch "--fetch-HEAD",
description: "Fetch the upstream repository to detect if the HEAD installation of the "\
"formula is outdated. Otherwise, the repository's HEAD will be checked for "\
"updates when a new stable or devel version has been released."
switch "--keep-tmp",
description: "Dont delete the temporary files created during installation."
switch "--build-bottle",
description: "Prepare the formula for eventual bottling during installation."
switch :force,
description: "Install without checking for previously installed keg-only or "\
"non-migrated versions."
switch :verbose,
description: "Print the verification and postinstall steps."
switch "--display-times",
description: "Print install times for each formula at the end of the run."
switch "-i", "--interactive",
description: "Download and patch <formula>, then open a shell. This allows the user to "\
"run `./configure --help` and otherwise determine how to turn the software "\
"package into a Homebrew package."
switch "-g", "--git",
description: "Create a Git repository, useful for creating patches to the software."
ARGV.formulae.each do |f|
next if f.options.empty?
f.options.each do |o|
name = o.flag
description = "`#{f.name}`: #{o.description}"
if name.end_with? "="
flag name, description: description
else
switch name, description: description
end
end
end
end
end
unless ARGV.force?
def install
install_args.parse
raise FormulaUnspecifiedError if args.remaining.empty?
unless args.force?
ARGV.named.each do |name|
next if File.exist?(name)
if name !~ HOMEBREW_TAP_FORMULA_REGEX && name !~ HOMEBREW_CASK_TAP_CASK_REGEX
@ -100,210 +178,210 @@ module Homebrew
end
end
begin
formulae = []
formulae = []
unless ARGV.casks.empty?
args = []
args << "--force" if ARGV.force?
args << "--debug" if ARGV.debug?
args << "--verbose" if ARGV.verbose?
unless ARGV.casks.empty?
cask_args = []
cask_args << "--force" if args.force?
cask_args << "--debug" if args.debug?
cask_args << "--verbose" if args.verbose?
ARGV.casks.each do |c|
ohai "brew cask install #{c} #{args.join " "}"
system("#{HOMEBREW_PREFIX}/bin/brew", "cask", "install", c, *args)
end
ARGV.casks.each do |c|
ohai "brew cask install #{c} #{cask_args.join " "}"
system("#{HOMEBREW_PREFIX}/bin/brew", "cask", "install", c, *cask_args)
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
FormulaInstaller.prevent_build_flags unless DevelopmentTools.installed?
ARGV.formulae.each do |f|
# head-only without --HEAD is an error
if !Homebrew.args.HEAD? && f.stable.nil? && f.devel.nil?
raise <<~EOS
#{f.full_name} is a head-only formula
Install with `brew install --HEAD #{f.full_name}`
EOS
end
# if the user's flags will prevent bottle only-installations when no
# developer tools are available, we need to stop them early on
FormulaInstaller.prevent_build_flags unless DevelopmentTools.installed?
# devel-only without --devel is an error
if !args.devel? && f.stable.nil? && f.head.nil?
raise <<~EOS
#{f.full_name} is a devel-only formula
Install with `brew install --devel #{f.full_name}`
EOS
end
ARGV.formulae.each do |f|
# head-only without --HEAD is an error
if !ARGV.build_head? && f.stable.nil? && f.devel.nil?
raise <<~EOS
#{f.full_name} is a head-only formula
Install with `brew install --HEAD #{f.full_name}`
if !(args.HEAD? || args.devel?) && f.stable.nil?
raise "#{f.full_name} has no stable download, please choose --devel or --HEAD"
end
# --HEAD, fail with no head defined
if args.head? && f.head.nil?
raise "No head is defined for #{f.full_name}"
end
# --devel, fail with no devel defined
if args.devel? && f.devel.nil?
raise "No devel block is defined for #{f.full_name}"
end
installed_head_version = f.latest_head_version
if installed_head_version &&
!f.head_version_outdated?(installed_head_version, fetch_head: args.fetch_HEAD?)
new_head_installed = true
end
prefix_installed = f.prefix.exist? && !f.prefix.children.empty?
if f.keg_only? && f.any_version_installed? && f.optlinked? && !args.force?
# keg-only install is only possible when no other version is
# linked to opt, because installing without any warnings can break
# dependencies. Therefore before performing other checks we need to be
# sure --force flag is passed.
if f.outdated?
optlinked_version = Keg.for(f.opt_prefix).version
onoe <<~EOS
#{f.full_name} #{optlinked_version} is already installed
To upgrade to #{f.version}, run `brew upgrade #{f.name}`
EOS
end
# devel-only without --devel is an error
if !ARGV.build_devel? && f.stable.nil? && f.head.nil?
raise <<~EOS
#{f.full_name} is a devel-only formula
Install with `brew install --devel #{f.full_name}`
EOS
end
if ARGV.build_stable? && f.stable.nil?
raise "#{f.full_name} has no stable download, please choose --devel or --HEAD"
end
# --HEAD, fail with no head defined
if ARGV.build_head? && f.head.nil?
raise "No head is defined for #{f.full_name}"
end
# --devel, fail with no devel defined
if ARGV.build_devel? && f.devel.nil?
raise "No devel block is defined for #{f.full_name}"
end
installed_head_version = f.latest_head_version
new_head_installed = installed_head_version &&
!f.head_version_outdated?(installed_head_version, fetch_head: ARGV.fetch_head?)
prefix_installed = f.prefix.exist? && !f.prefix.children.empty?
if f.keg_only? && f.any_version_installed? && f.optlinked? && !ARGV.force?
# keg-only install is only possible when no other version is
# linked to opt, because installing without any warnings can break
# dependencies. Therefore before performing other checks we need to be
# sure --force flag is passed.
if f.outdated?
optlinked_version = Keg.for(f.opt_prefix).version
onoe <<~EOS
#{f.full_name} #{optlinked_version} is already installed
To upgrade to #{f.version}, run `brew upgrade #{f.name}`
EOS
elsif ARGV.only_deps?
formulae << f
else
opoo <<~EOS
#{f.full_name} #{f.pkg_version} is already installed and up-to-date
To reinstall #{f.pkg_version}, run `brew reinstall #{f.name}`
EOS
end
elsif (ARGV.build_head? && new_head_installed) || prefix_installed
# After we're sure that --force flag is passed for linked to opt
# keg-only we need to be sure that the version we're attempting to
# install is not already installed.
installed_version = if ARGV.build_head?
f.latest_head_version
else
f.pkg_version
end
msg = "#{f.full_name} #{installed_version} is already installed"
linked_not_equals_installed = f.linked_version != installed_version
if f.linked? && linked_not_equals_installed
msg = <<~EOS
#{msg}
The currently linked version is #{f.linked_version}
You can use `brew switch #{f} #{installed_version}` to link this version.
EOS
elsif !f.linked? || f.keg_only?
msg = <<~EOS
#{msg}, it's just not linked
You can use `brew link #{f}` to link this version.
EOS
elsif ARGV.only_deps?
msg = nil
formulae << f
else
msg = <<~EOS
#{msg} and up-to-date
To reinstall #{f.pkg_version}, run `brew reinstall #{f.name}`
EOS
end
opoo msg if msg
elsif !f.any_version_installed? && old_formula = f.old_installed_formulae.first
msg = "#{old_formula.full_name} #{old_formula.installed_version} already installed"
if !old_formula.linked? && !old_formula.keg_only?
msg = <<~EOS
#{msg}, it's just not linked.
You can use `brew link #{old_formula.full_name}` to link this version.
EOS
end
opoo msg
elsif f.migration_needed? && !ARGV.force?
# Check if the formula we try to install is the same as installed
# but not migrated one. If --force passed then install anyway.
opoo <<~EOS
#{f.oldname} already installed, it's just not migrated
You can migrate formula with `brew migrate #{f}`
Or you can force install it with `brew install #{f} --force`
EOS
else
# If none of the above is true and the formula is linked, then
# FormulaInstaller will handle this case.
elsif args.only_dependencies?
formulae << f
else
opoo <<~EOS
#{f.full_name} #{f.pkg_version} is already installed and up-to-date
To reinstall #{f.pkg_version}, run `brew reinstall #{f.name}`
EOS
end
elsif (args.HEAD? && new_head_installed) || prefix_installed
# After we're sure that --force flag is passed for linked to opt
# keg-only we need to be sure that the version we're attempting to
# install is not already installed.
installed_version = if args.HEAD?
f.latest_head_version
else
f.pkg_version
end
# Even if we don't install this formula mark it as no longer just
# installed as a dependency.
next unless f.opt_prefix.directory?
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
unless tab.installed_on_request
tab.installed_on_request = true
tab.write
msg = "#{f.full_name} #{installed_version} is already installed"
linked_not_equals_installed = f.linked_version != installed_version
if f.linked? && linked_not_equals_installed
msg = <<~EOS
#{msg}
The currently linked version is #{f.linked_version}
You can use `brew switch #{f} #{installed_version}` to link this version.
EOS
elsif !f.linked? || f.keg_only?
msg = <<~EOS
#{msg}, it's just not linked
You can use `brew link #{f}` to link this version.
EOS
elsif args.only_dependencies?
msg = nil
formulae << f
else
msg = <<~EOS
#{msg} and up-to-date
To reinstall #{f.pkg_version}, run `brew reinstall #{f.name}`
EOS
end
end
return if formulae.empty?
Install.perform_preinstall_checks
formulae.each do |f|
Migrator.migrate_if_needed(f)
install_formula(f)
Cleanup.install_formula_clean!(f)
end
Homebrew.messages.display_messages
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).
ofail e.message
rescue FormulaUnavailableError => e
if e.name == "updog"
ofail "What's updog?"
return
end
ofail e.message
if (reason = MissingFormula.reason(e.name))
$stderr.puts reason
return
end
ohai "Searching for similarly named formulae..."
formulae_search_results = search_formulae(e.name)
case formulae_search_results.length
when 0
ofail "No similarly named formulae found."
when 1
puts "This similarly named formula was found:"
puts formulae_search_results
puts "To install it, run:\n brew install #{formulae_search_results.first}"
opoo msg if msg
elsif !f.any_version_installed? && old_formula = f.old_installed_formulae.first
msg = "#{old_formula.full_name} #{old_formula.installed_version} already installed"
if !old_formula.linked? && !old_formula.keg_only?
msg = <<~EOS
#{msg}, it's just not linked.
You can use `brew link #{old_formula.full_name}` to link this version.
EOS
end
opoo msg
elsif f.migration_needed? && !args.force?
# Check if the formula we try to install is the same as installed
# but not migrated one. If --force passed then install anyway.
opoo <<~EOS
#{f.oldname} already installed, it's just not migrated
You can migrate formula with `brew migrate #{f}`
Or you can force install it with `brew install #{f} --force`
EOS
else
puts "These similarly named formulae were found:"
puts Formatter.columns(formulae_search_results)
puts "To install one of them, run (for example):\n brew install #{formulae_search_results.first}"
# If none of the above is true and the formula is linked, then
# FormulaInstaller will handle this case.
formulae << f
end
# Do not search taps if the formula name is qualified
return if e.name.include?("/")
# Even if we don't install this formula mark it as no longer just
# installed as a dependency.
next unless f.opt_prefix.directory?
ohai "Searching taps..."
taps_search_results = search_taps(e.name)[:formulae]
case taps_search_results.length
when 0
ofail "No formulae found in taps."
when 1
puts "This formula was found in a tap:"
puts taps_search_results
puts "To install it, run:\n brew install #{taps_search_results.first}"
else
puts "These formulae were found in taps:"
puts Formatter.columns(taps_search_results)
puts "To install one of them, run (for example):\n brew install #{taps_search_results.first}"
keg = Keg.new(f.opt_prefix.resolved_path)
tab = Tab.for_keg(keg)
unless tab.installed_on_request
tab.installed_on_request = true
tab.write
end
end
return if formulae.empty?
Install.perform_preinstall_checks
formulae.each do |f|
Migrator.migrate_if_needed(f)
install_formula(f)
Cleanup.install_formula_clean!(f)
end
Homebrew.messages.display_messages
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).
ofail e.message
rescue FormulaUnavailableError => e
if e.name == "updog"
ofail "What's updog?"
return
end
ofail e.message
if (reason = MissingFormula.reason(e.name))
$stderr.puts reason
return
end
ohai "Searching for similarly named formulae..."
formulae_search_results = search_formulae(e.name)
case formulae_search_results.length
when 0
ofail "No similarly named formulae found."
when 1
puts "This similarly named formula was found:"
puts formulae_search_results
puts "To install it, run:\n brew install #{formulae_search_results.first}"
else
puts "These similarly named formulae were found:"
puts Formatter.columns(formulae_search_results)
puts "To install one of them, run (for example):\n brew install #{formulae_search_results.first}"
end
# Do not search taps if the formula name is qualified
return if e.name.include?("/")
ohai "Searching taps..."
taps_search_results = search_taps(e.name)[:formulae]
case taps_search_results.length
when 0
ofail "No formulae found in taps."
when 1
puts "This formula was found in a tap:"
puts taps_search_results
puts "To install it, run:\n brew install #{taps_search_results.first}"
else
puts "These formulae were found in taps:"
puts Formatter.columns(taps_search_results)
puts "To install one of them, run (for example):\n brew install #{taps_search_results.first}"
end
end
def install_formula(f)
@ -312,12 +390,11 @@ module Homebrew
fi = FormulaInstaller.new(f)
fi.options = build_options.used_options
fi.invalid_option_names = build_options.invalid_option_names
fi.ignore_deps = ARGV.ignore_deps?
fi.only_deps = ARGV.only_deps?
fi.build_bottle = ARGV.build_bottle?
fi.interactive = ARGV.interactive?
fi.git = ARGV.git?
fi.ignore_deps = args.ignore_dependencies?
fi.only_deps = args.only_dependencies?
fi.build_bottle = args.build_bottle?
fi.interactive = args.interactive?
fi.git = args.git?
fi.prelude
fi.install
fi.finish

View File

@ -33,7 +33,7 @@ class FormulaInstaller
end
attr_reader :formula
attr_accessor :options, :build_bottle, :invalid_option_names
attr_accessor :options, :build_bottle
attr_accessor :installed_as_dependency, :installed_on_request, :link_keg
mode_attr_accessor :show_summary_heading, :show_header
mode_attr_accessor :build_from_source, :force_bottle, :include_test
@ -58,7 +58,6 @@ class FormulaInstaller
@installed_as_dependency = false
@installed_on_request = true
@options = Options.new
@invalid_option_names = []
@requirement_messages = []
@poured_bottle = false
@pour_failed = false
@ -267,10 +266,6 @@ class FormulaInstaller
opoo "#{formula.full_name}: #{old_flag} was deprecated; using #{new_flag} instead!"
end
invalid_option_names.each do |option|
opoo "#{formula.full_name}: this formula has no #{option} option so it will be ignored!"
end
options = display_options(formula)
if show_header?
oh1 "Installing #{Formatter.identifier(formula.full_name)} #{options}".strip

View File

@ -20,7 +20,6 @@ module Homebrew
fi = FormulaInstaller.new(f)
fi.options = options
fi.invalid_option_names = build_options.invalid_option_names
fi.build_bottle = ARGV.build_bottle? || (!f.bottle_defined? && f.build.bottle?)
fi.interactive = ARGV.interactive?
fi.git = ARGV.git?

View File

@ -37,17 +37,4 @@ describe BuildOptions do
specify "#unused_options" do
expect(subject.unused_options).to include("--without-baz")
end
specify "#invalid_options" do
expect(subject.invalid_options).to be_empty
expect(bad_build.invalid_options).to include("--without-bas")
expect(bad_build.invalid_options).to include("--without-abc")
expect(bad_build.invalid_options).not_to include("--with-foo")
expect(bad_build.invalid_options).not_to include("--with-baz")
end
specify "#invalid_option_names" do
expect(subject.invalid_option_names).to be_empty
expect(bad_build.invalid_option_names).to eq(%w[--without-abc --without-bas])
end
end

View File

@ -166,12 +166,12 @@ describe "brew install", :integration_test do
.and be_a_success
end
it "ignores invalid options" do
it "rejects invalid options" do
setup_test_formula "testball1"
expect { brew "install", "testball1", "--with-fo" }
.to output(/testball1: this formula has no \-\-with\-fo option so it will be ignored!/).to_stderr
.and output(/Downloading file/).to_stdout
.and be_a_success
expect { brew "install", "testball1", "--invalid" }
.to output(/Error: invalid option: --invalid/).to_stderr
.and not_to_output.to_stdout
.and be_a_failure
end
it "succeeds when a non-fatal requirement isn't satisfied" do

View File

@ -182,7 +182,7 @@ these flags should only appear after a command.
Upload logs for a failed build of *`formula`* to a new Gist.
*`formula`* is usually the name of the formula to install, but it can be specified
in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
in several different ways.
If `--with-hostname` is passed, include the hostname in the Gist.
@ -240,7 +240,7 @@ these flags should only appear after a command.
Install *`formula`*.
*`formula`* is usually the name of the formula to install, but it can be specified
in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
in several different ways.
If `--debug` (or `-d`) is passed and brewing fails, open an interactive debugging
session with access to IRB or a shell inside the temporary build directory.

View File

@ -184,7 +184,7 @@ If \fB\-\-force\-bottle\fR is passed, download a bottle if it exists for the cur
Upload logs for a failed build of \fIformula\fR to a new Gist\.
.
.IP
\fIformula\fR is usually the name of the formula to install, but it can be specified in several different ways\. See \fISPECIFYING FORMULAE\fR\.
\fIformula\fR is usually the name of the formula to install, but it can be specified in several different ways\.
.
.IP
If \fB\-\-with\-hostname\fR is passed, include the hostname in the Gist\.
@ -252,7 +252,7 @@ See the docs for examples of using the JSON output: \fIhttps://docs\.brew\.sh/Qu
Install \fIformula\fR\.
.
.IP
\fIformula\fR is usually the name of the formula to install, but it can be specified in several different ways\. See \fISPECIFYING FORMULAE\fR\.
\fIformula\fR is usually the name of the formula to install, but it can be specified in several different ways\.
.
.IP
If \fB\-\-debug\fR (or \fB\-d\fR) is passed and brewing fails, open an interactive debugging session with access to IRB or a shell inside the temporary build directory\.