Add --os=all and --arch=all options.

This commit is contained in:
Markus Reiter 2023-04-14 15:33:40 +02:00
parent f7b3225574
commit 486c3765ce
No known key found for this signature in database
GPG Key ID: 245293B51702655B
18 changed files with 534 additions and 184 deletions

View File

@ -33,13 +33,15 @@ module Homebrew
end
def freeze_named_args!(named_args, cask_options:)
options = {}
options[:force_bottle] = true if self[:force_bottle?]
options[:override_spec] = :head if self[:HEAD?]
options[:flags] = flags_only unless flags_only.empty?
self[:named] = NamedArgs.new(
*named_args.freeze,
override_spec: spec(nil),
force_bottle: self[:force_bottle?],
flags: flags_only,
cask_options: cask_options,
parent: self,
parent: self,
cask_options: cask_options,
**options,
)
end
@ -98,6 +100,44 @@ module Homebrew
return :cask if cask? && !formula?
end
sig { returns(T::Array[[Symbol, Symbol]]) }
def os_arch_combinations
skip_invalid_combinations = false
oses = case (os_sym = os&.to_sym)
when nil
[SimulateSystem.current_os]
when :all
skip_invalid_combinations = true
[
*MacOSVersions::SYMBOLS.keys,
:linux,
]
else
[os_sym]
end
arches = case (arch_sym = arch&.to_sym)
when nil
[SimulateSystem.current_arch]
when :all
skip_invalid_combinations = true
OnSystem::ARCH_OPTIONS
else
[arch_sym]
end
oses.product(arches).select do |os, arch|
if skip_invalid_combinations
bottle_tag = Utils::Bottles::Tag.new(system: os, arch: arch)
bottle_tag.valid_combination?
else
true
end
end
end
private
def option_to_name(option)
@ -124,14 +164,6 @@ module Homebrew
@cli_args.freeze
end
def spec(default = :stable)
if self[:HEAD?]
:head
else
default
end
end
def respond_to_missing?(method_name, *)
@table.key?(method_name)
end

View File

@ -240,6 +240,9 @@ module Homebrew
sig { returns(T.nilable(T::Array[String])) }
def only; end
sig { returns(T.nilable(String)) }
def os; end
sig { returns(T.nilable(T::Array[String])) }
def except; end
@ -270,6 +273,9 @@ module Homebrew
sig { returns(T::Boolean) }
def s?; end
sig { returns(T.nilable(String)) }
def arch; end
sig { returns(T.nilable(String)) }
def appdir; end

View File

@ -11,7 +11,24 @@ module Homebrew
#
# @api private
class NamedArgs < Array
def initialize(*args, parent: Args.new, override_spec: nil, force_bottle: false, flags: [], cask_options: false)
sig {
params(
args: String,
parent: Args,
override_spec: Symbol,
force_bottle: T::Boolean,
flags: T::Array[String],
cask_options: T::Boolean,
).void
}
def initialize(
*args,
parent: Args.new,
override_spec: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil),
cask_options: false
)
require "cask/cask"
require "cask/cask_loader"
require "formulary"
@ -50,11 +67,17 @@ module Homebrew
warn: T::Boolean,
).returns(T::Array[T.any(Formula, Keg, Cask::Cask)])
}
def to_formulae_and_casks(only: parent&.only_formula_or_cask, ignore_unavailable: nil, method: nil, uniq: true,
warn: true)
def to_formulae_and_casks(
only: parent&.only_formula_or_cask,
ignore_unavailable: nil,
method: T.unsafe(nil),
uniq: true,
warn: T.unsafe(nil)
)
@to_formulae_and_casks ||= {}
@to_formulae_and_casks[only] ||= downcased_unique_named.flat_map do |name|
load_formula_or_cask(name, only: only, method: method, warn: warn)
options = { warn: warn }.compact
load_formula_or_cask(name, only: only, method: method, **options)
rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError,
Cask::CaskUnreadableError
@ -88,14 +111,15 @@ module Homebrew
end.uniq.freeze
end
def load_formula_or_cask(name, only: nil, method: nil, warn: true)
def load_formula_or_cask(name, only: nil, method: nil, warn: nil)
unreadable_error = nil
if only != :cask
begin
formula = case method
when nil, :factory
Formulary.factory(name, *spec, force_bottle: @force_bottle, flags: @flags, warn: warn)
options = { warn: warn, force_bottle: @force_bottle, flags: @flags }.compact
Formulary.factory(name, *@override_spec, **options)
when :resolve
resolve_formula(name)
when :latest_kegs
@ -126,7 +150,8 @@ module Homebrew
begin
config = Cask::Config.from_args(@parent) if @cask_options
cask = Cask::CaskLoader.load(name, config: config, warn: warn)
options = { warn: warn }.compact
cask = Cask::CaskLoader.load(name, config: config, **options)
if unreadable_error.present?
onoe <<~EOS
@ -177,7 +202,7 @@ module Homebrew
private :load_formula_or_cask
def resolve_formula(name)
Formulary.resolve(name, spec: spec, force_bottle: @force_bottle, flags: @flags)
Formulary.resolve(name, **{ spec: @override_spec, force_bottle: @force_bottle, flags: @flags }.compact)
end
private :resolve_formula
@ -306,11 +331,6 @@ module Homebrew
end.uniq
end
def spec
@override_spec
end
private :spec
def resolve_kegs(name)
raise UsageError if name.blank?

View File

@ -16,6 +16,12 @@ module Homebrew
If <formula> is provided, display the file or directory used to cache <formula>.
EOS
flag "--os=",
description: "Show cache file for the given operating system." \
"(Pass `all` to show cache files for all operating systems.)"
flag "--arch=",
description: "Show cache file for the given CPU architecture." \
"(Pass `all` to show cache files for all architectures.)"
switch "-s", "--build-from-source",
description: "Show the cache file used when building from source."
switch "--force-bottle",
@ -31,6 +37,8 @@ module Homebrew
conflicts "--build-from-source", "--force-bottle", "--bottle-tag", "--HEAD", "--cask"
conflicts "--formula", "--cask"
conflicts "--os", "--bottle-tag"
conflicts "--arch", "--bottle-tag"
named_args [:formula, :cask]
end
@ -46,21 +54,62 @@ module Homebrew
end
formulae_or_casks = args.named.to_formulae_and_casks
os_arch_combinations = args.os_arch_combinations
formulae_or_casks.each do |formula_or_cask|
if formula_or_cask.is_a? Formula
print_formula_cache formula_or_cask, args: args
case formula_or_cask
when Formula
formula = T.cast(formula_or_cask, Formula)
ref = formula.loaded_from_api? ? formula.full_name : formula.path
os_arch_combinations.each do |os, arch|
SimulateSystem.with os: os, arch: arch do
Formulary.clear_cache
formula = Formulary.factory(ref)
print_formula_cache(formula, os: os, arch: arch, args: args)
end
end
else
print_cask_cache formula_or_cask
cask = formula_or_cask
ref = cask.loaded_from_api? ? cask.full_token : cask.sourcefile_path
os_arch_combinations.each do |os, arch|
next if os == :linux
SimulateSystem.with os: os, arch: arch do
cask = Cask::CaskLoader.load(ref)
print_cask_cache(cask)
end
end
end
end
end
sig { params(formula: Formula, args: CLI::Args).void }
def self.print_formula_cache(formula, args:)
if fetch_bottle?(formula, force_bottle: args.force_bottle?, bottle_tag: args.bottle_tag&.to_sym,
build_from_source_formulae: args.build_from_source_formulae)
puts formula.bottle_for_tag(args.bottle_tag&.to_sym)&.cached_download
sig { params(formula: Formula, os: Symbol, arch: Symbol, args: CLI::Args).void }
def self.print_formula_cache(formula, os:, arch:, args:)
if fetch_bottle?(
formula,
force_bottle: args.force_bottle?,
bottle_tag: args.bottle_tag&.to_sym,
build_from_source_formulae: args.build_from_source_formulae,
os: args.os&.to_sym,
arch: args.arch&.to_sym,
)
bottle_tag = if (bottle_tag = args.bottle_tag&.to_sym)
# TODO: odeprecate "--bottle-tag"
Utils::Bottles::Tag.from_symbol(bottle_tag)
else
Utils::Bottles::Tag.new(system: os, arch: arch)
end
bottle = formula.bottle_for_tag(bottle_tag)
if bottle.nil?
opoo "Bottle for tag #{bottle_tag.to_sym.inspect} is unavailable."
return
end
puts bottle.cached_download
elsif args.HEAD?
puts formula.head.cached_download
else

View File

@ -18,12 +18,14 @@ module Homebrew
Download a bottle (if available) or source packages for <formula>e
and binaries for <cask>s. For files, also print SHA-256 checksums.
EOS
# This is needed for downloading ARM casks in CI.
flag "--arch=",
description: "Download for the given CPU architecture.",
hidden: true
flag "--bottle-tag=",
description: "Download a bottle for given tag."
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",
@ -60,6 +62,8 @@ module Homebrew
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
@ -68,17 +72,14 @@ module Homebrew
def self.fetch
args = fetch_args.parse
if (arch = args.arch)
SimulateSystem.arch = arch.to_sym
end
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
f = formula_or_cask
[f, *f.recursive_dependencies.map(&:to_formula)]
formula = formula_or_cask
[formula, *formula.recursive_dependencies.map(&:to_formula)]
else
formula_or_cask
end
@ -87,52 +88,92 @@ module Homebrew
args.named.to_formulae_and_casks
end.uniq
os_arch_combinations = args.os_arch_combinations
puts "Fetching: #{bucket * ", "}" if bucket.size > 1
bucket.each do |formula_or_cask|
case formula_or_cask
when Formula
f = formula_or_cask
formula = T.cast(formula_or_cask, Formula)
ref = formula.loaded_from_api? ? formula.full_name : formula.path
f.print_tap_action verb: "Fetching"
os_arch_combinations.each do |os, arch|
SimulateSystem.with os: os, arch: arch do
Formulary.clear_cache
formula = Formulary.factory(ref)
fetched_bottle = false
if fetch_bottle?(f, force_bottle: args.force_bottle?, bottle_tag: args.bottle_tag&.to_sym,
build_from_source_formulae: args.build_from_source_formulae)
begin
f.clear_cache if args.force?
f.fetch_bottle_tab
fetch_formula(f.bottle_for_tag(args.bottle_tag&.to_sym), args: args)
rescue Interrupt
raise
rescue => e
raise if Homebrew::EnvConfig.developer?
formula.print_tap_action verb: "Fetching"
fetched_bottle = false
onoe e.message
opoo "Bottle fetch failed, fetching the source instead."
else
fetched_bottle = true
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?
# TODO: Deprecate `--bottle-tag`.
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: 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
formula.fetch_bottle_tab
fetch_formula(bottle, args: args)
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
next if fetched_bottle
fetch_formula(formula, args: args)
formula.resources.each do |r|
fetch_resource(r, args: args)
r.patches.each { |p| fetch_patch(p, args: args) if p.external? }
end
formula.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
end
end
next if fetched_bottle
fetch_formula(f, args: args)
f.resources.each do |r|
fetch_resource(r, args: args)
r.patches.each { |p| fetch_patch(p, args: args) if p.external? }
end
f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
else
cask = formula_or_cask
ref = cask.loaded_from_api? ? cask.full_token : cask.sourcefile_path
quarantine = args.quarantine?
quarantine = true if quarantine.nil?
os_arch_combinations.each do |os, arch|
next if os == :linux
download = Cask::Download.new(cask, quarantine: quarantine)
fetch_cask(download, args: args)
SimulateSystem.with os: os, arch: arch do
cask = Cask::CaskLoader.load(ref)
quarantine = args.quarantine?
quarantine = true if quarantine.nil?
download = Cask::Download.new(cask, quarantine: quarantine)
fetch_cask(download, args: args)
end
end
end
end
end

View File

@ -29,10 +29,10 @@ module Homebrew
locally available formulae and casks and skip style checks. Will exit with a
non-zero status if any errors are found.
EOS
# This is needed for auditing ARM casks in CI.
flag "--arch=",
description: "Audit the given CPU architecture.",
hidden: true
flag "--os=",
description: "Audit the given operating system. (Pass `all` to audit all operating systems.)"
flag "--arch=",
description: "Audit the given CPU architecture. (Pass `all` to audit all architectures.)"
switch "--strict",
description: "Run additional, stricter style checks."
switch "--git",
@ -106,9 +106,9 @@ module Homebrew
def self.audit
args = audit_args.parse
if (arch = args.arch)
SimulateSystem.arch = arch.to_sym
end
Formulary.enable_factory_cache!
os_arch_combinations = args.os_arch_combinations
Homebrew.auditing = true
inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug?
@ -200,7 +200,12 @@ module Homebrew
spdx_license_data = SPDX.license_data
spdx_exception_data = SPDX.exception_data
new_formula_problem_lines = T.let([], T::Array[String])
formula_results = audit_formulae.sort.to_h do |f|
formula_results = {}
audit_formulae.sort.each do |f|
path = f.path
only = only_cops ? ["style"] : args.only
options = {
new_formula: new_formula,
@ -214,63 +219,83 @@ module Homebrew
style_offenses: style_offenses&.for_path(f.path),
}.compact
audit_proc = proc { FormulaAuditor.new(f, **options).tap(&:audit) }
os_arch_combinations.each do |os, arch|
SimulateSystem.with os: os, arch: arch do
odebug "Auditing Formula #{f} on os #{os} and arch #{arch}"
# 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)
else
audit_proc.call
end
Formulary.clear_cache
f = Formulary.factory(path)
if fa.problems.any? || fa.new_formula_problems.any?
formula_count += 1
problem_count += fa.problems.size
problem_lines = format_problem_lines(fa.problems)
corrected_problem_count += options.fetch(:style_offenses, []).count(&:corrected?)
new_formula_problem_lines += format_problem_lines(fa.new_formula_problems)
if args.display_filename?
puts problem_lines.map { |s| "#{f.path}: #{s}" }
else
puts "#{f.full_name}:", problem_lines.map { |s| " #{s}" }
audit_proc = proc { FormulaAuditor.new(f, **options).tap(&:audit) }
# 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)
else
audit_proc.call
end
if fa.problems.any? || fa.new_formula_problems.any?
formula_count += 1
problem_count += fa.problems.size
problem_lines = format_problem_lines(fa.problems)
corrected_problem_count += options.fetch(:style_offenses, []).count(&:corrected?)
new_formula_problem_lines += format_problem_lines(fa.new_formula_problems)
if args.display_filename?
puts problem_lines.map { |s| "#{f.path}: #{s}" }
else
puts "#{f.full_name}:", problem_lines.map { |s| " #{s}" }
end
end
formula_results.deep_merge!({ f.path => fa.problems + fa.new_formula_problems })
end
end
[f.path, fa.problems + fa.new_formula_problems]
end
cask_results = if audit_casks.empty?
{}
else
cask_results = {}
if audit_casks.any?
require "cask/auditor"
if args.display_failures_only?
odeprecated "`brew audit <cask> --display-failures-only`", "`brew audit <cask>` without the argument"
end
end
require "cask/auditor"
audit_casks.each do |cask|
path = cask.sourcefile_path
audit_casks.to_h do |cask|
odebug "Auditing Cask #{cask}"
errors = Cask::Auditor.audit(
cask,
# For switches, we add `|| nil` so that `nil` will be passed
# instead of `false` if they aren't set.
# This way, we can distinguish between "not set" and "set to false".
audit_online: (args.online? || nil),
audit_strict: (args.strict? || nil),
os_arch_combinations.each do |os, arch|
next if os == :linux
# No need for `|| nil` for `--[no-]signing`
# because boolean switches are already `nil` if not passed
audit_signing: args.signing?,
audit_new_cask: (args.new_cask? || nil),
audit_token_conflicts: (args.token_conflicts? || nil),
quarantine: true,
any_named_args: !no_named_args,
only: args.only,
except: args.except,
)
[cask.sourcefile_path, errors]
SimulateSystem.with os: os, arch: arch do
odebug "Auditing Cask #{cask} on os #{os} and arch #{arch}"
cask = Cask::CaskLoader.load(path)
errors = Cask::Auditor.audit(
cask,
# For switches, we add `|| nil` so that `nil` will be passed
# instead of `false` if they aren't set.
# This way, we can distinguish between "not set" and "set to false".
audit_online: (args.online? || nil),
audit_strict: (args.strict? || nil),
# No need for `|| nil` for `--[no-]signing`
# because boolean switches are already `nil` if not passed
audit_signing: args.signing?,
audit_new_cask: (args.new_cask? || nil),
audit_token_conflicts: (args.token_conflicts? || nil),
quarantine: true,
any_named_args: !no_named_args,
only: args.only,
except: args.except,
)
cask_results.deep_merge!({ cask.sourcefile_path => errors })
end
end
end

View File

@ -7,6 +7,7 @@ require "cli/parser"
class String
def f(*args)
require "formula"
Formulary.factory(self, *args)
end

View File

@ -343,7 +343,7 @@ module Homebrew
nil
end
if libiconv&.linked_keg&.directory?
unless libiconv.keg_only?
unless libiconv&.keg_only?
<<~EOS
A libiconv formula is installed and linked.
This will break stuff. For serious. Unlink it.

View File

@ -4,13 +4,16 @@
module Utils
module Bottles
class << self
undef tag
module MacOSOverride
sig { params(tag: T.nilable(T.any(Symbol, Tag))).returns(Tag) }
def tag(tag = nil)
return Tag.new(system: MacOS.version.to_sym, arch: Hardware::CPU.arch) if tag.nil?
def tag(symbol = nil)
return Utils::Bottles::Tag.from_symbol(symbol) if symbol.present?
Utils::Bottles::Tag.new(system: MacOS.version.to_sym, arch: Hardware::CPU.arch)
super
end
end
prepend MacOSOverride
end
class Collector

View File

@ -10,12 +10,16 @@ module Homebrew
force_bottle: T::Boolean,
bottle_tag: T.nilable(Symbol),
build_from_source_formulae: T::Array[String],
os: T.nilable(Symbol),
arch: T.nilable(Symbol),
).returns(T::Boolean)
}
def fetch_bottle?(formula, force_bottle:, bottle_tag:, build_from_source_formulae:)
def fetch_bottle?(formula, force_bottle:, bottle_tag:, build_from_source_formulae:, os:, arch:)
bottle = formula.bottle
return true if force_bottle && bottle.present?
return true if os.present?
return true if arch.present?
return true if bottle_tag.present? && formula.bottled?(bottle_tag)
bottle.present? &&

View File

@ -385,7 +385,7 @@ class Formula
# The Bottle object for given tag.
# @private
sig { params(tag: T.nilable(Symbol)).returns(T.nilable(Bottle)) }
sig { params(tag: T.nilable(Utils::Bottles::Tag)).returns(T.nilable(Bottle)) }
def bottle_for_tag(tag = nil)
Bottle.new(self, bottle_specification, tag) if bottled?(tag)
end

View File

@ -165,7 +165,7 @@ class FormulaInstaller
return true if formula.local_bottle_path.present?
bottle = formula.bottle_for_tag(Utils::Bottles.tag.to_sym)
bottle = formula.bottle_for_tag(Utils::Bottles.tag)
return false if bottle.nil?
unless bottle.compatible_locations?

View File

@ -38,11 +38,17 @@ class FormulaVersions
end
end
sig { params(rev: String).returns(String) }
def file_contents_at_revision(rev)
repository.cd { Utils.popen_read("git", "cat-file", "blob", "#{rev}:#{entry_name}") }
end
def formula_at_revision(rev)
sig {
type_parameters(:U)
.params(rev: String, _block: T.proc.params(arg0: Formula).returns(T.type_parameter(:U)))
.returns(T.nilable(T.type_parameter(:U)))
}
def formula_at_revision(rev, &_block)
Homebrew.raise_deprecation_exceptions = true
yield @formula_at_revision[rev] ||= begin

View File

@ -51,7 +51,12 @@ module Formulary
next if type == :formulary_factory
cached_objects.each_value do |klass|
namespace = Utils.deconstantize(klass.name)
class_name = klass.name
# Already removed from namespace.
next if class_name.nil?
namespace = Utils.deconstantize(class_name)
next if Utils.deconstantize(namespace) != name
remove_const(Utils.demodulize(namespace).to_sym)
@ -119,6 +124,10 @@ module Formulary
end
end
sig {
params(name: String, path: Pathname, flags: T::Array[String], ignore_errors: T::Boolean)
.returns(T.class_of(Formula))
}
def self.load_formula_from_path(name, path, flags:, ignore_errors:)
contents = path.open("r") { |f| ensure_utf8_encoding(f).read }
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(path.to_s)}"
@ -127,6 +136,7 @@ module Formulary
cache[:path][path] = klass
end
sig { params(name: String, flags: T::Array[String]).returns(T.class_of(Formula)) }
def self.load_formula_from_api(name, flags:)
namespace = :"FormulaNamespaceAPI#{Digest::MD5.hexdigest(name)}"
@ -313,15 +323,27 @@ module Formulary
end
end
klass = T.cast(klass, T.class_of(Formula))
mod.const_set(class_name, klass)
cache[:api] ||= {}
cache[:api][name] = klass
end
def self.resolve(name, spec: nil, force_bottle: false, flags: [])
sig { params(name: String, spec: Symbol, force_bottle: T::Boolean, flags: T::Array[String]).returns(Formula) }
def self.resolve(
name,
spec: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil)
)
options = {
force_bottle: force_bottle,
flags: flags,
}.compact
if name.include?("/") || File.exist?(name)
f = factory(name, *spec, force_bottle: force_bottle, flags: flags)
f = factory(name, *spec, **options)
if f.any_version_installed?
tab = Tab.for_formula(f)
resolved_spec = spec || tab.spec
@ -334,8 +356,10 @@ module Formulary
end
else
rack = to_rack(name)
alias_path = factory(name, force_bottle: force_bottle, flags: flags).alias_path
f = from_rack(rack, *spec, alias_path: alias_path, force_bottle: force_bottle, flags: flags)
if (alias_path = factory(name, **options).alias_path)
options[:alias_path] = alias_path
end
f = from_rack(rack, *spec, **options)
end
# If this formula was installed with an alias that has since changed,
@ -524,12 +548,12 @@ module Formulary
# Loads tapped formulae.
class TapLoader < FormulaLoader
def initialize(tapped_name, from: nil, warn: true)
def initialize(tapped_name, from:, warn:)
name, path, tap = formula_name_path(tapped_name, warn: warn)
super name, path, tap: tap
end
def formula_name_path(tapped_name, warn: true)
def formula_name_path(tapped_name, warn:)
user, repo, name = tapped_name.split("/", 3).map(&:downcase)
tap = Tap.fetch user, repo
path = find_formula_from_name(name, tap)
@ -648,25 +672,46 @@ module Formulary
# * a formula pathname
# * a formula URL
# * a local bottle reference
sig {
params(
ref: T.nilable(T.any(Pathname, String)),
spec: Symbol,
alias_path: Pathname,
from: Symbol,
warn: T::Boolean,
force_bottle: T::Boolean,
flags: T::Array[String],
ignore_errors: T::Boolean,
).returns(Formula)
}
def self.factory(
ref, spec = :stable, alias_path: nil, from: nil, warn: true,
force_bottle: false, flags: [], ignore_errors: false
ref,
spec = :stable,
alias_path: T.unsafe(nil),
from: T.unsafe(nil),
warn: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil),
ignore_errors: T.unsafe(nil)
)
raise ArgumentError, "Formulae must have a ref!" unless ref
cache_key = "#{ref}-#{spec}-#{alias_path}-#{from}"
if factory_cached? && cache[:formulary_factory] &&
cache[:formulary_factory][cache_key]
return cache[:formulary_factory][cache_key]
end
return cache[:formulary_factory][cache_key] if factory_cached? && cache[:formulary_factory]&.key?(cache_key)
loader_options = { from: from, warn: warn }.compact
formula_options = { alias_path: alias_path,
force_bottle: force_bottle,
flags: flags,
ignore_errors: ignore_errors }.compact
formula = loader_for(ref, **loader_options)
.get_formula(spec, **formula_options)
formula = loader_for(ref, from: from, warn: warn).get_formula(spec, alias_path: alias_path,
force_bottle: force_bottle, flags: flags,
ignore_errors: ignore_errors)
if factory_cached?
cache[:formulary_factory] ||= {}
cache[:formulary_factory][cache_key] ||= formula
end
formula
end
@ -676,15 +721,35 @@ module Formulary
# @param :alias_path will be used if the formula is found not to be
# installed, and discarded if it is installed because the `alias_path` used
# to install the formula will be set instead.
def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags: [])
sig {
params(
rack: Pathname,
# Automatically resolves the formula's spec if not specified.
spec: Symbol,
alias_path: Pathname,
force_bottle: T::Boolean,
flags: T::Array[String],
).returns(Formula)
}
def self.from_rack(
rack, spec = T.unsafe(nil),
alias_path: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil)
)
kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : []
keg = kegs.find(&:linked?) || kegs.find(&:optlinked?) || kegs.max_by(&:version)
options = {
alias_path: alias_path,
force_bottle: force_bottle,
flags: flags,
}.compact
if keg
from_keg(keg, spec, alias_path: alias_path, force_bottle: force_bottle, flags: flags)
from_keg(keg, *spec, **options)
else
factory(rack.basename.to_s, spec || :stable, alias_path: alias_path, from: :rack, warn: false,
force_bottle: force_bottle, flags: flags)
factory(rack.basename.to_s, *spec, from: :rack, warn: false, **options)
end
end
@ -696,24 +761,45 @@ module Formulary
end
# Return a {Formula} instance for the given keg.
#
# @param spec when nil, will auto resolve the formula's spec.
def self.from_keg(keg, spec = nil, alias_path: nil, force_bottle: false, flags: [])
sig {
params(
keg: Keg,
# Automatically resolves the formula's spec if not specified.
spec: Symbol,
alias_path: Pathname,
force_bottle: T::Boolean,
flags: T::Array[String],
).returns(Formula)
}
def self.from_keg(
keg,
spec = T.unsafe(nil),
alias_path: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil)
)
tab = Tab.for_keg(keg)
tap = tab.tap
spec ||= tab.spec
formula_name = keg.rack.basename.to_s
options = {
alias_path: alias_path,
from: :keg,
warn: false,
force_bottle: force_bottle,
flags: flags,
}.compact
f = if tap.nil?
factory(keg.rack.basename.to_s, spec, alias_path: alias_path, from: :keg, warn: false,
force_bottle: force_bottle, flags: flags)
factory(formula_name, spec, **options)
else
begin
factory("#{tap}/#{keg.rack.basename}", spec, alias_path: alias_path, from: :keg, warn: false,
force_bottle: force_bottle, flags: flags)
factory("#{tap}/#{formula_name}", spec, **options)
rescue FormulaUnavailableError
# formula may be migrated to different tap. Try to search in core and all taps.
factory(keg.rack.basename.to_s, spec, alias_path: alias_path, from: :keg, warn: false,
force_bottle: force_bottle, flags: flags)
factory(formula_name, spec, **options)
end
end
f.build = tab
@ -723,13 +809,35 @@ module Formulary
end
# Return a {Formula} instance directly from contents.
sig {
params(
name: String,
path: Pathname,
contents: String,
spec: Symbol,
alias_path: Pathname,
force_bottle: T::Boolean,
flags: T::Array[String],
ignore_errors: T::Boolean,
).returns(Formula)
}
def self.from_contents(
name, path, contents, spec = :stable, alias_path: nil,
force_bottle: false, flags: [], ignore_errors: false
name,
path,
contents,
spec = :stable,
alias_path: T.unsafe(nil),
force_bottle: T.unsafe(nil),
flags: T.unsafe(nil),
ignore_errors: T.unsafe(nil)
)
FormulaContentsLoader.new(name, path, contents)
.get_formula(spec, alias_path: alias_path, force_bottle: force_bottle,
flags: flags, ignore_errors: ignore_errors)
options = {
alias_path: alias_path,
force_bottle: force_bottle,
flags: flags,
ignore_errors: ignore_errors,
}.compact
FormulaContentsLoader.new(name, path, contents).get_formula(spec, **options)
end
def self.to_rack(ref)

View File

@ -71,16 +71,37 @@ class Attr < Parlour::Plugin
tree << element
elsif node.type == :send && children.shift.nil?
method_name = children.shift
if [:attr_rw, :attr_predicate].include?(method_name)
case method_name
when :attr_rw, :attr_predicate
children.each do |name_node|
tree << [method_name, name_node.children.first.to_s]
end
when :delegate
children.each do |name_node|
name_node.children.each do |pair|
delegated_method = pair.children.first
delegated_methods = if delegated_method.type == :array
delegated_method.children
else
[delegated_method]
end
delegated_methods.each do |delegated_method_sym|
tree << [method_name, delegated_method_sym.children.first.to_s]
end
end
end
end
end
tree
end
ARRAY_METHODS = T.let(["to_a", "to_ary"].freeze, T::Array[String])
HASH_METHODS = T.let(["to_h", "to_hash"].freeze, T::Array[String])
STRING_METHODS = T.let(["to_s", "to_str", "to_json"].freeze, T::Array[String])
sig { params(tree: T::Array[T.untyped], namespace: Parlour::RbiGenerator::Namespace, sclass: T::Boolean).void }
def process_custom_attr(tree, namespace, sclass: false)
tree.each do |node|
@ -107,6 +128,32 @@ class Attr < Parlour::Plugin
name = node.shift
name = "self.#{name}" if sclass
namespace.create_method(name, return_type: "T::Boolean")
when :delegate
name = node.shift
return_type = if name.end_with?("?")
"T::Boolean"
elsif ARRAY_METHODS.include?(name)
"Array"
elsif HASH_METHODS.include?(name)
"Hash"
elsif STRING_METHODS.include?(name)
"String"
else
"T.untyped"
end
name = "self.#{name}" if sclass
namespace.create_method(
name,
parameters: [
Parlour::RbiGenerator::Parameter.new("*args"),
Parlour::RbiGenerator::Parameter.new("**options"),
Parlour::RbiGenerator::Parameter.new("&block"),
],
return_type: return_type,
)
else
raise "Malformed tree."
end

View File

@ -4,17 +4,17 @@ require "cli/named_args"
def setup_unredable_formula(name)
error = FormulaUnreadableError.new(name, RuntimeError.new("testing"))
allow(Formulary).to receive(:factory).with(name, force_bottle: false, flags: [], warn: true).and_raise(error)
allow(Formulary).to receive(:factory).with(name, {}).and_raise(error)
end
def setup_unredable_cask(name)
error = Cask::CaskUnreadableError.new(name, "testing")
allow(Cask::CaskLoader).to receive(:load).with(name).and_raise(error)
allow(Cask::CaskLoader).to receive(:load).with(name, config: nil, warn: true).and_raise(error)
allow(Cask::CaskLoader).to receive(:load).with(name, config: nil).and_raise(error)
config = instance_double(Cask::Config)
allow(Cask::Config).to receive(:from_args).and_return(config)
allow(Cask::CaskLoader).to receive(:load).with(name, config: config, warn: true).and_raise(error)
allow(Cask::CaskLoader).to receive(:load).with(name, config: config).and_raise(error)
end
describe Homebrew::CLI::NamedArgs do

View File

@ -16,7 +16,7 @@ module Test
loader = double(get_formula: formula)
allow(Formulary).to receive(:loader_for).with(ref, from: :keg, warn: false).and_return(loader)
allow(Formulary).to receive(:loader_for).with(ref, from: nil, warn: true).and_return(loader)
allow(Formulary).to receive(:loader_for).with(ref, {}).and_return(loader)
end
end
end

View File

@ -10,11 +10,19 @@ module Utils
module Bottles
class << self
# Gets the tag for the running OS.
def tag(symbol = nil)
return Tag.from_symbol(symbol) if symbol.present?
@tag ||= Tag.new(system: HOMEBREW_SYSTEM.downcase.to_sym,
arch: HOMEBREW_PROCESSOR.downcase.to_sym)
sig { params(tag: T.nilable(T.any(Symbol, Tag))).returns(Tag) }
def tag(tag = nil)
case tag
when Symbol
Tag.from_symbol(tag)
when Tag
tag
else
@tag ||= Tag.new(
system: HOMEBREW_SYSTEM.downcase.to_sym,
arch: HOMEBREW_PROCESSOR.downcase.to_sym,
)
end
end
def built_as?(formula)