Skip to content

Add which entry command#21790

Open
ooye-sanket wants to merge 13 commits intoHomebrew:mainfrom
ooye-sanket:add-which-entry-command
Open

Add which entry command#21790
ooye-sanket wants to merge 13 commits intoHomebrew:mainfrom
ooye-sanket:add-which-entry-command

Conversation

@ooye-sanket
Copy link
Copy Markdown
Contributor

Adds brew which-entry <formula> a developer command that generates
a single executables.txt entry for a formula by reading
sh.brew.path_exec_files from its bottle manifest.

This is intended to replace the batch brew which-update job with a
per-formula update triggered by homebrew/core CI on each merge.
What it does

  • Fetches the bottle manifest and reads sh.brew.path_exec_files
  • With --output-db=<file>: upserts the formula's line in the DB file
    and removes it if the formula is disabled, deprecated, or deleted
  • Without --output-db=: prints the DB line to stdout

Relationship to other PRs
The companion homebrew/core PR (Homebrew/homebrew-core#272976) slims
the existing workflow down to just call brew which-entry per formula
detected by brew test-bot --only-formulae-detect, replacing the
current daily batch job entirely.

@MikeMcQuaid please have a look!

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) with your changes locally?

  • AI was used to generate or assist with generating this PR. Please specify below how you used AI to help you, and what steps you have taken to manually verify the changes.

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @ooye-sanket! Looks good so far. Please add a very basic integration test and args check for which-entry. Also, please inline functions that are only used once as there's a fair bit of indirection right now.

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment on lines +61 to +62
rescue JSON::ParserError
[]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth a opoo warning of the exception in this case?

@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Thanks @MikeMcQuaid! I've pushed the changes:

  • Inlined cached_manifest, fetch_manifest, read_db, and remove_entry .
  • Added opoo warning when JSON::ParserError is raised so failures aren't silent
  • Added the basic spec with args check

Let me know if the level of inlining looks right or if you'd like it structured differently!

@ooye-sanket ooye-sanket requested a review from MikeMcQuaid March 21, 2026 09:35
Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Please ensure CI syntax job passing before asking for re-review.

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
if db_path
existing = db_path.exist? ? db_path.readlines(chomp: true).reject(&:empty?) : []
lines = existing.reject { |l| l.start_with?("#{formula.full_name}(") }
lines << line if line
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use filter_map?

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
@github-actions
Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added stale No recent activity and removed stale No recent activity labels Apr 13, 2026
@ooye-sanket ooye-sanket requested a review from MikeMcQuaid April 13, 2026 08:45
@ooye-sanket ooye-sanket force-pushed the add-which-entry-command branch from 9e3e0a6 to 00d9e78 Compare April 13, 2026 08:49
Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looking good, a few small things!

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated

sig { override.void }
def run
db_path = Pathname(T.unsafe(args).output_db) if T.unsafe(args).output_db
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be necessary, you may need to run brew typecheck --update to avoid the need for T.unsafe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ooye-sanket Can you take another look at this?

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
sig { params(db_path: Pathname, name: String, line: T.nilable(String)).void }
def write_db(db_path, name, line)
lines = db_path.readlines(chomp: true).compact_blank if db_path.exist?
lines = (lines || []).reject { |l| l.start_with?("#{name}(") }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lines = (lines || []).reject { |l| l.start_with?("#{name}(") }
lines = Array(lines).reject { |l| l.start_with?("#{name}(") }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not resolved

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there's an inconsistency here, right? We reject lines that start with name( but we don't actually write the version in parentheses anymore.

Copy link
Copy Markdown
Contributor Author

@ooye-sanket ooye-sanket May 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed as now uses Array(lines) and the reject pattern is corrected to "#{name}:" to match the new format without pkg_version.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to support both the old and new formats

Comment thread Library/Homebrew/dev-cmd/which-entry.rb
@MikeMcQuaid MikeMcQuaid requested a review from Rylan12 April 14, 2026 13:50
Copy link
Copy Markdown
Member

@Rylan12 Rylan12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the review delay here. I agree with Mike's feedback, and also have one other thought.

More generally, I'm not sure whether this actually needs to be a brew command at all. I will leave a comment on the other PR with more details

Edit: oh, one more thing, we should remove the existing command-not-found-db-update.yml workflow

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
@ooye-sanket
Copy link
Copy Markdown
Contributor Author

hey @Rylan12 The reason for keeping it as a brew command is that it already handles formula loading, bottle manifest caching, disabled/deprecated checks, and DB file management in a tested, typed Ruby context. Doing all that reliably in bash would be fragile. But happy to discuss if there's a simpler scope you have in mind.

@MikeMcQuaid
Copy link
Copy Markdown
Member

Let's keep it as Ruby for now and we can see how it's looking when the other PR is ready.

@Rylan12
Copy link
Copy Markdown
Member

Rylan12 commented Apr 18, 2026

I don't mind keeping it in Ruby for now, so this is not a blocker.

it already handles formula loading, bottle manifest caching, disabled/deprecated checks, and DB file management in a tested, typed Ruby context. Doing all that reliably in bash would be fragile

I think doing it in bash would actually let us bypass these complexities altogether, resulting in a simpler system overall. There would be no need to load formulae at all, and the deprecated/disabled info is available via the same bottle manifest that we already need to use.

Fair enough on the DB management, although I think a reasonably well-crafted sed line would do the trick.

I haven't attempted to write it out, so I could be wrong

@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Hey @MikeMcQuaid I've made all the changes (dropped pkg_version, fixed the reject pattern, removed T.unsafe) but I'm unable to run brew typecheck --update locally to regenerate the RBI file. Could you help run it or point me to how to get past this? Everything else should be good to go!

@ooye-sanket ooye-sanket requested a review from MikeMcQuaid April 22, 2026 15:49
@MikeMcQuaid
Copy link
Copy Markdown
Member

@ooye-sanket what happens when you run it locally?

@MikeMcQuaid
Copy link
Copy Markdown
Member

@ooye-sanket nudge on above question!

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting on author answering question.

@ooye-sanket ooye-sanket force-pushed the add-which-entry-command branch from 91f72e6 to 64df19c Compare April 27, 2026 09:42
@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Hey @MikeMcQuaid, sorry for the delay! I ran brew typecheck --update locally and it worked successfully it generated the new sorbet/rbi/dsl/homebrew/dev_cmd/which_entry.rbi RBI file which I've now committed. I've also rebased onto latest upstream/main. Ready for re-review

Note: the brew typecheck CI failure is in extend/os/mac/keg_relocate.rb and extend/os/mac/keg.rb these are pre-existing errors unrelated to this PR.

CC @Rylan12

@MikeMcQuaid
Copy link
Copy Markdown
Member

Note: the brew typecheck CI failure is in extend/os/mac/keg_relocate.rb and extend/os/mac/keg.rb these are pre-existing errors unrelated to this PR.

That's not true: these are passing on main: https://github.com/Homebrew/brew/actions/runs/24983715091/job/73151721181

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still waiting on author

@MikeMcQuaid MikeMcQuaid removed the request for review from Rylan12 April 28, 2026 08:13
@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Hey @MikeMcQuaid, CI is now green! , The earlier typecheck failure was caused by our update accidentally removing mach_o_shim.rbi I've now restored it. Ready for re-review! 🙏

@ooye-sanket ooye-sanket requested a review from MikeMcQuaid April 30, 2026 07:52
@MikeMcQuaid MikeMcQuaid requested a review from Rylan12 April 30, 2026 10:10
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated

sig { override.void }
def run
db_path = Pathname(T.unsafe(args).output_db) if T.unsafe(args).output_db
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ooye-sanket Can you take another look at this?

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb
Comment thread Library/Homebrew/sorbet/rbi/dsl/cask/config.rbi
@ooye-sanket ooye-sanket force-pushed the add-which-entry-command branch from 5680029 to 50f706c Compare May 1, 2026 12:01
@ooye-sanket ooye-sanket requested a review from MikeMcQuaid May 1, 2026 12:28
@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Hey @MikeMcQuaid, sorry for the noise! All 3 issues fixed.
CI is green and ready for re-review!

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated

sig { override.void }
def run
db_path = Pathname(T.must(args.output_db)) if args.output_db
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should just make this argument required and raise a usage error if it's not passed. Or accept it as a named arg

Copy link
Copy Markdown
Contributor Author

@ooye-sanket ooye-sanket May 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed --output-db is now required and raises a UsageError if not passed.

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
sig { params(db_path: Pathname, name: String, line: T.nilable(String)).void }
def write_db(db_path, name, line)
lines = db_path.readlines(chomp: true).compact_blank if db_path.exist?
lines = (lines || []).reject { |l| l.start_with?("#{name}(") }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not resolved

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
sig { params(db_path: Pathname, name: String, line: T.nilable(String)).void }
def write_db(db_path, name, line)
lines = db_path.readlines(chomp: true).compact_blank if db_path.exist?
lines = (lines || []).reject { |l| l.start_with?("#{name}(") }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there's an inconsistency here, right? We reject lines that start with name( but we don't actually write the version in parentheses anymore.

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
@Rylan12
Copy link
Copy Markdown
Member

Rylan12 commented May 2, 2026

@ooye-sanket, some of the issues Mike pointed out don't actually seem to have been fixed yet. When you fix those, can you please run the commands locally on your machine and comment the output here to help verify that this works as expected?

It would be helpful to see a sample executables.txt with pkg_versions and and outdated exe list, then show the brew which-entry command and it's output, and then the fixed executables.txt file

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments not addressed. Do not mark as resolved or request re review until 1) all comments past and present resolved and 2) all @Rylan12's comments and questions answered.

If I'm requested for review again and this still isn't ready: I'm just likely to close this out, sorry. Please raise the bar for yourself here. Thank you ❤️

@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Hey @MikeMcQuaid and @Rylan12, all comments are now addressed in the latest commits.

  • --output-db is now required raises UsageError if not passed
  • write_db now uses Array(lines) and correctly rejects name: instead of name(
  • executables_from_manifest now uses formula.bottle.github_packages_manifest_resource with downloaded? and cached_download no more filesystem globbing
  • Removed command-not-found-db-update.yml as requested

Here's proof the command works locally:

{136E2976-0335-4DD9-8772-C398249919C7} {88692EFA-6CF5-495F-9132-113ADF4D0787} Entries are sorted, idempotent, and correctly formatted as name:executables.

NOTE : The only failing CI check is Docker / docker (x86_64 Ubuntu 22.04) which is a GitHub infrastructure issue apt-get update failing with permission errors, unrelated to our code. All 32 other checks are passing

@ooye-sanket ooye-sanket requested review from MikeMcQuaid and Rylan12 May 2, 2026 08:56
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
sig { params(db_path: Pathname, name: String, line: T.nilable(String)).void }
def write_db(db_path, name, line)
lines = db_path.readlines(chomp: true).compact_blank if db_path.exist?
lines = (lines || []).reject { |l| l.start_with?("#{name}(") }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to support both the old and new formats

Comment thread Library/Homebrew/dev-cmd/which-entry.rb Outdated
@ooye-sanket
Copy link
Copy Markdown
Contributor Author

Hey @Rylan12, all feedback addressed! please have a look
image

@ooye-sanket ooye-sanket requested a review from Rylan12 May 3, 2026 07:28
Copy link
Copy Markdown
Member

@Rylan12 Rylan12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ooye-sanket, this implementation does not work correctly for any formulae that have multiple executables.

For future pushes, please include proof that you've tested the changes and that they work correctly for the standard case and also edge cases. Since this will run on every formula update, it's imperative that the code is sound and can handle all of the possible situations that may arise.

To help with this, unit tests would be useful for all changes (aside from a full integration tests, which are okay to skip because they're too slow).

Comment on lines +59 to +60
manifest_path = manifest_resource.cached_download
return [] unless manifest_path.exist?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think .fetch is probably good enough here. It's called by default with verify_download_integrity: true, so I think we'll get an error before this point if we weren't able to download the manifest. I'd say we can just get rid of these lines since I'm not convinced they add additional safety. It seems appropriate to me for this to fail if the formula is bottled but unable to download the bottle manifest. That does seem error-worthy

Suggested change
manifest_path = manifest_resource.cached_download
return [] unless manifest_path.exist?

Comment on lines +63 to +65
rescue JSON::ParserError => e
opoo "Failed to parse bottle manifest for #{formula.name}: #{e.message}"
[]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get rid of this. BottleResource#manifest_annotations raises a nice error if the JSON doesn't parse, and I think we want a hard error here. Also it's a bit weird to rescue a JSON parse error when we don't actually call JSON.parse directly in this method.

Suggested change
rescue JSON::ParserError => e
opoo "Failed to parse bottle manifest for #{formula.name}: #{e.message}"
[]

Comment on lines +415 to +420
def path_exec_files
exec_files = manifest_annotations["sh.brew.path_exec_files"]
return [] if exec_files.blank?

exec_files.split.map { |f| File.basename(f) }.sort
end
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work correctly for annotations that have multiple entries. The entries are comma-separated, so split doesn't work.

Additionally, the File.basename doesn't belong in this method. This method should return the data directly, and leave it up to the caller how to process it.

Suggested change
def path_exec_files
exec_files = manifest_annotations["sh.brew.path_exec_files"]
return [] if exec_files.blank?
exec_files.split.map { |f| File.basename(f) }.sort
end
def path_exec_files
manifest_annotations.fetch("sh.brew.path_exec_files", "").split(",")
end

Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ooye-sanket please stop requesting my review here, thanks. I'll review when @Rylan12 is ✅.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants