brew/Library/Homebrew/dev-cmd/unbottled.rb

245 lines
6.9 KiB
Ruby
Raw Normal View History

# typed: true
# frozen_string_literal: true
require "cli/parser"
require "formula"
2021-08-06 02:30:44 -04:00
require "api"
require "os/mac/xcode"
module Homebrew
module_function
sig { returns(CLI::Parser) }
def unbottled_args
Homebrew::CLI::Parser.new do
description <<~EOS
2021-01-26 15:21:24 -05:00
Show the unbottled dependents of formulae.
EOS
2021-01-26 15:21:24 -05:00
flag "--tag=",
description: "Use the specified bottle tag (e.g. `big_sur`) instead of the current OS."
switch "--dependents",
2021-01-26 15:21:24 -05:00
description: "Skip getting analytics data and sort by number of dependents instead."
switch "--total",
2021-01-26 15:21:24 -05:00
description: "Print the number of unbottled and total formulae."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to check them. " \
2023-02-10 23:15:40 -05:00
"Implied if `HOMEBREW_EVAL_ALL` is set."
2021-01-26 15:21:24 -05:00
conflicts "--dependents", "--total"
2021-01-10 14:26:40 -05:00
named_args :formula
end
end
sig { void }
def unbottled
args = unbottled_args.parse
Formulary.enable_factory_cache!
@bottle_tag = if (tag = args.tag)
Utils::Bottles::Tag.from_symbol(tag.to_sym)
else
Utils::Bottles.tag
end
2023-05-13 22:35:08 +02:00
os = @bottle_tag.system
arch = if Hardware::CPU::INTEL_ARCHS.include?(@bottle_tag.arch)
2022-11-15 05:22:42 +00:00
:intel
elsif Hardware::CPU::ARM_ARCHS.include?(@bottle_tag.arch)
:arm
else
raise "Unknown arch #{@bottle_tag.arch}."
end
2023-05-13 22:35:08 +02:00
Homebrew::SimulateSystem.with os: os, arch: arch do
all = args.eval_all?
if args.total?
if !all && !Homebrew::EnvConfig.eval_all?
odisabled "brew unbottled --total", "brew unbottled --total --eval-all or HOMEBREW_EVAL_ALL"
end
all = true
end
2023-05-13 22:35:08 +02:00
if args.named.blank?
ohai "Getting formulae..."
elsif all
raise UsageError, "Cannot specify formulae when using `--eval-all`/`--total`."
end
2023-05-13 22:35:08 +02:00
formulae, all_formulae, formula_installs =
formulae_all_installs_from_args(args, all)
deps_hash, uses_hash = deps_uses_from_formulae(all_formulae)
if args.dependents?
formula_dependents = {}
formulae = formulae.sort_by do |f|
dependents = uses_hash[f.name]&.length || 0
formula_dependents[f.name] ||= dependents
end.reverse
elsif all
output_total(formulae)
return
end
2023-05-13 22:35:08 +02:00
noun, hash = if args.named.present?
[nil, {}]
elsif args.dependents?
["dependents", formula_dependents]
else
["installs", formula_installs]
end
2023-05-13 22:35:08 +02:00
output_unbottled(formulae, deps_hash, noun, hash, args.named.present?)
end
end
def formulae_all_installs_from_args(args, all)
if args.named.present?
formulae = all_formulae = args.named.to_formulae
elsif args.dependents?
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
odisabled "brew unbottled --dependents", "brew unbottled --all --dependents or HOMEBREW_EVAL_ALL"
end
formulae = all_formulae = Formula.all
@sort = " (sorted by number of dependents)"
elsif all
formulae = all_formulae = Formula.all
else
formula_installs = {}
ohai "Getting analytics data..."
2021-08-06 02:30:44 -04:00
analytics = Homebrew::API::Analytics.fetch "install", 90
if analytics.blank?
raise UsageError,
"default sort by analytics data requires " \
"`HOMEBREW_NO_GITHUB_API` and `HOMEBREW_NO_ANALYTICS` to be unset"
end
formulae = analytics["items"].map do |i|
f = i["formula"].split.first
next if f.include?("/")
next if formula_installs[f].present?
formula_installs[f] = i["count"]
begin
Formula[f]
rescue FormulaUnavailableError
nil
end
end.compact
@sort = " (sorted by installs in the last 90 days; top 10,000 only)"
all_formulae = Formula.all
end
[formulae, all_formulae, formula_installs]
end
def deps_uses_from_formulae(all_formulae)
ohai "Populating dependency tree..."
deps_hash = {}
uses_hash = {}
all_formulae.each do |f|
deps = f.recursive_dependencies do |_, dep|
Dependency.prune if dep.optional?
end.map(&:to_formula)
deps_hash[f.name] = deps
deps.each do |dep|
uses_hash[dep.name] ||= []
uses_hash[dep.name] << f
end
end
[deps_hash, uses_hash]
end
def output_total(formulae)
ohai "Unbottled :#{@bottle_tag} formulae"
unbottled_formulae = 0
formulae.each do |f|
next if f.bottle_specification.tag?(@bottle_tag)
unbottled_formulae += 1
end
puts "#{unbottled_formulae}/#{formulae.length} remaining."
end
def output_unbottled(formulae, deps_hash, noun, hash, any_named_args)
ohai ":#{@bottle_tag} bottle status#{@sort}"
any_found = T.let(false, T::Boolean)
formulae.each do |f|
name = f.name.downcase
if f.disabled?
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}: formula disabled" if any_named_args
next
end
requirements = f.recursive_requirements
if @bottle_tag.linux?
if requirements.any? { |r| r.is_a?(MacOSRequirement) && !r.version }
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires macOS" if any_named_args
next
end
2021-03-01 13:43:47 +00:00
elsif requirements.any?(LinuxRequirement)
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: requires Linux" if any_named_args
next
else
macos_version = @bottle_tag.to_macos_version
macos_satisfied = requirements.all? do |r|
case r
when MacOSRequirement
next true unless r.version_specified?
2023-04-04 17:59:36 -07:00
macos_version.compare(r.comparator, r.version)
when XcodeRequirement
next true unless r.version
Version.new(MacOS::Xcode.latest_version(macos: macos_version)) >= r.version
when ArchRequirement
r.arch == @bottle_tag.arch
else
true
end
end
unless macos_satisfied
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: doesn't support this macOS" if any_named_args
next
end
end
if f.bottle_specification.tag?(@bottle_tag, no_older_versions: true)
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}: already bottled" if any_named_args
next
end
deps = Array(deps_hash[f.name]).reject do |dep|
dep.bottle_specification.tag?(@bottle_tag, no_older_versions: true)
end
2020-12-23 10:57:10 +01:00
if deps.blank?
count = " (#{hash[f.name]} #{noun})" if noun
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}#{count}: ready to bottle"
2020-12-23 10:57:10 +01:00
next
end
any_found ||= true
count = " (#{hash[f.name]} #{noun})" if noun
puts "#{Tty.bold}#{Tty.yellow}#{name}#{Tty.reset}#{count}: unbottled deps: #{deps.join(" ")}"
end
return if any_found
return if any_named_args
puts "No unbottled dependencies found!"
end
end