mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

- This reverts commit 68dbf12220f9a52ddf5ca5eb3aa1b356aa42da3a that was a revert of commit 68596ec720a50f44ae9c30c5806aa7f2d4bcc96f because I put the RBI file in the wrong place and Tapioca got rid of it, causing this week's release to be blocked.
250 lines
7.8 KiB
Ruby
250 lines
7.8 KiB
Ruby
# typed: true
|
|
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
require "tsort"
|
|
|
|
module Homebrew
|
|
module Bundle
|
|
# TODO: refactor into multiple modules
|
|
module BrewDumper
|
|
module_function
|
|
|
|
def reset!
|
|
require "bundle/brew_services"
|
|
|
|
Homebrew::Bundle::BrewServices.reset!
|
|
@formulae = nil
|
|
@formulae_by_full_name = nil
|
|
@formulae_by_name = nil
|
|
@formula_aliases = nil
|
|
@formula_oldnames = nil
|
|
end
|
|
|
|
def formulae
|
|
return @formulae if @formulae
|
|
|
|
formulae_by_full_name
|
|
@formulae
|
|
end
|
|
|
|
def formulae_by_full_name(name = nil)
|
|
return @formulae_by_full_name[name] if name.present? && @formulae_by_full_name&.key?(name)
|
|
|
|
require "formula"
|
|
require "formulary"
|
|
Formulary.enable_factory_cache!
|
|
|
|
@formulae_by_name ||= {}
|
|
@formulae_by_full_name ||= {}
|
|
|
|
if name.nil?
|
|
formulae = Formula.installed.map(&method(:add_formula))
|
|
sort!(formulae)
|
|
return @formulae_by_full_name
|
|
end
|
|
|
|
formula = Formula[name]
|
|
add_formula(formula)
|
|
rescue FormulaUnavailableError => e
|
|
opoo "'#{name}' formula is unreadable: #{e}"
|
|
{}
|
|
end
|
|
|
|
def formulae_by_name(name)
|
|
formulae_by_full_name(name) || @formulae_by_name[name]
|
|
end
|
|
|
|
def dump(describe: false, no_restart: false)
|
|
require "bundle/brew_services"
|
|
|
|
requested_formula = formulae.select do |f|
|
|
f[:installed_on_request?] || !f[:installed_as_dependency?]
|
|
end
|
|
requested_formula.map do |f|
|
|
brewline = if describe && f[:desc].present?
|
|
f[:desc].split("\n").map { |s| "# #{s}\n" }.join
|
|
else
|
|
""
|
|
end
|
|
brewline += "brew \"#{f[:full_name]}\""
|
|
|
|
args = f[:args].map { |arg| "\"#{arg}\"" }.sort.join(", ")
|
|
brewline += ", args: [#{args}]" unless f[:args].empty?
|
|
brewline += ", restart_service: :changed" if !no_restart && BrewServices.started?(f[:full_name])
|
|
brewline += ", link: #{f[:link?]}" unless f[:link?].nil?
|
|
brewline
|
|
end.join("\n")
|
|
end
|
|
|
|
def formula_aliases
|
|
return @formula_aliases if @formula_aliases
|
|
|
|
@formula_aliases = {}
|
|
formulae.each do |f|
|
|
aliases = f[:aliases]
|
|
next if aliases.blank?
|
|
|
|
aliases.each do |a|
|
|
@formula_aliases[a] = f[:full_name]
|
|
if f[:full_name].include? "/" # tap formula
|
|
tap_name = f[:full_name].rpartition("/").first
|
|
@formula_aliases["#{tap_name}/#{a}"] = f[:full_name]
|
|
end
|
|
end
|
|
end
|
|
@formula_aliases
|
|
end
|
|
|
|
def formula_oldnames
|
|
return @formula_oldnames if @formula_oldnames
|
|
|
|
@formula_oldnames = {}
|
|
formulae.each do |f|
|
|
oldnames = f[:oldnames]
|
|
next if oldnames.blank?
|
|
|
|
oldnames.each do |oldname|
|
|
@formula_oldnames[oldname] = f[:full_name]
|
|
if f[:full_name].include? "/" # tap formula
|
|
tap_name = f[:full_name].rpartition("/").first
|
|
@formula_oldnames["#{tap_name}/#{oldname}"] = f[:full_name]
|
|
end
|
|
end
|
|
end
|
|
@formula_oldnames
|
|
end
|
|
|
|
def add_formula(formula)
|
|
hash = formula_to_hash formula
|
|
|
|
@formulae_by_name[hash[:name]] = hash
|
|
@formulae_by_full_name[hash[:full_name]] = hash
|
|
|
|
hash
|
|
end
|
|
private_class_method :add_formula
|
|
|
|
def formula_to_hash(formula)
|
|
keg = if formula.linked?
|
|
link = true if formula.keg_only?
|
|
formula.linked_keg
|
|
else
|
|
link = false unless formula.keg_only?
|
|
formula.any_installed_prefix
|
|
end
|
|
|
|
if keg
|
|
require "tab"
|
|
|
|
tab = Tab.for_keg(keg)
|
|
args = tab.used_options.map(&:name)
|
|
version = begin
|
|
keg.realpath.basename
|
|
rescue
|
|
# silently handle broken symlinks
|
|
nil
|
|
end.to_s
|
|
args << "HEAD" if version.start_with?("HEAD")
|
|
installed_as_dependency = tab.installed_as_dependency
|
|
installed_on_request = tab.installed_on_request
|
|
runtime_dependencies = if (runtime_deps = tab.runtime_dependencies)
|
|
runtime_deps.filter_map { |d| d["full_name"] }
|
|
|
|
end
|
|
poured_from_bottle = tab.poured_from_bottle
|
|
end
|
|
|
|
runtime_dependencies ||= formula.runtime_dependencies.map(&:name)
|
|
|
|
bottled = if (stable = formula.stable) && stable.bottle_defined?
|
|
bottle_hash = formula.bottle_hash.deep_symbolize_keys
|
|
stable.bottled?
|
|
end
|
|
|
|
{
|
|
name: formula.name,
|
|
desc: formula.desc,
|
|
oldnames: formula.oldnames,
|
|
full_name: formula.full_name,
|
|
aliases: formula.aliases,
|
|
any_version_installed?: formula.any_version_installed?,
|
|
args: Array(args).uniq,
|
|
version:,
|
|
installed_as_dependency?: installed_as_dependency || false,
|
|
installed_on_request?: installed_on_request || false,
|
|
dependencies: runtime_dependencies,
|
|
build_dependencies: formula.deps.select(&:build?).map(&:name).uniq,
|
|
conflicts_with: formula.conflicts.map(&:name),
|
|
pinned?: formula.pinned? || false,
|
|
outdated?: formula.outdated? || false,
|
|
link?: link,
|
|
poured_from_bottle?: poured_from_bottle || false,
|
|
bottle: bottle_hash || false,
|
|
bottled: bottled || false,
|
|
official_tap: formula.tap&.official? || false,
|
|
}
|
|
end
|
|
private_class_method :formula_to_hash
|
|
|
|
class Topo < Hash
|
|
include TSort
|
|
|
|
def each_key(&block)
|
|
keys.each(&block)
|
|
end
|
|
alias tsort_each_node each_key
|
|
|
|
def tsort_each_child(node, &block)
|
|
fetch(node.downcase).sort.each(&block)
|
|
end
|
|
end
|
|
|
|
def sort!(formulae)
|
|
# Step 1: Sort by formula full name while putting tap formulae behind core formulae.
|
|
# So we can have a nicer output.
|
|
formulae = formulae.sort do |a, b|
|
|
if a[:full_name].exclude?("/") && b[:full_name].include?("/")
|
|
-1
|
|
elsif a[:full_name].include?("/") && b[:full_name].exclude?("/")
|
|
1
|
|
else
|
|
a[:full_name] <=> b[:full_name]
|
|
end
|
|
end
|
|
|
|
# Step 2: Sort by formula dependency topology.
|
|
topo = Topo.new
|
|
formulae.each do |f|
|
|
topo[f[:name]] = topo[f[:full_name]] = f[:dependencies].filter_map do |dep|
|
|
ff = formulae_by_name(dep)
|
|
next if ff.blank?
|
|
next unless ff[:any_version_installed?]
|
|
|
|
ff[:full_name]
|
|
end
|
|
end
|
|
@formulae = topo.tsort
|
|
.map { |name| @formulae_by_full_name[name] || @formulae_by_name[name] }
|
|
.uniq { |f| f[:full_name] }
|
|
rescue TSort::Cyclic => e
|
|
e.message =~ /\["([^"]*)".*"([^"]*)"\]/
|
|
cycle_first = Regexp.last_match(1)
|
|
cycle_last = Regexp.last_match(2)
|
|
odie e.message if !cycle_first || !cycle_last
|
|
|
|
odie <<~EOS
|
|
Formulae dependency graph sorting failed (likely due to a circular dependency):
|
|
#{cycle_first}: #{topo[cycle_first] if topo}
|
|
#{cycle_last}: #{topo[cycle_last] if topo}
|
|
Please run the following commands and try again:
|
|
brew update
|
|
brew uninstall --ignore-dependencies --force #{cycle_first} #{cycle_last}
|
|
brew install #{cycle_first} #{cycle_last}
|
|
EOS
|
|
end
|
|
private_class_method :sort!
|
|
end
|
|
end
|
|
end
|