Port Homebrew::DevCmd::Typecheck
This commit is contained in:
parent
445d81db2e
commit
ba5f392d4c
@ -1,95 +1,99 @@
|
||||
# typed: true
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "abstract_command"
|
||||
require "cli/parser"
|
||||
require "fileutils"
|
||||
|
||||
module Homebrew
|
||||
sig { returns(CLI::Parser) }
|
||||
def self.typecheck_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
description <<~EOS
|
||||
Check for typechecking errors using Sorbet.
|
||||
EOS
|
||||
switch "--fix",
|
||||
description: "Automatically fix type errors."
|
||||
switch "-q", "--quiet",
|
||||
description: "Silence all non-critical errors."
|
||||
switch "--update",
|
||||
description: "Update RBI files."
|
||||
switch "--update-all",
|
||||
description: "Update all RBI files rather than just updated gems."
|
||||
switch "--suggest-typed",
|
||||
depends_on: "--update",
|
||||
description: "Try upgrading `typed` sigils."
|
||||
flag "--dir=",
|
||||
description: "Typecheck all files in a specific directory."
|
||||
flag "--file=",
|
||||
description: "Typecheck a single file."
|
||||
flag "--ignore=",
|
||||
description: "Ignores input files that contain the given string " \
|
||||
"in their paths (relative to the input path passed to Sorbet)."
|
||||
module DevCmd
|
||||
class Typecheck < AbstractCommand
|
||||
include FileUtils
|
||||
|
||||
conflicts "--dir", "--file"
|
||||
cmd_args do
|
||||
description <<~EOS
|
||||
Check for typechecking errors using Sorbet.
|
||||
EOS
|
||||
switch "--fix",
|
||||
description: "Automatically fix type errors."
|
||||
switch "-q", "--quiet",
|
||||
description: "Silence all non-critical errors."
|
||||
switch "--update",
|
||||
description: "Update RBI files."
|
||||
switch "--update-all",
|
||||
description: "Update all RBI files rather than just updated gems."
|
||||
switch "--suggest-typed",
|
||||
depends_on: "--update",
|
||||
description: "Try upgrading `typed` sigils."
|
||||
flag "--dir=",
|
||||
description: "Typecheck all files in a specific directory."
|
||||
flag "--file=",
|
||||
description: "Typecheck a single file."
|
||||
flag "--ignore=",
|
||||
description: "Ignores input files that contain the given string " \
|
||||
"in their paths (relative to the input path passed to Sorbet)."
|
||||
|
||||
named_args :none
|
||||
end
|
||||
end
|
||||
conflicts "--dir", "--file"
|
||||
|
||||
sig { void }
|
||||
def self.typecheck
|
||||
args = typecheck_args.parse
|
||||
|
||||
update = args.update? || args.update_all?
|
||||
groups = update ? Homebrew.valid_gem_groups : ["typecheck"]
|
||||
Homebrew.install_bundler_gems!(groups:)
|
||||
|
||||
HOMEBREW_LIBRARY_PATH.cd do
|
||||
if update
|
||||
safe_system "bundle", "exec", "tapioca", "dsl"
|
||||
# Prefer adding args here: Library/Homebrew/sorbet/tapioca/config.yml
|
||||
tapioca_args = args.update_all? ? ["--all"] : []
|
||||
|
||||
ohai "Updating homegrown RBI files..."
|
||||
safe_system "bundle", "exec", "ruby", "sorbet/custom_generators/tty.rb"
|
||||
safe_system "bundle", "exec", "ruby", "sorbet/custom_generators/env_config.rb"
|
||||
|
||||
ohai "Updating Tapioca RBI files..."
|
||||
safe_system "bundle", "exec", "tapioca", "gem", *tapioca_args
|
||||
safe_system "bundle", "exec", "parlour"
|
||||
|
||||
if args.suggest_typed?
|
||||
ohai "Bumping Sorbet `typed` sigils..."
|
||||
# --sorbet needed because of https://github.com/Shopify/spoom/issues/488
|
||||
safe_system "bundle", "exec", "spoom", "bump", "--dry", "--sorbet", "#{Gem.bin_path("sorbet", "srb")} tc"
|
||||
end
|
||||
|
||||
return
|
||||
named_args :none
|
||||
end
|
||||
|
||||
srb_exec = %w[bundle exec srb tc]
|
||||
sig { override.void }
|
||||
def run
|
||||
update = args.update? || args.update_all?
|
||||
groups = update ? Homebrew.valid_gem_groups : ["typecheck"]
|
||||
Homebrew.install_bundler_gems!(groups:)
|
||||
|
||||
srb_exec << "--quiet" if args.quiet?
|
||||
HOMEBREW_LIBRARY_PATH.cd do
|
||||
if update
|
||||
safe_system "bundle", "exec", "tapioca", "dsl"
|
||||
# Prefer adding args here: Library/Homebrew/sorbet/tapioca/config.yml
|
||||
tapioca_args = args.update_all? ? ["--all"] : []
|
||||
|
||||
if args.fix?
|
||||
# Auto-correcting method names is almost always wrong.
|
||||
srb_exec << "--suppress-error-code" << "7003"
|
||||
ohai "Updating homegrown RBI files..."
|
||||
safe_system "bundle", "exec", "ruby", "sorbet/custom_generators/tty.rb"
|
||||
safe_system "bundle", "exec", "ruby", "sorbet/custom_generators/env_config.rb"
|
||||
|
||||
srb_exec << "--autocorrect"
|
||||
end
|
||||
ohai "Updating Tapioca RBI files..."
|
||||
safe_system "bundle", "exec", "tapioca", "gem", *tapioca_args
|
||||
safe_system "bundle", "exec", "parlour"
|
||||
|
||||
srb_exec += ["--ignore", args.ignore] if args.ignore.present?
|
||||
if args.file.present? || args.dir.present?
|
||||
cd("sorbet") do
|
||||
srb_exec += ["--file", "../#{args.file}"] if args.file
|
||||
srb_exec += ["--dir", "../#{args.dir}"] if args.dir
|
||||
if args.suggest_typed?
|
||||
ohai "Bumping Sorbet `typed` sigils..."
|
||||
# --sorbet needed because of https://github.com/Shopify/spoom/issues/488
|
||||
safe_system "bundle", "exec", "spoom", "bump", "--dry", "--sorbet",
|
||||
"#{Gem.bin_path("sorbet", "srb")} tc"
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
srb_exec = %w[bundle exec srb tc]
|
||||
|
||||
srb_exec << "--quiet" if args.quiet?
|
||||
|
||||
if args.fix?
|
||||
# Auto-correcting method names is almost always wrong.
|
||||
srb_exec << "--suppress-error-code" << "7003"
|
||||
|
||||
srb_exec << "--autocorrect"
|
||||
end
|
||||
|
||||
srb_exec += ["--ignore", args.ignore] if args.ignore.present?
|
||||
if args.file.present? || args.dir.present?
|
||||
cd("sorbet") do
|
||||
srb_exec += ["--file", "../#{args.file}"] if args.file
|
||||
srb_exec += ["--dir", "../#{args.dir}"] if args.dir
|
||||
end
|
||||
end
|
||||
success = system(*srb_exec)
|
||||
return if success
|
||||
|
||||
$stderr.puts "Check #{Formatter.url("https://docs.brew.sh/Typechecking")} for " \
|
||||
"more information on how to resolve these errors."
|
||||
Homebrew.failed = true
|
||||
end
|
||||
end
|
||||
success = system(*srb_exec)
|
||||
return if success
|
||||
|
||||
$stderr.puts "Check #{Formatter.url("https://docs.brew.sh/Typechecking")} for " \
|
||||
"more information on how to resolve these errors."
|
||||
Homebrew.failed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,318 +1,318 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "abstract_command"
|
||||
require "cli/parser"
|
||||
require "formula"
|
||||
require "api"
|
||||
require "os/mac/xcode"
|
||||
|
||||
module Homebrew
|
||||
module_function
|
||||
module DevCmd
|
||||
class Unbottled < AbstractCommand
|
||||
cmd_args do
|
||||
description <<~EOS
|
||||
Show the unbottled dependents of formulae.
|
||||
EOS
|
||||
flag "--tag=",
|
||||
description: "Use the specified bottle tag (e.g. `big_sur`) instead of the current OS."
|
||||
switch "--dependents",
|
||||
description: "Skip getting analytics data and sort by number of dependents instead."
|
||||
switch "--total",
|
||||
description: "Print the number of unbottled and total formulae."
|
||||
switch "--lost",
|
||||
description: "Print the `homebrew/core` commits where bottles were lost in the last week."
|
||||
switch "--eval-all",
|
||||
description: "Evaluate all available formulae and casks, whether installed or not, to check them. " \
|
||||
"Implied if `HOMEBREW_EVAL_ALL` is set."
|
||||
|
||||
sig { returns(CLI::Parser) }
|
||||
def unbottled_args
|
||||
Homebrew::CLI::Parser.new do
|
||||
description <<~EOS
|
||||
Show the unbottled dependents of formulae.
|
||||
EOS
|
||||
flag "--tag=",
|
||||
description: "Use the specified bottle tag (e.g. `big_sur`) instead of the current OS."
|
||||
switch "--dependents",
|
||||
description: "Skip getting analytics data and sort by number of dependents instead."
|
||||
switch "--total",
|
||||
description: "Print the number of unbottled and total formulae."
|
||||
switch "--lost",
|
||||
description: "Print the `homebrew/core` commits where bottles were lost in the last week."
|
||||
switch "--eval-all",
|
||||
description: "Evaluate all available formulae and casks, whether installed or not, to check them. " \
|
||||
"Implied if `HOMEBREW_EVAL_ALL` is set."
|
||||
conflicts "--dependents", "--total", "--lost"
|
||||
|
||||
conflicts "--dependents", "--total", "--lost"
|
||||
|
||||
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
|
||||
|
||||
if args.lost?
|
||||
if args.named.present?
|
||||
raise UsageError, "`brew unbottled --lost` cannot be used with formula arguments!"
|
||||
elsif !CoreTap.instance.installed?
|
||||
raise UsageError, "`brew unbottled --lost` requires `homebrew/core` to be tapped locally!"
|
||||
else
|
||||
output_lost_bottles
|
||||
return
|
||||
named_args :formula
|
||||
end
|
||||
end
|
||||
|
||||
os = @bottle_tag.system
|
||||
arch = if Hardware::CPU::INTEL_ARCHS.include?(@bottle_tag.arch)
|
||||
:intel
|
||||
elsif Hardware::CPU::ARM_ARCHS.include?(@bottle_tag.arch)
|
||||
:arm
|
||||
else
|
||||
raise "Unknown arch #{@bottle_tag.arch}."
|
||||
end
|
||||
sig { override.void }
|
||||
def run
|
||||
Formulary.enable_factory_cache!
|
||||
|
||||
Homebrew::SimulateSystem.with(os:, arch:) do
|
||||
all = args.eval_all?
|
||||
if args.total?
|
||||
if !all && !Homebrew::EnvConfig.eval_all?
|
||||
raise UsageError, "`brew unbottled --total` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
|
||||
@bottle_tag = if (tag = args.tag)
|
||||
Utils::Bottles::Tag.from_symbol(tag.to_sym)
|
||||
else
|
||||
Utils::Bottles.tag
|
||||
end
|
||||
|
||||
all = true
|
||||
end
|
||||
|
||||
if args.named.blank?
|
||||
ohai "Getting formulae..."
|
||||
elsif all
|
||||
raise UsageError, "Cannot specify formulae when using `--eval-all`/`--total`."
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
noun, hash = if args.named.present?
|
||||
[nil, {}]
|
||||
elsif args.dependents?
|
||||
["dependents", formula_dependents]
|
||||
else
|
||||
["installs", formula_installs]
|
||||
end
|
||||
|
||||
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?
|
||||
raise UsageError, "`brew unbottled --dependents` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
|
||||
end
|
||||
|
||||
formulae = all_formulae = Formula.all(eval_all: args.eval_all?)
|
||||
|
||||
@sort = " (sorted by number of dependents)"
|
||||
elsif all
|
||||
formulae = all_formulae = Formula.all(eval_all: args.eval_all?)
|
||||
else
|
||||
formula_installs = {}
|
||||
|
||||
ohai "Getting analytics data..."
|
||||
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"].filter_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
|
||||
@sort = " (sorted by installs in the last 90 days; top 10,000 only)"
|
||||
|
||||
all_formulae = Formula.all(eval_all: args.eval_all?)
|
||||
end
|
||||
|
||||
# Remove deprecated formulae as we do not care if they are unbottled
|
||||
formulae = Array(formulae).reject(&:deprecated?) if formulae.present?
|
||||
all_formulae = Array(all_formulae).reject(&:deprecated?) if all_formulae.present?
|
||||
|
||||
[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 = Dependency.expand(f, cache_key: "unbottled") 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
|
||||
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?
|
||||
|
||||
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
|
||||
if args.lost?
|
||||
if args.named.present?
|
||||
raise UsageError, "`brew unbottled --lost` cannot be used with formula arguments!"
|
||||
elsif !CoreTap.instance.installed?
|
||||
raise UsageError, "`brew unbottled --lost` requires `homebrew/core` to be tapped locally!"
|
||||
else
|
||||
true
|
||||
output_lost_bottles
|
||||
return
|
||||
end
|
||||
end
|
||||
unless macos_satisfied
|
||||
puts "#{Tty.bold}#{Tty.red}#{name}#{Tty.reset}: doesn't support this macOS" if any_named_args
|
||||
next
|
||||
|
||||
os = @bottle_tag.system
|
||||
arch = if Hardware::CPU::INTEL_ARCHS.include?(@bottle_tag.arch)
|
||||
:intel
|
||||
elsif Hardware::CPU::ARM_ARCHS.include?(@bottle_tag.arch)
|
||||
:arm
|
||||
else
|
||||
raise "Unknown arch #{@bottle_tag.arch}."
|
||||
end
|
||||
|
||||
Homebrew::SimulateSystem.with(os:, arch:) do
|
||||
all = args.eval_all?
|
||||
if args.total?
|
||||
if !all && !Homebrew::EnvConfig.eval_all?
|
||||
raise UsageError, "`brew unbottled --total` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
|
||||
end
|
||||
|
||||
all = true
|
||||
end
|
||||
|
||||
if args.named.blank?
|
||||
ohai "Getting formulae..."
|
||||
elsif all
|
||||
raise UsageError, "Cannot specify formulae when using `--eval-all`/`--total`."
|
||||
end
|
||||
|
||||
formulae, all_formulae, formula_installs = formulae_all_installs_from_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
|
||||
|
||||
noun, hash = if args.named.present?
|
||||
[nil, {}]
|
||||
elsif args.dependents?
|
||||
["dependents", formula_dependents]
|
||||
else
|
||||
["installs", formula_installs]
|
||||
end
|
||||
|
||||
output_unbottled(formulae, deps_hash, noun, hash, args.named.present?)
|
||||
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
|
||||
private
|
||||
|
||||
deps = Array(deps_hash[f.name]).reject do |dep|
|
||||
dep.bottle_specification.tag?(@bottle_tag, no_older_versions: true)
|
||||
end
|
||||
def formulae_all_installs_from_args(all)
|
||||
if args.named.present?
|
||||
formulae = all_formulae = args.named.to_formulae
|
||||
elsif args.dependents?
|
||||
if !args.eval_all? && !Homebrew::EnvConfig.eval_all?
|
||||
raise UsageError,
|
||||
"`brew unbottled --dependents` needs `--eval-all` passed or `HOMEBREW_EVAL_ALL` set!"
|
||||
end
|
||||
|
||||
if deps.blank?
|
||||
count = " (#{hash[f.name]} #{noun})" if noun
|
||||
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}#{count}: ready to bottle"
|
||||
next
|
||||
end
|
||||
formulae = all_formulae = Formula.all(eval_all: args.eval_all?)
|
||||
|
||||
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
|
||||
@sort = " (sorted by number of dependents)"
|
||||
elsif all
|
||||
formulae = all_formulae = Formula.all(eval_all: args.eval_all?)
|
||||
else
|
||||
formula_installs = {}
|
||||
|
||||
puts "No unbottled dependencies found!"
|
||||
end
|
||||
ohai "Getting analytics data..."
|
||||
analytics = Homebrew::API::Analytics.fetch "install", 90
|
||||
|
||||
def output_lost_bottles
|
||||
ohai ":#{@bottle_tag} lost bottles"
|
||||
if analytics.blank?
|
||||
raise UsageError,
|
||||
"default sort by analytics data requires " \
|
||||
"`HOMEBREW_NO_GITHUB_API` and `HOMEBREW_NO_ANALYTICS` to be unset"
|
||||
end
|
||||
|
||||
bottle_tag_regex_fragment = " +sha256.* #{@bottle_tag}: "
|
||||
formulae = analytics["items"].filter_map do |i|
|
||||
f = i["formula"].split.first
|
||||
next if f.include?("/")
|
||||
next if formula_installs[f].present?
|
||||
|
||||
# $ git log --patch --no-ext-diff -G'^ +sha256.* sonoma:' --since=@{'1 week ago'}
|
||||
git_log = %w[git log --patch --no-ext-diff]
|
||||
git_log << "-G^#{bottle_tag_regex_fragment}"
|
||||
git_log << "--since=@{'1 week ago'}"
|
||||
|
||||
bottle_tag_sha_regex = /^[+-]#{bottle_tag_regex_fragment}/
|
||||
|
||||
processed_formulae = Set.new
|
||||
commit = T.let(nil, T.nilable(String))
|
||||
formula = T.let(nil, T.nilable(String))
|
||||
lost_bottles = 0
|
||||
|
||||
CoreTap.instance.path.cd do
|
||||
Utils.safe_popen_read(*git_log) do |io|
|
||||
io.each_line do |line|
|
||||
case line
|
||||
when /^commit [0-9a-f]{40}$/
|
||||
# Example match: `commit 7289b409b96a752540befef1a56b8a818baf1db7`
|
||||
if commit && formula && lost_bottles.positive? && processed_formulae.exclude?(formula)
|
||||
puts "#{commit}: bottle lost for #{formula}"
|
||||
formula_installs[f] = i["count"]
|
||||
begin
|
||||
Formula[f]
|
||||
rescue FormulaUnavailableError
|
||||
nil
|
||||
end
|
||||
processed_formulae << formula
|
||||
commit = line.split.last
|
||||
formula = nil
|
||||
when %r{^diff --git a/Formula/}
|
||||
# Example match: `diff --git a/Formula/a/aws-cdk.rb b/Formula/a/aws-cdk.rb`
|
||||
formula = line.split("/").last.chomp(".rb\n")
|
||||
formula = CoreTap.instance.formula_renames.fetch(formula, formula)
|
||||
lost_bottles = 0
|
||||
when bottle_tag_sha_regex
|
||||
# Example match: `- sha256 cellar: :any_skip_relocation, sonoma: "f0a4..."`
|
||||
next if processed_formulae.include?(formula)
|
||||
end
|
||||
@sort = " (sorted by installs in the last 90 days; top 10,000 only)"
|
||||
|
||||
case line.chr
|
||||
when "+" then lost_bottles -= 1
|
||||
when "-" then lost_bottles += 1
|
||||
end
|
||||
when /^[+] +sha256.* all: /
|
||||
# Example match: `+ sha256 cellar: :any_skip_relocation, all: "9e35..."`
|
||||
lost_bottles -= 1
|
||||
all_formulae = Formula.all(eval_all: args.eval_all?)
|
||||
end
|
||||
|
||||
# Remove deprecated formulae as we do not care if they are unbottled
|
||||
formulae = Array(formulae).reject(&:deprecated?) if formulae.present?
|
||||
all_formulae = Array(all_formulae).reject(&:deprecated?) if all_formulae.present?
|
||||
|
||||
[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 = Dependency.expand(f, cache_key: "unbottled") 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
|
||||
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?
|
||||
|
||||
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
|
||||
|
||||
if deps.blank?
|
||||
count = " (#{hash[f.name]} #{noun})" if noun
|
||||
puts "#{Tty.bold}#{Tty.green}#{name}#{Tty.reset}#{count}: ready to bottle"
|
||||
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
|
||||
|
||||
def output_lost_bottles
|
||||
ohai ":#{@bottle_tag} lost bottles"
|
||||
|
||||
bottle_tag_regex_fragment = " +sha256.* #{@bottle_tag}: "
|
||||
|
||||
# $ git log --patch --no-ext-diff -G'^ +sha256.* sonoma:' --since=@{'1 week ago'}
|
||||
git_log = %w[git log --patch --no-ext-diff]
|
||||
git_log << "-G^#{bottle_tag_regex_fragment}"
|
||||
git_log << "--since=@{'1 week ago'}"
|
||||
|
||||
bottle_tag_sha_regex = /^[+-]#{bottle_tag_regex_fragment}/
|
||||
|
||||
processed_formulae = Set.new
|
||||
commit = T.let(nil, T.nilable(String))
|
||||
formula = T.let(nil, T.nilable(String))
|
||||
lost_bottles = 0
|
||||
|
||||
CoreTap.instance.path.cd do
|
||||
Utils.safe_popen_read(*git_log) do |io|
|
||||
io.each_line do |line|
|
||||
case line
|
||||
when /^commit [0-9a-f]{40}$/
|
||||
# Example match: `commit 7289b409b96a752540befef1a56b8a818baf1db7`
|
||||
if commit && formula && lost_bottles.positive? && processed_formulae.exclude?(formula)
|
||||
puts "#{commit}: bottle lost for #{formula}"
|
||||
end
|
||||
processed_formulae << formula
|
||||
commit = line.split.last
|
||||
formula = nil
|
||||
when %r{^diff --git a/Formula/}
|
||||
# Example match: `diff --git a/Formula/a/aws-cdk.rb b/Formula/a/aws-cdk.rb`
|
||||
formula = line.split("/").last.chomp(".rb\n")
|
||||
formula = CoreTap.instance.formula_renames.fetch(formula, formula)
|
||||
lost_bottles = 0
|
||||
when bottle_tag_sha_regex
|
||||
# Example match: `- sha256 cellar: :any_skip_relocation, sonoma: "f0a4..."`
|
||||
next if processed_formulae.include?(formula)
|
||||
|
||||
case line.chr
|
||||
when "+" then lost_bottles -= 1
|
||||
when "-" then lost_bottles += 1
|
||||
end
|
||||
when /^[+] +sha256.* all: /
|
||||
# Example match: `+ sha256 cellar: :any_skip_relocation, all: "9e35..."`
|
||||
lost_bottles -= 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return if !commit || !formula || !lost_bottles.positive? || processed_formulae.include?(formula)
|
||||
|
||||
puts "#{commit}: bottle lost for #{formula}"
|
||||
end
|
||||
end
|
||||
|
||||
return if !commit || !formula || !lost_bottles.positive? || processed_formulae.include?(formula)
|
||||
|
||||
puts "#{commit}: bottle lost for #{formula}"
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
require "dev-cmd/typecheck"
|
||||
|
||||
RSpec.describe "brew typecheck" do
|
||||
RSpec.describe Homebrew::DevCmd::Typecheck do
|
||||
it_behaves_like "parseable arguments"
|
||||
end
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cmd/shared_examples/args_parse"
|
||||
require "dev-cmd/unbottled"
|
||||
|
||||
RSpec.describe "brew unbottled" do
|
||||
RSpec.describe Homebrew::DevCmd::Unbottled do
|
||||
it_behaves_like "parseable arguments"
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user