brew/Library/Homebrew/utils.rb
apainintheneck 85bd4c7e1f utils/backtrace: scrub sorbet-runtime from backtrace
Ever since we started using this at runtime it's been polluting
the backtrace output. This makes it harder to debug errors and
increases the amount of info users have to paste into the box
when filing an issue.

This is a very direct approach. Essentially, we strip out
everything related to the `sorbet-runtime` gem whenever the top
line in the backtrace is unrelated to sorbet-runtime.

The hope is that this will allow errors related to sorbet to
be diagnosed easily while also reducing the backtrace size
for all other types of errors.

Sometimes it is useful to see the full backtrace though.
For those cases, we include the full backtrace when
`--verbose` is passed in and print a warning that the
Sorbet lines have been removed from the backtrace the
first time they are removed.

Note: This requires gems to be set up so that the call to
`Gem.paths.home` works correctly. For that reason, it must
be included after `utils/gems` which is included in
`standalone/load_path` already.
2023-09-21 21:07:22 -07:00

171 lines
5.4 KiB
Ruby

# typed: true
# frozen_string_literal: true
require "time"
require "utils/analytics"
require "utils/backtrace"
require "utils/curl"
require "utils/fork"
require "utils/formatter"
require "utils/gems"
require "utils/git"
require "utils/git_repository"
require "utils/github"
require "utils/gzip"
require "utils/inreplace"
require "utils/link"
require "utils/popen"
require "utils/repology"
require "utils/svn"
require "utils/tty"
require "tap_constants"
require "PATH"
require "extend/kernel"
module Homebrew
extend Context
def self._system(cmd, *args, **options)
pid = fork do
yield if block_given?
args.map!(&:to_s)
begin
exec(cmd, *args, **options)
rescue
nil
end
exit! 1 # never gets here unless exec failed
end
Process.wait(T.must(pid))
$CHILD_STATUS.success?
end
def self.system(cmd, *args, **options)
if verbose?
puts "#{cmd} #{args * " "}".gsub(RUBY_PATH, "ruby")
.gsub($LOAD_PATH.join(File::PATH_SEPARATOR).to_s, "$LOAD_PATH")
end
_system(cmd, *args, **options)
end
# rubocop:disable Style/GlobalVars
sig { params(the_module: Module, pattern: Regexp).void }
def self.inject_dump_stats!(the_module, pattern)
@injected_dump_stat_modules ||= {}
@injected_dump_stat_modules[the_module] ||= []
injected_methods = @injected_dump_stat_modules[the_module]
the_module.module_eval do
instance_methods.grep(pattern).each do |name|
next if injected_methods.include? name
method = instance_method(name)
define_method(name) do |*args, &block|
time = Time.now
begin
method.bind(self).call(*args, &block)
ensure
$times[name] ||= 0
$times[name] += Time.now - time
end
end
end
end
return unless $times.nil?
$times = {}
at_exit do
col_width = [$times.keys.map(&:size).max.to_i + 2, 15].max
$times.sort_by { |_k, v| v }.each do |method, time|
puts format("%<method>-#{col_width}s %<time>0.4f sec", method: "#{method}:", time: time)
end
end
end
# rubocop:enable Style/GlobalVars
end
module Utils
# Removes the rightmost segment from the constant expression in the string.
#
# deconstantize('Net::HTTP') # => "Net"
# deconstantize('::Net::HTTP') # => "::Net"
# deconstantize('String') # => ""
# deconstantize('::String') # => ""
# deconstantize('') # => ""
#
# See also #demodulize.
# @see https://github.com/rails/rails/blob/b0dd7c7/activesupport/lib/active_support/inflector/methods.rb#L247-L258
# `ActiveSupport::Inflector.deconstantize`
sig { params(path: String).returns(String) }
def self.deconstantize(path)
T.must(path[0, path.rindex("::") || 0]) # implementation based on the one in facets' Module#spacename
end
# Removes the module part from the expression in the string.
#
# demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"
# demodulize('Inflections') # => "Inflections"
# demodulize('::Inflections') # => "Inflections"
# demodulize('') # => ""
#
# See also #deconstantize.
# @see https://github.com/rails/rails/blob/b0dd7c7/activesupport/lib/active_support/inflector/methods.rb#L230-L245
# `ActiveSupport::Inflector.demodulize`
sig { params(path: String).returns(String) }
def self.demodulize(path)
if (i = path.rindex("::"))
T.must(path[(i + 2)..])
else
path
end
end
# A lightweight alternative to `ActiveSupport::Inflector.pluralize`:
# Combines `stem` with the `singular` or `plural` suffix based on `count`.
# Adds a prefix of the count value if `include_count` is set to true.
sig {
params(stem: String, count: Integer, plural: String, singular: String, include_count: T::Boolean).returns(String)
}
def self.pluralize(stem, count, plural: "s", singular: "", include_count: false)
prefix = include_count ? "#{count} " : ""
suffix = (count == 1) ? singular : plural
"#{prefix}#{stem}#{suffix}"
end
sig { params(author: String).returns({ email: String, name: String }) }
def self.parse_author!(author)
match_data = /^(?<name>[^<]+?)[ \t]*<(?<email>[^>]+?)>$/.match(author)
if match_data
name = match_data[:name]
email = match_data[:email]
end
raise UsageError, "Unable to parse name and email." if name.blank? && email.blank?
{ name: T.must(name), email: T.must(email) }
end
# Makes an underscored, lowercase form from the expression in the string.
#
# Changes '::' to '/' to convert namespaces to paths.
#
# underscore('ActiveModel') # => "active_model"
# underscore('ActiveModel::Errors') # => "active_model/errors"
#
# @see https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/inflector/methods.rb#L81-L100
# `ActiveSupport::Inflector.underscore`
sig { params(camel_cased_word: T.any(String, Symbol)).returns(String) }
def self.underscore(camel_cased_word)
return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
word = camel_cased_word.to_s.gsub("::", "/")
word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) do
T.must(::Regexp.last_match(1) || ::Regexp.last_match(2)) << "_"
end
word.tr!("-", "_")
word.downcase!
word
end
end