Merge pull request #20490 from Homebrew/parser-formula-stubs

Load formulae from stubs in argument parser
This commit is contained in:
Mike McQuaid 2025-08-25 07:28:29 +00:00 committed by GitHub
commit a0b6d12d30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 27 deletions

View File

@ -80,17 +80,24 @@ module Homebrew
@to_formulae_and_casks ||= T.let( @to_formulae_and_casks ||= T.let(
{}, T.nilable(T::Hash[T.nilable(Symbol), T::Array[T.any(Formula, Keg, Cask::Cask)]]) {}, T.nilable(T::Hash[T.nilable(Symbol), T::Array[T.any(Formula, Keg, Cask::Cask)]])
) )
@to_formulae_and_casks[only] ||= downcased_unique_named.flat_map do |name| @to_formulae_and_casks[only] ||= begin
options = { warn: }.compact download_queue = Homebrew::DownloadQueue.new if Homebrew::EnvConfig.download_concurrency > 1
load_formula_or_cask(name, only:, method:, **options)
rescue FormulaUnreadableError, FormulaClassUnavailableError, formulae_and_casks = downcased_unique_named.flat_map do |name|
TapFormulaUnreadableError, TapFormulaClassUnavailableError, load_and_fetch_full_formula_or_cask(name, only:, method:, warn:, download_queue:)
Cask::CaskUnreadableError rescue FormulaUnreadableError, FormulaClassUnavailableError,
# Need to rescue before `*UnavailableError` (superclass of this) TapFormulaUnreadableError, TapFormulaClassUnavailableError,
# The formula/cask was found, but there's a problem with its implementation Cask::CaskUnreadableError
raise # Need to rescue before `*UnavailableError` (superclass of this)
rescue NoSuchKegError, FormulaUnavailableError, Cask::CaskUnavailableError, FormulaOrCaskUnavailableError # The formula/cask was found, but there's a problem with its implementation
ignore_unavailable ? [] : raise raise
rescue NoSuchKegError, FormulaUnavailableError, Cask::CaskUnavailableError, FormulaOrCaskUnavailableError
ignore_unavailable ? [] : raise
end
download_queue&.fetch
map_to_fully_loaded(formulae_and_casks)
end.freeze end.freeze
if uniq if uniq
@ -152,11 +159,19 @@ module Homebrew
T::Array[T.any(Formula, Keg, Cask::Cask, T::Array[Keg], FormulaOrCaskUnavailableError)], T::Array[T.any(Formula, Keg, Cask::Cask, T::Array[Keg], FormulaOrCaskUnavailableError)],
]), ]),
) )
@to_formulae_casks_unknowns[method] = downcased_unique_named.map do |name| @to_formulae_casks_unknowns[method] = begin
load_formula_or_cask(name, only:, method:) download_queue = Homebrew::DownloadQueue.new if Homebrew::EnvConfig.download_concurrency > 1
rescue FormulaOrCaskUnavailableError => e
e formulae_and_casks = downcased_unique_named.map do |name|
end.uniq.freeze load_and_fetch_full_formula_or_cask(name, only:, method:, download_queue:)
rescue FormulaOrCaskUnavailableError => e
e
end.uniq
download_queue&.fetch
map_to_fully_loaded(formulae_and_casks)
end.freeze
end end
sig { params(uniq: T::Boolean).returns(T::Array[Formula]) } sig { params(uniq: T::Boolean).returns(T::Array[Formula]) }
@ -315,10 +330,21 @@ module Homebrew
end end
sig { sig {
params(name: String, only: T.nilable(Symbol), method: T.nilable(Symbol), warn: T.nilable(T::Boolean)) params(name: String, only: T.nilable(Symbol), method: T.nilable(Symbol), warn: T::Boolean,
download_queue: T.nilable(Homebrew::DownloadQueue))
.returns(T.any(Formula, Keg, Cask::Cask, T::Array[Keg])) .returns(T.any(Formula, Keg, Cask::Cask, T::Array[Keg]))
} }
def load_formula_or_cask(name, only: nil, method: nil, warn: nil) def load_and_fetch_full_formula_or_cask(name, only: nil, method: nil, warn: false, download_queue: nil)
formula_or_cask = load_formula_or_cask(name, only:, method:, warn:)
formula_or_cask.fetch_fully_loaded_formula!(download_queue:) if formula_or_cask.is_a?(Formula)
formula_or_cask
end
sig {
params(name: String, only: T.nilable(Symbol), method: T.nilable(Symbol), warn: T::Boolean)
.returns(T.any(Formula, Keg, Cask::Cask, T::Array[Keg]))
}
def load_formula_or_cask(name, only: nil, method: nil, warn: false)
Homebrew.with_no_api_env_if_needed(@without_api) do Homebrew.with_no_api_env_if_needed(@without_api) do
unreadable_error = nil unreadable_error = nil
@ -326,8 +352,8 @@ module Homebrew
begin begin
case method case method
when nil, :factory when nil, :factory
options = { warn:, force_bottle: @force_bottle, flags: @flags }.compact Formulary.factory(name, *@override_spec,
Formulary.factory(name, *@override_spec, **options) warn:, force_bottle: @force_bottle, flags: @flags, prefer_stub: true)
when :resolve when :resolve
resolve_formula(name) resolve_formula(name)
when :latest_kegs when :latest_kegs
@ -447,7 +473,7 @@ module Homebrew
sig { params(name: String).returns(Formula) } sig { params(name: String).returns(Formula) }
def resolve_formula(name) def resolve_formula(name)
Formulary.resolve(name, **{ spec: @override_spec, force_bottle: @force_bottle, flags: @flags }.compact) Formulary.resolve(name, spec: @override_spec, force_bottle: @force_bottle, flags: @flags, prefer_stub: true)
end end
sig { params(name: String).returns([Pathname, T::Array[Keg]]) } sig { params(name: String).returns([Pathname, T::Array[Keg]]) }
@ -577,6 +603,19 @@ module Homebrew
opoo package_conflicts_message(ref, loaded_type, cask) opoo package_conflicts_message(ref, loaded_type, cask)
end end
sig {
type_parameters(:U)
.params(formulae_and_casks: T::Array[T.all(T.type_parameter(:U), Object)])
.returns(T::Array[T.all(T.type_parameter(:U), Object)])
}
def map_to_fully_loaded(formulae_and_casks)
formulae_and_casks.map do |formula_or_cask|
next formula_or_cask unless formula_or_cask.is_a?(Formula)
T.cast(formula_or_cask.fully_loaded_formula, T.all(T.type_parameter(:U), Object))
end
end
end end
end end
end end

View File

@ -733,7 +733,7 @@ module Homebrew
next if arg.match?(HOMEBREW_CASK_TAP_CASK_REGEX) next if arg.match?(HOMEBREW_CASK_TAP_CASK_REGEX)
begin begin
Formulary.factory(arg, spec, flags: argv.select { |a| a.start_with?("--") }) Formulary.factory(arg, spec, flags: argv.select { |a| a.start_with?("--") }, prefer_stub: true)
rescue FormulaUnavailableError, FormulaSpecificationError rescue FormulaUnavailableError, FormulaSpecificationError
nil nil
end end

View File

@ -36,8 +36,8 @@ class Dependency
[name, tags].hash [name, tags].hash
end end
def to_formula def to_formula(prefer_stub: false)
formula = Formulary.factory(name, warn: false) formula = Formulary.factory(name, warn: false, prefer_stub:)
formula.build = BuildOptions.new(options, formula.options) formula.build = BuildOptions.new(options, formula.options)
formula formula
end end
@ -45,7 +45,7 @@ class Dependency
sig { params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer)).returns(T::Boolean) } sig { params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer)).returns(T::Boolean) }
def installed?(minimum_version: nil, minimum_revision: nil) def installed?(minimum_version: nil, minimum_revision: nil)
formula = begin formula = begin
to_formula to_formula(prefer_stub: true)
rescue FormulaUnavailableError rescue FormulaUnavailableError
nil nil
end end
@ -86,7 +86,7 @@ class Dependency
end end
def missing_options(inherited_options) def missing_options(inherited_options)
formula = to_formula formula = to_formula(prefer_stub: true)
required = options required = options
required |= inherited_options required |= inherited_options
required &= formula.options.to_a required &= formula.options.to_a

View File

@ -65,7 +65,8 @@ RSpec.describe Utils::Autoremove do
include_context "with formulae for dependency testing" include_context "with formulae for dependency testing"
before do before do
allow(Formulary).to receive(:factory).with("three", { warn: false }).and_return(formula_is_build_dep) allow(Formulary).to receive(:factory).with("three", { prefer_stub: false, warn: false })
.and_return(formula_is_build_dep)
end end
context "when formulae are bottles" do context "when formulae are bottles" do