From fb35add3b696fd2840a8a14eb67e02e4a95e56fd Mon Sep 17 00:00:00 2001 From: Carlo Cabrera Date: Sat, 2 Aug 2025 03:37:31 +0800 Subject: [PATCH 1/3] Replace `ensure_formula_installed!` with `Formula#ensure_installed!` `ensure_formula_installed!` requires the `Formula` class to be loaded before being called to work properly. Let's guarantee that instead by implementing it as an instance method of the `Formula` class. See discussion at #20358. --- Library/Homebrew/dev-cmd/bottle.rb | 4 +-- Library/Homebrew/dev-cmd/bump.rb | 5 +--- Library/Homebrew/dev-cmd/cat.rb | 3 +- Library/Homebrew/extend/kernel.rb | 4 ++- Library/Homebrew/formula.rb | 38 +++++++++++++++++++++++++ Library/Homebrew/style.rb | 9 ++---- Library/Homebrew/test/utils/git_spec.rb | 2 +- Library/Homebrew/utils/git.rb | 2 +- Library/Homebrew/utils/pypi.rb | 6 ++-- 9 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Library/Homebrew/dev-cmd/bottle.rb b/Library/Homebrew/dev-cmd/bottle.rb index 429ba88d46..0a6d847e46 100644 --- a/Library/Homebrew/dev-cmd/bottle.rb +++ b/Library/Homebrew/dev-cmd/bottle.rb @@ -354,9 +354,7 @@ module Homebrew end return if gnu_tar_formula.blank? - ensure_formula_installed!(gnu_tar_formula.name, reason: "bottling") - - gnu_tar_formula + gnu_tar_formula.ensure_installed!(reason: "bottling") end sig { params(mtime: String, default_tar: T::Boolean).returns([String, T::Array[String]]) } diff --git a/Library/Homebrew/dev-cmd/bump.rb b/Library/Homebrew/dev-cmd/bump.rb index 77d1d37e9d..68e87f9a49 100644 --- a/Library/Homebrew/dev-cmd/bump.rb +++ b/Library/Homebrew/dev-cmd/bump.rb @@ -135,10 +135,7 @@ module Homebrew if args.repology? && !Utils::Curl.curl_supports_tls13? begin - unless HOMEBREW_BREWED_CURL_PATH.exist? - require "formula" - ensure_formula_installed!("curl", reason: "Repology queries") - end + Formula["curl"].ensure_installed!(reason: "Repology queries") unless HOMEBREW_BREWED_CURL_PATH.exist? rescue FormulaUnavailableError opoo "A newer `curl` is required for Repology queries." end diff --git a/Library/Homebrew/dev-cmd/cat.rb b/Library/Homebrew/dev-cmd/cat.rb index 41073c3600..07a87d99c2 100644 --- a/Library/Homebrew/dev-cmd/cat.rb +++ b/Library/Homebrew/dev-cmd/cat.rb @@ -31,8 +31,7 @@ module Homebrew ENV["BAT_CONFIG_PATH"] = Homebrew::EnvConfig.bat_config_path ENV["BAT_THEME"] = Homebrew::EnvConfig.bat_theme require "formula" - ensure_formula_installed!( - "bat", + Formula["bat"].ensure_installed!( reason: "displaying / source", # The user might want to capture the output of `brew cat ...` # Redirect stdout to stderr diff --git a/Library/Homebrew/extend/kernel.rb b/Library/Homebrew/extend/kernel.rb index 90d0988643..1e2786e1fb 100644 --- a/Library/Homebrew/extend/kernel.rb +++ b/Library/Homebrew/extend/kernel.rb @@ -449,6 +449,8 @@ module Kernel } def ensure_formula_installed!(formula_name, reason: "", latest: false, output_to_stderr: true, quiet: false) + # odeprecated "ensure_formula_installed!", "Formula[\"#{formula_name}\"].ensure_installed!" + if output_to_stderr || quiet file = if quiet File::NULL @@ -496,7 +498,7 @@ module Kernel return executable if executable.exist? require "formula" - ensure_formula_installed!(formula_name, reason:, latest:).opt_bin/name + Formula[formula_name].ensure_installed!(reason:, latest:).opt_bin/name end sig { returns(T::Array[Pathname]) } diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 22dd48381c..51e0ec951b 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -302,6 +302,44 @@ class Formula Requirement.clear_cache end + # Ensure the given formula is installed + # This is useful for installing a utility formula (e.g. `shellcheck` for `brew style`) + sig { + params( + reason: String, + latest: T::Boolean, + output_to_stderr: T::Boolean, + quiet: T::Boolean, + ).returns(T.self_type) + } + def ensure_installed!(reason: "", latest: false, output_to_stderr: true, quiet: false) + if output_to_stderr || quiet + file = if quiet + File::NULL + else + $stderr + end + # Call this method itself with redirected stdout + redirect_stdout(file) do + return ensure_installed!(latest:, reason:, output_to_stderr: false) + end + end + + reason = " for #{reason}" if reason.present? + + unless any_version_installed? + ohai "Installing `#{name}`#{reason}..." + safe_system HOMEBREW_BREW_FILE, "install", "--formula", full_name + end + + if latest && !latest_version_installed? + ohai "Upgrading `#{name}`#{reason}..." + safe_system HOMEBREW_BREW_FILE, "upgrade", "--formula", full_name + end + + self + end + private # Allow full name logic to be re-used between names, aliases and installed aliases. diff --git a/Library/Homebrew/style.rb b/Library/Homebrew/style.rb index 2b5313b52b..80600a494c 100644 --- a/Library/Homebrew/style.rb +++ b/Library/Homebrew/style.rb @@ -322,21 +322,18 @@ module Homebrew def self.shellcheck require "formula" - ensure_formula_installed!("shellcheck", latest: true, - reason: "shell style checks").opt_bin/"shellcheck" + Formula["shellcheck"].ensure_installed!(latest: true, reason: "shell style checks").opt_bin/"shellcheck" end def self.shfmt require "formula" - ensure_formula_installed!("shfmt", latest: true, - reason: "formatting shell scripts") + Formula["shfmt"].ensure_installed!(latest: true, reason: "formatting shell scripts") HOMEBREW_LIBRARY/"Homebrew/utils/shfmt.sh" end def self.actionlint require "formula" - ensure_formula_installed!("actionlint", latest: true, - reason: "GitHub Actions checks").opt_bin/"actionlint" + Formula["actionlint"].ensure_installed!(latest: true, reason: "GitHub Actions checks").opt_bin/"actionlint" end # Collection of style offenses. diff --git a/Library/Homebrew/test/utils/git_spec.rb b/Library/Homebrew/test/utils/git_spec.rb index 2efec2fa07..238f580277 100644 --- a/Library/Homebrew/test/utils/git_spec.rb +++ b/Library/Homebrew/test/utils/git_spec.rb @@ -186,7 +186,7 @@ RSpec.describe Utils::Git do unless ENV["HOMEBREW_TEST_GENERIC_OS"] it "installs git" do expect(described_class).to receive(:available?).and_return(false) - expect(described_class).to receive(:ensure_formula_installed!).with("git") + allow_any_instance_of(Formula).to receive(:ensure_installed!) expect(described_class).to receive(:available?).and_return(true) described_class.ensure_installed! diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb index 4eeca69d1d..1a7da5cd32 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -104,7 +104,7 @@ module Utils raise "Refusing to install Git on a generic OS." if ENV["HOMEBREW_TEST_GENERIC_OS"] require "formula" - ensure_formula_installed!("git") + Formula["git"].ensure_installed! clear_available_cache rescue raise "Git is unavailable" diff --git a/Library/Homebrew/utils/pypi.rb b/Library/Homebrew/utils/pypi.rb index ec3cad9409..d1548868de 100644 --- a/Library/Homebrew/utils/pypi.rb +++ b/Library/Homebrew/utils/pypi.rb @@ -153,7 +153,7 @@ module PyPI @version ||= T.let(match[2], T.nilable(String)) elsif @is_url require "formula" - ensure_formula_installed!(@python_name) + Formula[@python_name].ensure_installed! # The URL might be a source distribution hosted somewhere; # try and use `pip install -q --no-deps --dry-run --report ...` to get its @@ -262,7 +262,7 @@ module PyPI odie "Missing #{missing_msg}" unless install_dependencies ohai "Installing #{missing_msg}" require "formula" - missing_dependencies.each(&:ensure_formula_installed!) + missing_dependencies.each { |dep| Formula[dep].ensure_installed! } end python_deps = formula.deps @@ -337,7 +337,7 @@ module PyPI end require "formula" - ensure_formula_installed!(python_name) + Formula[python_name].ensure_installed! # Resolve the dependency tree of all input packages show_info = !print_only && !silent From 0fd3b8e4af1e1a9f3cf64c8299e67530aa786ae7 Mon Sep 17 00:00:00 2001 From: Carlo Cabrera Date: Sat, 2 Aug 2025 04:00:52 +0800 Subject: [PATCH 2/3] Uncomment `odeprecated` This is close enough to #20331 to do now. --- Library/Homebrew/extend/kernel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/extend/kernel.rb b/Library/Homebrew/extend/kernel.rb index 1e2786e1fb..2e6540b43e 100644 --- a/Library/Homebrew/extend/kernel.rb +++ b/Library/Homebrew/extend/kernel.rb @@ -449,7 +449,7 @@ module Kernel } def ensure_formula_installed!(formula_name, reason: "", latest: false, output_to_stderr: true, quiet: false) - # odeprecated "ensure_formula_installed!", "Formula[\"#{formula_name}\"].ensure_installed!" + odeprecated "ensure_formula_installed!", "Formula[\"#{formula_name}\"].ensure_installed!" if output_to_stderr || quiet file = if quiet From fd80dd9eefb0d5aed1b359ae3532edbe258a9803 Mon Sep 17 00:00:00 2001 From: Carlo Cabrera Date: Sat, 2 Aug 2025 04:30:26 +0800 Subject: [PATCH 3/3] Fix test failure --- Library/Homebrew/test/utils/git_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/test/utils/git_spec.rb b/Library/Homebrew/test/utils/git_spec.rb index 238f580277..32d2749f52 100644 --- a/Library/Homebrew/test/utils/git_spec.rb +++ b/Library/Homebrew/test/utils/git_spec.rb @@ -186,7 +186,10 @@ RSpec.describe Utils::Git do unless ENV["HOMEBREW_TEST_GENERIC_OS"] it "installs git" do expect(described_class).to receive(:available?).and_return(false) - allow_any_instance_of(Formula).to receive(:ensure_installed!) + allow(CoreTap.instance).to receive(:installed?).and_return(true) + formula_double = instance_double(Formula) + allow(Formula).to receive(:[]).with("git").and_return(formula_double) + allow(formula_double).to receive(:ensure_installed!).and_return(formula_double) expect(described_class).to receive(:available?).and_return(true) described_class.ensure_installed!