Merge pull request #15564 from Bo98/better-auto-no-api

Better scoping for no-api commands
This commit is contained in:
Mike McQuaid 2023-06-22 12:38:03 +01:00 committed by GitHub
commit 8d9aa9070a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 101 deletions

View File

@ -176,4 +176,20 @@ module Homebrew
Tap.fetch(org, repo)
end
end
# @api private
sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }
def self.with_no_api_env(&block)
return yield if Homebrew::EnvConfig.no_install_from_api?
with_env(HOMEBREW_NO_INSTALL_FROM_API: "1", HOMEBREW_AUTOMATICALLY_SET_NO_INSTALL_FROM_API: "1", &block)
end
# @api private
sig { params(condition: T::Boolean, block: T.proc.returns(T.untyped)).returns(T.untyped) }
def self.with_no_api_env_if_needed(condition, &block)
return yield unless condition
with_no_api_env(&block)
end
end

View File

@ -916,7 +916,6 @@ then
livecheck
pr-pull
pr-upload
test
update-python-resources
)

View File

@ -32,7 +32,7 @@ module Homebrew
self[:remaining] = remaining_args.freeze
end
def freeze_named_args!(named_args, cask_options:)
def freeze_named_args!(named_args, cask_options:, without_api:)
options = {}
options[:force_bottle] = true if self[:force_bottle?]
options[:override_spec] = :head if self[:HEAD?]
@ -41,6 +41,7 @@ module Homebrew
*named_args.freeze,
parent: self,
cask_options: cask_options,
without_api: without_api,
**options,
)
end

View File

@ -19,6 +19,7 @@ module Homebrew
force_bottle: T::Boolean,
flags: T::Array[String],
cask_options: T::Boolean,
without_api: T::Boolean,
).void
}
def initialize(
@ -27,7 +28,8 @@ module Homebrew
override_spec: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil),
cask_options: false
cask_options: false,
without_api: false
)
require "cask/cask"
require "cask/cask_loader"
@ -40,6 +42,7 @@ module Homebrew
@force_bottle = force_bottle
@flags = flags
@cask_options = cask_options
@without_api = without_api
@parent = parent
super(@args)
@ -112,92 +115,94 @@ module Homebrew
end
def load_formula_or_cask(name, only: nil, method: nil, warn: nil)
unreadable_error = nil
Homebrew.with_no_api_env_if_needed(@without_api) do
unreadable_error = nil
if only != :cask
begin
formula = case method
when nil, :factory
options = { warn: warn, force_bottle: @force_bottle, flags: @flags }.compact
Formulary.factory(name, *@override_spec, **options)
when :resolve
resolve_formula(name)
when :latest_kegs
resolve_latest_keg(name)
when :default_kegs
resolve_default_keg(name)
when :kegs
_, kegs = resolve_kegs(name)
kegs
else
raise
end
warn_if_cask_conflicts(name, "formula") if only != :formula
return formula
rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e
# Need to rescue before `FormulaUnavailableError` (superclass of this)
# The formula was found, but there's a problem with its implementation
unreadable_error ||= e
rescue NoSuchKegError, FormulaUnavailableError => e
raise e if only == :formula
end
end
if only != :formula
want_keg_like_cask = [:latest_kegs, :default_kegs, :kegs].include?(method)
begin
config = Cask::Config.from_args(@parent) if @cask_options
options = { warn: warn }.compact
cask = Cask::CaskLoader.load(name, config: config, **options)
if unreadable_error.present?
onoe <<~EOS
Failed to load formula: #{name}
#{unreadable_error}
EOS
opoo "Treating #{name} as a cask."
end
# If we're trying to get a keg-like Cask, do our best to use the same cask
# file that was used for installation, if possible.
if want_keg_like_cask && (installed_caskfile = cask.installed_caskfile) && installed_caskfile.exist?
cask = Cask::CaskLoader.load(installed_caskfile)
end
return cask
rescue Cask::CaskUnreadableError, Cask::CaskInvalidError => e
# If we're trying to get a keg-like Cask, do our best to handle it
# not being readable and return something that can be used.
if want_keg_like_cask
cask_version = Cask::Cask.new(name, config: config).installed_version
cask = Cask::Cask.new(name, config: config) do
version cask_version if cask_version
if only != :cask
begin
formula = case method
when nil, :factory
options = { warn: warn, force_bottle: @force_bottle, flags: @flags }.compact
Formulary.factory(name, *@override_spec, **options)
when :resolve
resolve_formula(name)
when :latest_kegs
resolve_latest_keg(name)
when :default_kegs
resolve_default_keg(name)
when :kegs
_, kegs = resolve_kegs(name)
kegs
else
raise
end
return cask
warn_if_cask_conflicts(name, "formula") if only != :formula
return formula
rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e
# Need to rescue before `FormulaUnavailableError` (superclass of this)
# The formula was found, but there's a problem with its implementation
unreadable_error ||= e
rescue NoSuchKegError, FormulaUnavailableError => e
raise e if only == :formula
end
# Need to rescue before `CaskUnavailableError` (superclass of this)
# The cask was found, but there's a problem with its implementation
unreadable_error ||= e
rescue Cask::CaskUnavailableError => e
raise e if only == :cask
end
if only != :formula
want_keg_like_cask = [:latest_kegs, :default_kegs, :kegs].include?(method)
begin
config = Cask::Config.from_args(@parent) if @cask_options
options = { warn: warn }.compact
cask = Cask::CaskLoader.load(name, config: config, **options)
if unreadable_error.present?
onoe <<~EOS
Failed to load formula: #{name}
#{unreadable_error}
EOS
opoo "Treating #{name} as a cask."
end
# If we're trying to get a keg-like Cask, do our best to use the same cask
# file that was used for installation, if possible.
if want_keg_like_cask && (installed_caskfile = cask.installed_caskfile) && installed_caskfile.exist?
cask = Cask::CaskLoader.load(installed_caskfile)
end
return cask
rescue Cask::CaskUnreadableError, Cask::CaskInvalidError => e
# If we're trying to get a keg-like Cask, do our best to handle it
# not being readable and return something that can be used.
if want_keg_like_cask
cask_version = Cask::Cask.new(name, config: config).installed_version
cask = Cask::Cask.new(name, config: config) do
version cask_version if cask_version
end
return cask
end
# Need to rescue before `CaskUnavailableError` (superclass of this)
# The cask was found, but there's a problem with its implementation
unreadable_error ||= e
rescue Cask::CaskUnavailableError => e
raise e if only == :cask
end
end
raise unreadable_error if unreadable_error.present?
user, repo, short_name = name.downcase.split("/", 3)
if repo.present? && short_name.present?
tap = Tap.fetch(user, repo)
raise TapFormulaOrCaskUnavailableError.new(tap, short_name)
end
raise NoSuchKegError, name if resolve_formula(name)
raise FormulaOrCaskUnavailableError, name
end
raise unreadable_error if unreadable_error.present?
user, repo, short_name = name.downcase.split("/", 3)
if repo.present? && short_name.present?
tap = Tap.fetch(user, repo)
raise TapFormulaOrCaskUnavailableError.new(tap, short_name)
end
raise NoSuchKegError, name if resolve_formula(name)
raise FormulaOrCaskUnavailableError, name
end
private :load_formula_or_cask

View File

@ -138,6 +138,7 @@ module Homebrew
@named_args_type = nil
@max_named_args = nil
@min_named_args = nil
@named_args_without_api = false
@description = nil
@usage_banner = nil
@hide_from_man_page = false
@ -346,7 +347,7 @@ module Homebrew
check_named_args(named_args)
end
@args.freeze_named_args!(named_args, cask_options: @cask_options)
@args.freeze_named_args!(named_args, cask_options: @cask_options, without_api: @named_args_without_api)
@args.freeze_remaining_args!(non_options.empty? ? remaining : [*remaining, "--", non_options])
@args.freeze_processed_options!(@processed_options)
@args.freeze
@ -392,13 +393,14 @@ module Homebrew
sig {
params(
type: T.any(NilClass, Symbol, T::Array[String], T::Array[Symbol]),
number: T.nilable(Integer),
min: T.nilable(Integer),
max: T.nilable(Integer),
type: T.any(NilClass, Symbol, T::Array[String], T::Array[Symbol]),
number: T.nilable(Integer),
min: T.nilable(Integer),
max: T.nilable(Integer),
without_api: T::Boolean,
).void
}
def named_args(type = nil, number: nil, min: nil, max: nil)
def named_args(type = nil, number: nil, min: nil, max: nil, without_api: false)
if number.present? && (min.present? || max.present?)
raise ArgumentError, "Do not specify both `number` and `min` or `max`"
end
@ -417,6 +419,8 @@ module Homebrew
@min_named_args = min
@max_named_args = max
end
@named_args_without_api = without_api
end
sig { void }

View File

@ -123,7 +123,7 @@ module Homebrew
ENV.activate_extensions!
ENV.setup_build_environment
audit_formulae, audit_casks = without_api do # audit requires full Ruby source
audit_formulae, audit_casks = with_no_api_env do # audit requires full Ruby source
if args.tap
Tap.fetch(args.tap).then do |tap|
[
@ -217,7 +217,7 @@ module Homebrew
# Audit requires full Ruby source so disable API.
# We shouldn't do this for taps however so that we don't unnecessarily require a full Homebrew/core clone.
fa = if f.core_formula?
without_api(&audit_proc)
with_no_api_env(&audit_proc)
else
audit_proc.call
end
@ -347,10 +347,4 @@ module Homebrew
"* #{location}#{message.chomp.gsub("\n", "\n ")}#{status}"
end
end
def self.without_api(&block)
return yield if Homebrew::EnvConfig.no_install_from_api?
with_env(HOMEBREW_NO_INSTALL_FROM_API: "1", HOMEBREW_AUTOMATICALLY_SET_NO_INSTALL_FROM_API: "1", &block)
end
end

View File

@ -28,7 +28,7 @@ module Homebrew
switch "--retry",
description: "Retry if a testing fails."
named_args :installed_formula, min: 1
named_args :installed_formula, min: 1, without_api: true
end
end

View File

@ -87,13 +87,19 @@ class FormulaOrCaskUnavailableError < RuntimeError
super()
@name = name
# Store the state of these envs at the time the exception is thrown.
# This is so we do the fuzzy search for "did you mean" etc under that same mode,
# in case the list of formulae are different.
@without_api = Homebrew::EnvConfig.no_install_from_api?
@auto_without_api = Homebrew::EnvConfig.automatically_set_no_install_from_api?
end
sig { returns(String) }
def did_you_mean
require "formula"
similar_formula_names = Formula.fuzzy_search(name)
similar_formula_names = Homebrew.with_no_api_env_if_needed(@without_api) { Formula.fuzzy_search(name) }
return "" if similar_formula_names.blank?
"Did you mean #{similar_formula_names.to_sentence two_words_connector: " or ", last_word_connector: " or "}?"
@ -101,7 +107,11 @@ class FormulaOrCaskUnavailableError < RuntimeError
sig { returns(String) }
def to_s
"No available formula or cask with the name \"#{name}\". #{did_you_mean}".strip
s = "No available formula or cask with the name \"#{name}\". #{did_you_mean}".strip
if @auto_without_api && !CoreTap.instance.installed?
s += "\nA full git tap clone is required to use this command on core packages."
end
s
end
end