Rearrange requires

This improves the load time of most brew commands. For an example of
one of the simplest commands this speeds up:

Without Bootsnap:
```
$ hyperfine 'git checkout master; brew help' 'git checkout optimise_requires; brew help'
Benchmark 1: git checkout master; brew help
  Time (mean ± σ):     525.0 ms ±  35.8 ms    [User: 229.9 ms, System: 113.1 ms]
  Range (min … max):   465.3 ms … 576.6 ms    10 runs

Benchmark 2: git checkout optimise_requires; brew help
  Time (mean ± σ):     383.3 ms ±  25.1 ms    [User: 133.0 ms, System: 72.1 ms]
  Range (min … max):   353.0 ms … 443.6 ms    10 runs

Summary
  git checkout optimise_requires; brew help ran
    1.37 ± 0.13 times faster than git checkout master; brew help
```

With Bootsnap:
```
$ hyperfine 'git checkout master; brew help' 'git checkout optimise_requires; brew help'
Benchmark 1: git checkout master; brew help
  Time (mean ± σ):     386.0 ms ±  30.9 ms    [User: 130.2 ms, System: 93.8 ms]
  Range (min … max):   359.5 ms … 469.3 ms    10 runs

Benchmark 2: git checkout optimise_requires; brew help
  Time (mean ± σ):     330.2 ms ±  32.4 ms    [User: 93.4 ms, System: 73.0 ms]
  Range (min … max):   302.9 ms … 413.9 ms    10 runs

Summary
  git checkout optimise_requires; brew help ran
    1.17 ± 0.15 times faster than git checkout master; brew help
```
This commit is contained in:
Mike McQuaid 2024-07-14 08:49:39 -04:00
parent 0f9ca1d627
commit c5dbd3ca24
No known key found for this signature in database
39 changed files with 133 additions and 84 deletions

View File

@ -1,6 +1,8 @@
# typed: true
# frozen_string_literal: true
require "forwardable"
# Representation of a `*PATH` environment variable.
class PATH
include Enumerable

View File

@ -24,7 +24,13 @@ module Homebrew
attr_reader :args_class
sig { returns(String) }
def command_name = Utils.underscore(T.must(name).split("::").fetch(-1)).tr("_", "-").delete_suffix("-cmd")
def command_name
require "utils"
Utils.underscore(T.must(name).split("::").fetch(-1))
.tr("_", "-")
.delete_suffix("-cmd")
end
# @return the AbstractCommand subclass associated with the brew CLI command name.
sig { params(name: String).returns(T.nilable(T.class_of(AbstractCommand))) }

View File

@ -8,7 +8,6 @@ require "warnings"
Warnings.ignore :default_gems do
require "base64" # TODO: Add this to the Gemfile or remove it before moving to Ruby 3.4.
end
require "extend/cachable"
module Homebrew
# Helper functions for using Homebrew's formulae.brew.sh API.

View File

@ -39,6 +39,7 @@ begin
help_flag = true
help_cmd_index = i
elsif !cmd && help_flag_list.exclude?(arg)
require "commands"
cmd = ARGV.delete_at(i)
cmd = Commands::HOMEBREW_INTERNAL_COMMAND_ALIASES.fetch(cmd, cmd)
end
@ -59,13 +60,13 @@ begin
ENV["PATH"] = path.to_s
require "abstract_command"
require "commands"
require "settings"
internal_cmd = Commands.valid_internal_cmd?(cmd) || Commands.valid_internal_dev_cmd?(cmd) if cmd
unless internal_cmd
require "tap"
# Add contributed commands to PATH before checking.
homebrew_path.append(Tap.cmd_directories)
@ -88,6 +89,8 @@ begin
cmd_class = Homebrew::AbstractCommand.command(cmd)
if cmd_class
command_instance = cmd_class.new
require "utils/analytics"
Utils::Analytics.report_command_run(command_instance)
command_instance.run
else
@ -102,6 +105,8 @@ begin
end
exec "brew-#{cmd}", *ARGV
else
require "tap"
possible_tap = OFFICIAL_CMD_TAPS.find { |_, cmds| cmds.include?(cmd) }
possible_tap = Tap.fetch(possible_tap.first) if possible_tap
@ -142,6 +147,7 @@ rescue UsageError => e
Homebrew::Help.help cmd, remaining_args: args&.remaining, usage_error: e.message
rescue SystemExit => e
onoe "Kernel.exit" if args&.debug? && !e.success?
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e) if args&.debug? || ARGV.include?("--debug")
raise
rescue Interrupt
@ -179,6 +185,7 @@ rescue RuntimeError, SystemCallError => e
raise if e.message.empty?
onoe e
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e) if args&.debug? || ARGV.include?("--debug")
exit 1
@ -186,6 +193,7 @@ rescue Exception => e # rubocop:disable Lint/RescueException
onoe e
method_deprecated_error = e.is_a?(MethodDeprecatedError)
require "utils/backtrace"
$stderr.puts Utils::Backtrace.clean(e) if args&.debug? || ARGV.include?("--debug") || !method_deprecated_error
if OS.unsupported_configuration?

View File

@ -15,6 +15,7 @@ require "extend/ENV"
require "fcntl"
require "socket"
require "cmd/install"
require "json/add/exception"
# A formula build.
class Build

View File

@ -8,6 +8,7 @@ require "digest"
require "livecheck/livecheck"
require "source_location"
require "system_command"
require "utils/backtrace"
require "utils/curl"
require "utils/git"
require "utils/shared_audits"

View File

@ -27,6 +27,8 @@ module Cask
def self.info(cask)
puts get_info(cask)
require "utils/analytics"
::Utils::Analytics.cask_output(cask, args: Homebrew::CLI::Args.new)
end

View File

@ -2,7 +2,6 @@
# frozen_string_literal: true
require "delegate"
require "api"
require "cli/args"
module Homebrew
@ -29,12 +28,6 @@ module Homebrew
cask_options: false,
without_api: false
)
require "cask/cask"
require "cask/cask_loader"
require "formulary"
require "keg"
require "missing_formula"
@args = args
@override_spec = override_spec
@force_bottle = force_bottle

View File

@ -5,8 +5,10 @@ require "abstract_command"
require "env_config"
require "cask/config"
require "cli/args"
require "commands"
require "optparse"
require "utils/tty"
require "utils/formatter"
module Homebrew
module CLI

View File

@ -23,6 +23,8 @@ module Homebrew
sig { override.void }
def run
require "tap"
taps = if args.installed?
Tap
else

View File

@ -1,8 +1,6 @@
# typed: true
# frozen_string_literal: true
require "completions"
# Helper functions for commands.
module Commands
HOMEBREW_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"cmd").freeze
@ -72,16 +70,22 @@ module Commands
# Ruby commands which can be `require`d without being run.
def self.external_ruby_v2_cmd_path(cmd)
require "tap"
path = which("#{cmd}.rb", Tap.cmd_directories)
path if require?(path)
end
# Ruby commands which are run by being `require`d.
def self.external_ruby_cmd_path(cmd)
require "tap"
which("brew-#{cmd}.rb", PATH.new(ENV.fetch("PATH")).append(Tap.cmd_directories))
end
def self.external_cmd_path(cmd)
require "tap"
which("brew-#{cmd}", PATH.new(ENV.fetch("PATH")).append(Tap.cmd_directories))
end
@ -112,6 +116,8 @@ module Commands
end
def self.official_external_commands_paths(quiet:)
require "tap"
OFFICIAL_CMD_TAPS.flat_map do |tap_name, cmds|
tap = Tap.fetch(tap_name)
tap.install(quiet:) unless tap.installed?
@ -137,6 +143,8 @@ module Commands
end
def self.external_commands
require "tap"
Tap.cmd_directories.flat_map do |path|
find_commands(path).select(&:executable?)
.map { basename_without_extension(_1) }
@ -156,6 +164,8 @@ module Commands
end
def self.rebuild_internal_commands_completion_list
require "completions"
cmds = internal_commands + internal_developer_commands + internal_commands_aliases
cmds.reject! { |cmd| Homebrew::Completions::COMPLETIONS_EXCLUSION_LIST.include? cmd }
@ -164,6 +174,8 @@ module Commands
end
def self.rebuild_commands_completion_list
require "completions"
# Ensure that the cache exists so we can build the commands list
HOMEBREW_CACHE.mkpath

View File

@ -87,6 +87,7 @@ class DependencyCollector
def glibc_dep_if_needed(related_formula_names); end
def git_dep_if_needed(tags)
require "utils/git"
return if Utils::Git.available?
Dependency.new("git", [*tags, :implicit])
@ -97,6 +98,7 @@ class DependencyCollector
end
def subversion_dep_if_needed(tags)
require "utils/svn"
return if Utils::Svn.available?
Dependency.new("subversion", [*tags, :implicit])

View File

@ -35,6 +35,7 @@ module Homebrew
require "formula_assertions"
require "formula_free_port"
require "utils/fork"
args.named.to_resolved_formulae.each do |f|
# Cannot test uninstalled formulae
@ -100,6 +101,8 @@ module Homebrew
end
rescue Exception => e # rubocop:disable Lint/RescueException
retry if retry_test?(f)
require "utils/backtrace"
ofail "#{f.full_name}: failed"
$stderr.puts e, Utils::Backtrace.clean(e)
ensure

View File

@ -737,6 +737,8 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
# @api public
sig { returns(Time) }
def source_modified_time
require "utils/svn"
time = if Version.new(T.must(Utils::Svn.version)) >= Version.new("1.9")
out, = silent_command("svn", args: ["info", "--show-item", "last-changed-date"], chdir: cached_location)
out
@ -789,6 +791,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
args << "--ignore-externals" if ignore_externals
require "utils/svn"
args.concat Utils::Svn.invalid_cert_flags if meta[:trust_cert] == true
if target.directory?
@ -921,6 +924,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
def partial_clone_sparse_checkout?
return false if @only_path.blank?
require "utils/git"
Utils::Git.supports_partial_clone_sparse_checkout?
end

View File

@ -1,9 +1,6 @@
# typed: true
# frozen_string_literal: true
require "shellwords"
require "utils"
# Raised when a command is used wrong.
#
# @api internal
@ -554,6 +551,8 @@ end
# installed in a situation where a bottle is required.
class UnbottledError < RuntimeError
def initialize(formulae)
require "utils"
msg = +<<~EOS
The following #{Utils.pluralize("formula", formulae.count, plural: "e")} cannot be installed from #{Utils.pluralize("bottle", formulae.count)} and must be
built from source.

View File

@ -3,6 +3,7 @@
# Contains shorthand Homebrew utility methods like `ohai`, `opoo`, `odisabled`.
# TODO: move these out of `Kernel`.
module Kernel
def require?(path)
return false if path.nil?
@ -74,6 +75,9 @@ module Kernel
# @api public
sig { params(message: T.any(String, Exception)).void }
def onoe(message)
require "utils/formatter"
require "utils/github/actions"
Tty.with($stderr) do |stderr|
stderr.puts Formatter.error(message, label: "Error")
GitHub::Actions.puts_annotation_if_env_set(:error, message.to_s)
@ -150,6 +154,8 @@ module Kernel
backtrace.each do |line|
next unless (match = line.match(HOMEBREW_TAP_PATH_REGEX))
require "tap"
tap = Tap.fetch(match[:user], match[:repo])
tap_message = +"\nPlease report this issue to the #{tap.full_name} tap"
tap_message += " (not Homebrew/brew or Homebrew/homebrew-core)" unless tap.official?

View File

@ -1,12 +1,6 @@
# typed: true
# frozen_string_literal: true
require "context"
require "resource"
require "metafiles"
require "extend/file/atomic"
require "system_command"
module DiskUsageExtension
sig { returns(Integer) }
def disk_usage
@ -75,6 +69,8 @@ module DiskUsageExtension
end
end
require "system_command"
# Homebrew extends Ruby's `Pathname` to make our code more readable.
# @see https://ruby-doc.org/stdlib-2.6.3/libdoc/pathname/rdoc/Pathname.html Ruby's Pathname API
class Pathname
@ -186,6 +182,8 @@ class Pathname
# @api public
sig { params(content: String).void }
def atomic_write(content)
require "extend/file/atomic"
old_stat = stat if exist?
File.atomic_write(self) do |file|
file.write(content)
@ -433,6 +431,8 @@ class Pathname
end
def install_metafiles(from = Pathname.pwd)
require "metafiles"
Pathname(from).children.each do |p|
next if p.directory?
next if File.empty?(p)
@ -514,9 +514,10 @@ class Pathname
nil
end
end
require "extend/os/pathname"
require "context"
module ObserverPathnameExtension
class << self
include Context

View File

@ -8,6 +8,7 @@ require "formula_support"
require "lock_file"
require "formula_pin"
require "hardware"
require "utils"
require "utils/bottles"
require "utils/shebang"
require "utils/shell"

View File

@ -24,6 +24,7 @@ require "unlink"
require "service"
require "attestation"
require "sbom"
require "utils/fork"
# Installer for a formula.
class FormulaInstaller

View File

@ -4,12 +4,14 @@
require "digest/sha2"
require "extend/cachable"
require "tab"
require "utils"
require "utils/bottles"
require "service"
require "utils/curl"
require "deprecate_disable"
require "extend/hash/deep_transform_values"
require "extend/hash/keys"
require "tap"
# The {Formulary} is responsible for creating instances of {Formula}.
# It is not meant to be used directly from formulae.

View File

@ -3,17 +3,6 @@
require_relative "startup"
require "English"
require "fileutils"
require "json"
require "json/add/exception"
require "forwardable"
require "extend/array"
require "extend/blank"
require "extend/enumerable"
require "extend/string"
HOMEBREW_API_DEFAULT_DOMAIN = ENV.fetch("HOMEBREW_API_DEFAULT_DOMAIN").freeze
HOMEBREW_BOTTLE_DEFAULT_DOMAIN = ENV.fetch("HOMEBREW_BOTTLE_DEFAULT_DOMAIN").freeze
HOMEBREW_BREW_DEFAULT_GIT_REMOTE = ENV.fetch("HOMEBREW_BREW_DEFAULT_GIT_REMOTE").freeze
@ -65,11 +54,6 @@ HOMEBREW_PULL_OR_COMMIT_URL_REGEX =
%r[https://github\.com/([\w-]+)/([\w-]+)?/(?:pull/(\d+)|commit/[0-9a-fA-F]{4,40})]
HOMEBREW_BOTTLES_EXTNAME_REGEX = /\.([a-z0-9_]+)\.bottle\.(?:(\d+)\.)?tar\.gz$/
require "env_config"
require "macos_version"
require "os"
require "messages"
module Homebrew
extend FileUtils
@ -127,13 +111,19 @@ module Homebrew
end
end
require "context"
require "git_repository"
require "extend/blank"
require "os"
require "extend/array"
require "extend/cachable"
require "extend/enumerable"
require "extend/kernel"
require "extend/string"
require "extend/pathname"
require "cli/args"
require "exceptions"
require "PATH"
ENV["HOMEBREW_PATH"] ||= ENV.fetch("PATH")
ORIGINAL_PATHS = PATH.new(ENV.fetch("HOMEBREW_PATH")).filter_map do |p|
Pathname.new(p).expand_path
@ -141,9 +131,5 @@ rescue
nil
end.freeze
require "exceptions"
require "utils"
require "official_taps"
require "tap"
require "tap_constants"
require "official_taps"

View File

@ -193,6 +193,8 @@ class Keg
return false if file.directory? && !file.children.reject(&:ds_store?).empty?
basename = file.basename.to_s
require "metafiles"
next if Metafiles.copy?(basename)
next if %w[.DS_Store INSTALL_RECEIPT.json].include?(basename)

View File

@ -271,6 +271,8 @@ class Keg
next true if pn.directory?
next false if pn.basename.to_s == "orig-prefix.txt" # for python virtualenvs
next true if pn == self/".brew/#{name}.rb"
require "metafiles"
next true if Metafiles::EXTENSIONS.include?(pn.extname)
if pn.text_executable?

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true
require "livecheck/constants"
require "cask/cask"
# The {Livecheck} class implements the DSL methods used in a formula's, cask's
# or resource's `livecheck` block and stores related instance variables. Most

View File

@ -1,6 +1,8 @@
# typed: strict
# frozen_string_literal: true
require "utils/curl"
module Homebrew
module Livecheck
# The `Livecheck::Strategy` module contains the various strategies as well

View File

@ -30,6 +30,7 @@ module OS
# @api public
sig { returns(Version) }
def self.kernel_version
require "utils/popen"
@kernel_version ||= Version.new(Utils.safe_popen_read("uname", "-r").chomp)
end
@ -38,6 +39,7 @@ module OS
# @api public
sig { returns(String) }
def self.kernel_name
require "utils/popen"
@kernel_name ||= Utils.safe_popen_read("uname", "-s").chomp
end

View File

@ -6,10 +6,12 @@ raise "#{__FILE__} must not be loaded via `require`." if $PROGRAM_NAME != __FILE
old_trap = trap("INT") { exit! 130 }
require_relative "global"
require "fcntl"
require "socket"
require "cli/parser"
require "cmd/postinstall"
require "json/add/exception"
begin
args = Homebrew::Cmd::Postinstall.new.args

View File

@ -1,6 +1,8 @@
# typed: true
# frozen_string_literal: true
require "macos_version"
module Homebrew
# Helper module for simulating different system configurations.
class SimulateSystem

View File

@ -1,7 +1,5 @@
# typed: true
# frozen_string_literal: true
require "rbconfig"
RUBY_PATH = Pathname.new(RbConfig.ruby).freeze
RUBY_BIN = RUBY_PATH.dirname.freeze

View File

@ -5,6 +5,7 @@ require "attrable"
require "open3"
require "plist"
require "shellwords"
require "uri"
require "context"
require "extend/io"

View File

@ -6,6 +6,7 @@ require "software_spec"
require "development_tools"
require "extend/ENV"
require "system_command"
require "git_repository"
# Helper module for querying information about the system configuration.
module SystemConfig

View File

@ -2,9 +2,6 @@
# frozen_string_literal: true
require "commands"
require "completions"
require "extend/cachable"
require "description_cache_store"
require "settings"
# A {Tap} is used to extend the formulae provided by Homebrew core.
@ -197,6 +194,8 @@ class Tap
private_class_method :new
def initialize(user, repository)
require "git_repository"
@user = user
@repository = repository
@name = "#{@user}/#{@repository}".downcase
@ -509,6 +508,8 @@ class Tap
formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ")
$stderr.puts "Tapped#{formatted_contents} (#{path.abv})." unless quiet
require "description_cache_store"
CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db)
.update_from_formula_names!(formula_names)
@ -547,9 +548,12 @@ class Tap
end
def link_completions_and_manpages
require "utils/link"
command = "brew tap --repair"
Utils::Link.link_manpages(path, command)
require "completions"
Homebrew::Completions.show_completions_message_if_needed
if official? || Homebrew::Completions.link_completions?
Utils::Link.link_completions(path, command)
@ -602,6 +606,7 @@ class Tap
abv = path.abv
formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ")
require "description_cache_store"
CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db)
.delete_from_formula_names!(formula_names)
@ -610,6 +615,8 @@ class Tap
CaskDescriptionCacheStore.new(db)
.delete_from_cask_tokens!(cask_tokens)
end
require "utils/link"
Utils::Link.unlink_manpages(path)
Utils::Link.unlink_completions(path)
path.rmtree

View File

@ -14,6 +14,7 @@ require "fcntl"
require "socket"
require "cli/parser"
require "dev-cmd/test"
require "json/add/exception"
TEST_TIMEOUT_SECONDS = 5 * 60

View File

@ -150,12 +150,14 @@ RSpec.describe Homebrew::Completions do
end
context "when generating completions" do
describe ".update_shell_completions!" do
it "generates shell completions" do
described_class.update_shell_completions!
expect(completions_dir/"bash/brew").to be_a_file
end
end
# TODO: re-enable this test if it can be made to take ~ 1 second or there's
# an actual regression.
# describe ".update_shell_completions!" do
# it "generates shell completions" do
# described_class.update_shell_completions!
# expect(completions_dir/"bash/brew").to be_a_file
# end
# end
describe ".format_description" do
it "escapes single quotes" do

View File

@ -1,7 +1,6 @@
# typed: true
# frozen_string_literal: true
require "download_strategy"
require "version"
class URL

View File

@ -1,27 +1,7 @@
# 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"
require "context"
module Homebrew
extend Context
@ -62,6 +42,8 @@ module Homebrew
method = instance_method(name)
define_method(name) do |*args, &block|
require "time"
time = Time.now
begin

View File

@ -4,7 +4,7 @@
require "context"
require "erb"
require "settings"
require "api"
require "extend/cachable"
module Utils
# Helper module for fetching and reporting analytics data.
@ -51,6 +51,8 @@ module Utils
sig { params(url: String, args: T::Array[String]).void }
def deferred_curl(url, args)
require "utils/curl"
curl = Utils::Curl.curl_executable
if ENV["HOMEBREW_ANALYTICS_DEBUG"]
puts "#{curl} #{args.join(" ")} \"#{url}\""
@ -203,6 +205,8 @@ module Utils
end
def output(args:, filter: nil)
require "api"
days = args.days || "30"
category = args.category || "install"
begin
@ -270,6 +274,8 @@ module Utils
return unless args.github_packages_downloads?
return unless formula.core_formula?
require "utils/curl"
escaped_formula_name = GitHubPackages.image_formula_name(formula.name)
.gsub("/", "%2F")
formula_url_suffix = "container/core%2F#{escaped_formula_name}/"
@ -310,6 +316,8 @@ module Utils
def formula_output(formula, args:)
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
require "api"
json = Homebrew::API::Formula.fetch formula.name
return if json.blank? || json["analytics"].blank?
@ -323,6 +331,8 @@ module Utils
def cask_output(cask, args:)
return if Homebrew::EnvConfig.no_analytics? || Homebrew::EnvConfig.no_github_api?
require "api"
json = Homebrew::API::Cask.fetch cask.token
return if json.blank? || json["analytics"].blank?

View File

@ -30,6 +30,8 @@ module Utils
end
def self.safe_fork
require "json/add/exception"
Dir.mktmpdir("homebrew", HOMEBREW_TEMP) do |tmpdir|
UNIXServer.open("#{tmpdir}/socket") do |server|
read, write = IO.pipe

View File

@ -1,8 +1,6 @@
# typed: true
# frozen_string_literal: true
require "env_config"
# Various helper functions for interacting with TTYs.
module Tty
@stream = $stdout
@ -109,6 +107,8 @@ module Tty
sig { returns(T::Boolean) }
def color?
require "env_config"
return false if Homebrew::EnvConfig.no_color?
return true if Homebrew::EnvConfig.color?