From 5a11cd69a189a19d362cc53cd6ef64e8b97969db Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Wed, 6 May 2026 14:04:01 +0200 Subject: [PATCH 1/4] info: add Binaries section listing executables List the formula's bin/ and sbin/ executables in a new `==> Binaries` section before `==> Caveats`. For installed formulae the list is read from the linked keg; for not-installed formulae `--fetch-manifest` triggers a fallback that reads from the bottle manifest, mirroring how `Bottle Size` and `Installed Size` already behave. --- Library/Homebrew/bottle.rb | 8 +++ Library/Homebrew/cmd/info.rb | 14 +++++ Library/Homebrew/resource.rb | 5 ++ Library/Homebrew/test/cmd/info_spec.rb | 73 ++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/Library/Homebrew/bottle.rb b/Library/Homebrew/bottle.rb index af967aab12625..613a87d8f4b20 100644 --- a/Library/Homebrew/bottle.rb +++ b/Library/Homebrew/bottle.rb @@ -192,6 +192,14 @@ def installed_size resource.installed_size end + sig { returns(T.nilable(T::Array[String])) } + def path_exec_files + resource = github_packages_manifest_resource + return unless resource&.downloaded? + + resource.path_exec_files + end + sig { returns(Filename) } def filename Filename.create(T.cast(resource.owner, Formula), @tag, @spec.rebuild) diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index e75b849363afe..7cd8d4abaf120 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -596,6 +596,20 @@ def info_formula(formula) Options.dump_for_formula formula end + binaries_keg = kegs.find(&:linked?) || kegs.last + binaries = if binaries_keg + binary_files = [binaries_keg/"bin", binaries_keg/"sbin"].select(&:directory?).flat_map do |dir| + dir.children.select { |child| child.file? && child.executable? } + end + binary_files.map { |path| path.basename.to_s } + elsif (path_exec_files = formula.bottle&.path_exec_files) + path_exec_files.map { |path| File.basename(path) } + end + if binaries.present? + binaries = binaries.sort.uniq + ohai "Binaries", Formatter.columns(binaries) + end + caveats = Caveats.new(formula) if (caveats_string = caveats.to_s.presence) ohai "Caveats", caveats_string diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb index 9d7a69e4db986..3e738c36a28bf 100644 --- a/Library/Homebrew/resource.rb +++ b/Library/Homebrew/resource.rb @@ -411,6 +411,11 @@ def installed_size manifest_annotations["sh.brew.bottle.installed_size"]&.to_i end + sig { returns(T.nilable(T::Array[String])) } + def path_exec_files + manifest_annotations["sh.brew.path_exec_files"]&.split(",") + end + sig { override.returns(String) } def download_queue_type = "Bottle Manifest" diff --git a/Library/Homebrew/test/cmd/info_spec.rb b/Library/Homebrew/test/cmd/info_spec.rb index 42e5af770b5a8..a8755ee2eecf4 100644 --- a/Library/Homebrew/test/cmd/info_spec.rb +++ b/Library/Homebrew/test/cmd/info_spec.rb @@ -239,6 +239,79 @@ .and not_to_output.to_stderr end + it "prints a Binaries section listing executables in bin and sbin" do + info = described_class.new([]) + formula = formula("testball") do + url "https://brew.sh/testball-0.1.tar.gz" + homepage "https://brew.sh/testball" + desc "Some test" + end + + keg_path = HOMEBREW_CELLAR/"testball/0.1" + (keg_path/"bin").mkpath + (keg_path/"sbin").mkpath + ["bin/testball", "bin/another", "sbin/daemon"].each do |rel| + file = keg_path/rel + file.write("#!/bin/sh\n") + file.chmod(0755) + end + tab = Tab.empty + tab.tabfile = keg_path/AbstractTab::FILENAME + tab.write + + allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") + allow(formula).to receive(:core_formula?).and_return(false) + + expect { info.send(:info_formula, formula) } + .to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout + .and not_to_output.to_stderr + end + + it "prints a Binaries section from the bottle manifest when the formula is not installed" do + info = described_class.new([]) + formula = formula("testball") do + url "https://brew.sh/testball-0.1.tar.gz" + homepage "https://brew.sh/testball" + desc "Some test" + end + + bottle = instance_double( + Bottle, + path_exec_files: ["bin/testball", "bin/another", "sbin/daemon"], + bottle_size: nil, + installed_size: nil, + ) + allow(bottle).to receive(:fetch_tab) + allow(formula).to receive_messages(bottle:, core_formula?: false) + allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") + + expect { info.send(:info_formula, formula) } + .to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout + .and not_to_output.to_stderr + end + + it "omits the Binaries section when no executables are installed" do + info = described_class.new([]) + formula = formula("testball") do + url "https://brew.sh/testball-0.1.tar.gz" + homepage "https://brew.sh/testball" + desc "Some test" + end + + keg_path = HOMEBREW_CELLAR/"testball/0.1" + (keg_path/"lib").mkpath + tab = Tab.empty + tab.tabfile = keg_path/AbstractTab::FILENAME + tab.write + + allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") + allow(formula).to receive(:core_formula?).and_return(false) + + expect { info.send(:info_formula, formula) } + .to not_to_output(/==> Binaries/).to_stdout + .and not_to_output.to_stderr + end + describe "::installation_status" do it "prints on-request installs explicitly" do expect(described_class.installation_status(instance_double(Tab, installed_on_request: true))) From 3c888296ce25a226b8b4362f99a0d75451ca831c Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Wed, 6 May 2026 14:40:51 +0200 Subject: [PATCH 2/4] info: gate Binaries section on --verbose Move the `==> Binaries` section behind `--verbose` (`-v`) so the default `brew info` output stays concise. --- Library/Homebrew/cmd/info.rb | 26 ++++++++++--------- Library/Homebrew/test/cmd/info_spec.rb | 35 ++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 7cd8d4abaf120..99f8358b27ea9 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -70,7 +70,7 @@ class NameSize < T::Struct depends_on: "--json", description: "Include the variations hash in each formula's JSON output." switch "-v", "--verbose", - description: "Show more verbose analytics data for ." + description: "Show more verbose data for ." switch "--formula", "--formulae", description: "Treat all named arguments as formulae." switch "--cask", "--casks", @@ -596,18 +596,20 @@ def info_formula(formula) Options.dump_for_formula formula end - binaries_keg = kegs.find(&:linked?) || kegs.last - binaries = if binaries_keg - binary_files = [binaries_keg/"bin", binaries_keg/"sbin"].select(&:directory?).flat_map do |dir| - dir.children.select { |child| child.file? && child.executable? } + if args.verbose? + binaries_keg = kegs.find(&:linked?) || kegs.last + binaries = if binaries_keg + binary_files = [binaries_keg/"bin", binaries_keg/"sbin"].select(&:directory?).flat_map do |dir| + dir.children.select { |child| child.file? && child.executable? } + end + binary_files.map { |path| path.basename.to_s } + elsif (path_exec_files = formula.bottle&.path_exec_files) + path_exec_files.map { |path| File.basename(path) } + end + if binaries.present? + binaries = binaries.sort.uniq + ohai "Binaries", Formatter.columns(binaries) end - binary_files.map { |path| path.basename.to_s } - elsif (path_exec_files = formula.bottle&.path_exec_files) - path_exec_files.map { |path| File.basename(path) } - end - if binaries.present? - binaries = binaries.sort.uniq - ohai "Binaries", Formatter.columns(binaries) end caveats = Caveats.new(formula) diff --git a/Library/Homebrew/test/cmd/info_spec.rb b/Library/Homebrew/test/cmd/info_spec.rb index a8755ee2eecf4..765a8a2cb4a29 100644 --- a/Library/Homebrew/test/cmd/info_spec.rb +++ b/Library/Homebrew/test/cmd/info_spec.rb @@ -239,8 +239,8 @@ .and not_to_output.to_stderr end - it "prints a Binaries section listing executables in bin and sbin" do - info = described_class.new([]) + it "prints a Binaries section listing executables in bin and sbin with --verbose" do + info = described_class.new(["--verbose"]) formula = formula("testball") do url "https://brew.sh/testball-0.1.tar.gz" homepage "https://brew.sh/testball" @@ -267,8 +267,8 @@ .and not_to_output.to_stderr end - it "prints a Binaries section from the bottle manifest when the formula is not installed" do - info = described_class.new([]) + it "prints a Binaries section from the bottle manifest when the formula is not installed with --verbose" do + info = described_class.new(["--verbose"]) formula = formula("testball") do url "https://brew.sh/testball-0.1.tar.gz" homepage "https://brew.sh/testball" @@ -290,7 +290,7 @@ .and not_to_output.to_stderr end - it "omits the Binaries section when no executables are installed" do + it "omits the Binaries section without --verbose" do info = described_class.new([]) formula = formula("testball") do url "https://brew.sh/testball-0.1.tar.gz" @@ -298,6 +298,31 @@ desc "Some test" end + keg_path = HOMEBREW_CELLAR/"testball/0.1" + (keg_path/"bin").mkpath + binary = keg_path/"bin/testball" + binary.write("#!/bin/sh\n") + binary.chmod(0755) + tab = Tab.empty + tab.tabfile = keg_path/AbstractTab::FILENAME + tab.write + + allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb") + allow(formula).to receive(:core_formula?).and_return(false) + + expect { info.send(:info_formula, formula) } + .to not_to_output(/==> Binaries/).to_stdout + .and not_to_output.to_stderr + end + + it "omits the Binaries section when no executables are installed" do + info = described_class.new(["--verbose"]) + formula = formula("testball") do + url "https://brew.sh/testball-0.1.tar.gz" + homepage "https://brew.sh/testball" + desc "Some test" + end + keg_path = HOMEBREW_CELLAR/"testball/0.1" (keg_path/"lib").mkpath tab = Tab.empty From 83f633668416da7bb1f91fca9fd834afc72c4d82 Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Wed, 6 May 2026 14:59:00 +0200 Subject: [PATCH 3/4] info: fetch bottle manifest under --verbose Trigger the bottle manifest fetch under `--verbose` so users don't need to combine `-v` with `--fetch-manifest`. Mirrors how `-v` already pulls analytics inline. --- Library/Homebrew/cmd/info.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 99f8358b27ea9..575138d6dd35e 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -524,7 +524,7 @@ def info_formula(formula) puts "Not installed" if (bottle = formula.bottle) begin - bottle.fetch_tab(quiet: !args.debug?) if args.fetch_manifest? + bottle.fetch_tab(quiet: !args.debug?) if args.fetch_manifest? || args.verbose? bottle_size = bottle.bottle_size installed_size = bottle.installed_size puts "Bottle Size: #{Formatter.disk_usage_readable(bottle_size)}" if bottle_size From 6fae3de01e5a3cea3a6f04e88461e01e338a81a4 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Wed, 6 May 2026 14:57:34 +0100 Subject: [PATCH 4/4] cmd/info: add future deprecation. --- Library/Homebrew/cmd/info.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb index 575138d6dd35e..fbc10c9edee47 100644 --- a/Library/Homebrew/cmd/info.rb +++ b/Library/Homebrew/cmd/info.rb @@ -53,6 +53,7 @@ class NameSize < T::Struct switch "--github", description: "Open the GitHub source page for and in a browser. " \ "To view the history locally: `brew log -p` or " + # odeprecated replace this with --verbose on next release switch "--fetch-manifest", description: "Fetch GitHub Packages manifest for extra information when is not installed." flag "--json",