brew icon indicating copy to clipboard operation
brew copied to clipboard

`brew deps` output shows inconsistent dependency information between tree and non-tree output

Open lorentzforces opened this issue 1 year ago β€’ 18 comments

brew doctor output

Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: Putting non-prefixed coreutils in your path can cause GMP builds to fail.

Warning: Homebrew's "sbin" was not found in your PATH but you have installed
formulae that put executables in /opt/homebrew/sbin.
Consider setting your PATH for example like so:
  echo 'export PATH="/opt/homebrew/sbin:$PATH"' >> /Users/51195/.bash_profile

Verification

  • [X] My "brew doctor output" above says Your system is ready to brew. and am still able to reproduce my issue.
  • [X] I ran brew update twice and am still able to reproduce my issue.
  • [X] This issue's title and/or description do not reference a single formula e.g. brew install wget. If they do, open an issue at https://github.com/Homebrew/homebrew-core/issues/new/choose instead.

brew config output

HOMEBREW_VERSION: 4.1.13
ORIGIN: https://github.com/Homebrew/brew
HEAD: a8519f78fb63f2f2266950bdd8141037da69f8bd
Last commit: 6 hours ago
Core tap JSON: 25 Sep 18:03 UTC
HOMEBREW_PREFIX: /opt/homebrew
HOMEBREW_CASK_OPTS: []
HOMEBREW_EDITOR: nvim
HOMEBREW_MAKE_JOBS: 10
Homebrew Ruby: 2.6.10 => /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
CPU: 10-core 64-bit arm_firestorm_icestorm
Clang: 14.0.3 build 1403
Git: 2.42.0 => /opt/homebrew/bin/git
Curl: 8.1.2 => /usr/bin/curl
macOS: 13.5.2-arm64
CLT: 14.3.1.0.1.1683849156
Xcode: N/A
Rosetta 2: false

What were you trying to do (and why)?

When checking to see which of my installed homebrew packages had updates, I noticed that I had both [email protected] and OpenSSL@3 installed. I didn't realize I had two versions, and I wanted to know which packages depended on each respective version, if any. I have found a few unused packages recently in my Homebrew installation, and wanted to clean up packages that weren't being used.

I ran brew deps --tree --installed to look at all dependencies, and searched for "[email protected]" in that output.

What happened (include all command output)?

On initial check, I did not see "[email protected]" listed anywhere in the command's output.

I narrowed this down and found an inconsistency in brew deps output as follows.

Output of brew deps --installed tmux:

ca-certificates
libevent
ncurses
[email protected]
utf8proc

Output of brew deps --tree --installed tmux:

tmux
β”œβ”€β”€ libevent
β”‚   └── openssl@3
β”‚       └── ca-certificates
β”œβ”€β”€ ncurses
└── utf8proc

Note the different versions of openssl reported between these two commands.

What did you expect to happen?

I expect the same specific version of a package to be reported as a dependency regardless of which command or command arguments are used to view dependencies.

Step-by-step reproduction instructions (by running brew commands)

brew install tmux tmuxp
brew deps --installed tmux
brew deps --tree --installed tmux

lorentzforces avatar Sep 25 '23 18:09 lorentzforces

Created as a homebrew issue and not a formula issue as this behavior seems more likely to be on the homebrew side.

lorentzforces avatar Sep 25 '23 18:09 lorentzforces

I haven't looked into this but I can say that we have different dependency resolution code for the normal and tree versions of the brew deps command. For that reason I'm not completely surprised that they might return different results. Definitely seems like a bug though on our side.

apainintheneck avatar Sep 26 '23 05:09 apainintheneck

I think this is similar to #13717 (see also https://github.com/orgs/Homebrew/discussions/4784) -- there is a discrepancy between dependency information from the tab (i.e., install receipt) and that from the formulae which can be updated after the installation. In this case, tmux was likely installed before libevent was switched from [email protected] to openssl@3. When libevent was upgraded, its updated dependency info was not reflected in tmux's install receipt.

ZhongRuoyu avatar Sep 26 '23 06:09 ZhongRuoyu

@ZhongRuoyu Yep, you're right. That's exactly what's happening here. We end up calling Formula#runtime_dependencies internally and that defaults to the tab if there's a keg already installed. We could easily change that to always skip the tab in these situations but I'm not sure if that'd be less confusing.

https://github.com/Homebrew/brew/blob/f1d345a60e2c02f7e5c1b7930601b1921432cfb6/Library/Homebrew/formula.rb#L2120-L2122

apainintheneck avatar Sep 26 '23 08:09 apainintheneck

We could easily change that to always skip the tab in these situations but I'm not sure if that'd be less confusing.

I think it should always use rather than always skip the tab but: yes, I agree it would be much better (and would fix this bug) to be consistent here.

MikeMcQuaid avatar Sep 26 '23 10:09 MikeMcQuaid

I'm hesitant to change this since it's one of the few places where we get a glimpse of what's happening in the tab and for that reason it's occasionally useful for debugging. Ideally we'd store more up-to-date information in the tab but that's a very difficult problem as described in the original issue and I assume that's why nobody picked it up at the time.

I'm not sure if users expect this command to reflect installed versions or latest versions of packages in the event that an installed package is on an older version (through pinning or not updating it yet). Right now it seems to default to the installed version which seems reasonable.

apainintheneck avatar Sep 30 '23 03:09 apainintheneck

It does seem like brew deps --tree is more accurate in this case because it manually checks the direct dependencies at each level of the tree instead of using the recursive dependencies found in the tab.

apainintheneck avatar Sep 30 '23 03:09 apainintheneck

My own expectations as a user:

  • The default, I would assume, would be in any case to show me currently-installed dependencies. In 95% of the cases where I'm looking up dependency information (as a user), I want to know that information in relation to what's on my system at that moment.
  • The brew deps --tree option reads to me (from just looking at the command) as the same base information as brew deps, but with a different, more-verbose visual representation. Different display, same data.

It makes sense to me that for certain purposes, especially debugging, that you might want to know exactly what package and version were installed via tracking a receipt for a given install operation. That makes total sense. What doesn't make sense to me is that you would show this type of information as the default for any command, especially for the baseline command I would reach for if I was looking for dependency information.

Would it be unreasonable to add a separate, different option to show information from the "install receipt / tab," and show the current dependency information (same as the --tree option) if the command is invoked without that option present? Something like --from-receipt or just straight up --read-from-tab (to match that function parameter)?

lorentzforces avatar Oct 01 '23 03:10 lorentzforces

What doesn't make sense to me is that you would show this type of information as the default for any command, especially for the baseline command I would reach for if I was looking for dependency information.

It didn't use to be the default and people complained that it didn't match what's happening on their machine. There's not a single default that everyone will agree on and not find confusing.

Would it be unreasonable to add a separate, different option to show information from the "install receipt / tab,"

We could/should add an option for the opposite: to discard the information from your tap and use only what the formula says.

MikeMcQuaid avatar Oct 01 '23 15:10 MikeMcQuaid

@lorentzforces Just to be sure would you be willing to share the install receipt for this package? Something like this should work.

cat $(brew --cellar tmux)/*/INSTALL_RECEIPT.json

I also wonder what the output of this is?

brew deps tmux

The command you showed brew deps --installed tmux doesn't even try to evaluate the dependency tree but just directly returns what's in the tab with the #runtime_dependencies.

https://github.com/Homebrew/brew/blob/5ec560a4ba0111d9fe223ce40b3db01c183fe1d0/Library/Homebrew/cmd/deps.rb#L200-L203

I'm also inclined to open a new issue for the outdated install receipt since it seems like it's still causing unexpected behavior with multiple commands.

apainintheneck avatar Oct 01 '23 16:10 apainintheneck

@apainintheneck sure!

Output of cat $(brew --cellar tmux)/*/INSTALL_RECEIPT.json

{
  "homebrew_version": "4.0.11-95-gd15f571",
  "used_options": [

  ],
  "unused_options": [

  ],
  "built_as_bottle": true,
  "poured_from_bottle": true,
  "loaded_from_api": true,
  "installed_as_dependency": false,
  "installed_on_request": true,
  "changed_files": [
    "share/man/man1/tmux.1"
  ],
  "time": 1680893890,
  "source_modified_time": 1654774350,
  "compiler": "clang",
  "aliases": [

  ],
  "runtime_dependencies": [
    {
      "full_name": "ca-certificates",
      "version": "2023-01-10",
      "declared_directly": false
    },
    {
      "full_name": "[email protected]",
      "version": "1.1.1t",
      "declared_directly": false
    },
    {
      "full_name": "libevent",
      "version": "2.1.12",
      "declared_directly": true
    },
    {
      "full_name": "ncurses",
      "version": "6.4",
      "declared_directly": true
    },
    {
      "full_name": "utf8proc",
      "version": "2.8.0",
      "declared_directly": true
    }
  ],
  "source": {
    "spec": "stable",
    "versions": {
      "stable": "3.3a",
      "head": null,
      "version_scheme": 0
    },
    "path": "/opt/homebrew/Library/Taps/homebrew/homebrew-core/Formula/tmux.rb",
    "tap_git_head": null,
    "tap": "homebrew/core"
  },
  "arch": "arm64",
  "built_on": {
    "os": "Macintosh",
    "os_version": "macOS 13",
    "cpu_family": "dunno",
    "xcode": "14.2",
    "clt": "14.2.0.0.1.1668646533",
    "preferred_perl": "5.30"
  }
}

Output of brew deps tmux

ca-certificates
libevent
ncurses
[email protected]
utf8proc

It didn't use to be the default and people complained that it didn't match what's happening on their machine.

@MikeMcQuaid this seems like an odd statement to me; I have a pretty big conceptual gulf between "what is happening on the machine" and "what operations were historically performed on the machine," and to me that first one is much more important in my day-to-day. (I'm also defining "what is happening on the machine" as "what gets run when I run this program," not "what did homebrew do," which may also be different from "typical" usage)

That might be naivete on my part - I'm not someone who administrates machines or deployments, just someone who uses Homebrew to install programs for my dev environment on MacOS or Linuxbrew to keep packages outside my standard package manager up to date easily.

We could/should add an option for the opposite: to discard the information from your tap and use only what the formula says.

This is absolutely reasonable, and I'm 100% happy with such an option. As long as I can keep a specific option in my head that will do what I expect/want every time, that solves all my problems.

lorentzforces avatar Oct 01 '23 21:10 lorentzforces

Thanks for the debugging info!

After looking at things again, brew deps tmux is equivalent to brew deps --installed tmux internally if tmux is already installed so I shouldn't be surprised that the results are the same. If any of the other options are passed to the command, the results should be correct. These are the only two versions of the command that read all recursive dependencies from the tab. The other ones only read direct dependencies and recurse down the tree to get all of them.

For example, brew deps --skip-recommended tmux should return the results you're looking for. Of course, that's a workaround (we don't use recommended dependencies in core) but at least it points to the recursive dependency algorithms as being correct. Adding another option here is fine for me too though we'd ideally fix this at some point.

apainintheneck avatar Oct 02 '23 02:10 apainintheneck

For example, brew deps --skip-recommended tmux should return the results you're looking for.

yeah that's exactly the hack I have used numerous times myself.

Bo98 avatar Oct 02 '23 02:10 Bo98

I'm also inclined to open a new issue for the outdated install receipt since it seems like it's still causing unexpected behavior with multiple commands.

We have this issue every time there's a OpenSSL migration.

Ignoring the brew deps for a moment (where there's probably room for an extra flag somewhere) and focussing on the tab specifically: by the letter of how it is designed to work, it is correct. That's not to say it's necessarily 100% ideal. The tab is fairly eager with dependencies, mostly because it's tricky to do anything else safely. We collect recursive dependencies but don't really properly know what's actually a runtime dependency (linkage is fairly safe bet for some things, but not everything). But when we do OpenSSL migrations, we only revision bump things that actually have linkage to OpenSSL (usually direct dependencies plus anything recursive that's flagged in CI). In this case tmux did not need revision bumping, but the tab will think OpenSSL is a runtime dependency because it doesn't know otherwise.

But yes it does cause issues when brew uninstall [email protected] is blocked when in reality it's safe (except for the very few formulae left that directly depend on it), meaning people are told to keep something we've phased out as EOL.

(Though yeah, this is treading towards a separate issue rather than brew deps specifically)

Bo98 avatar Oct 02 '23 02:10 Bo98

This is absolutely reasonable, and I'm 100% happy with such an option. As long as I can keep a specific option in my head that will do what I expect/want every time, that solves all my problems.

Better still may be to make this both an option an a documented environment variable so you can set it once and always do what you want.


Another thing worth noted about preferring/deciding between using the tab and recalculating the dependencies: the prior is way quicker.

MikeMcQuaid avatar Oct 02 '23 11:10 MikeMcQuaid

There was recently added a paragraph to the command's docs addressing this:

If any version of each formula argument is installed and no other options are passed, this command displays their actual runtime dependencies (similar to brew linkage), which may differ from the current versons’ stated dependencies if the installed versions are outdated.

EricFromCanada avatar Oct 02 '23 12:10 EricFromCanada

I'm debugging something and I'm not sure it is related or not (happy to file a separate issue). What I'm seeing is severe differences between brew missing, brew deps --tree X, and what I'm calculating the missing dependencies to be... Here's a simple example where the tab(?) seems stale despite the brew being up to date:

# confirmed HOMEBREW_NO_INSTALL_FROM_API=1 makes no difference to this output:

# git-imerge is up to date
brew outdated | grep git-imerge 
# => nothing

# the version I currently have:
brew list --versions git-imerge
# => git-imerge 1.2.0_1

# git-imerge is missing [email protected] ?
brew missing | grep git-imerge
# => git-imerge: [email protected]

# why? because that's what it says here:
cat $(brew --cellar git-imerge)/*/.brew/git-imerge.rb | grep depend
# => depends_on "[email protected]"

# and here:
cat $(brew --cellar git-imerge)/*/INSTALL_RECEIPT.json | jq ".runtime_dependencies.[] | select(.declared_directly) | .full_name"
# => "[email protected]"

# but not here?!?! isn't it up to date?
brew cat git-imerge | grep depend
# => depends_on "[email protected]"

It seems to me that this is hinting at stale install info possibly causing some of the pain for OP.

Please feel free to tell me this is unrelated and to file a separate issue.

HOMEBREW_VERSION: 4.1.14-60-g5349b76-dirty

(dirty is unrelated... I think I found a bug in Homebrew/env_config.rb on default value for HOMEBREW_LIVECHECK_WATCHLIST)

zenspider avatar Oct 14 '23 21:10 zenspider

I'm debugging something and I'm not sure it is related or not (happy to file a separate issue). What I'm seeing is severe differences between brew missing, brew deps --tree X, and what I'm calculating the missing dependencies to be...

For what it's worth, other related Homebrew commands such as brew uses --recursive --installed <formula> and brew deps --tree <formula> still show discrepancies as well in some rather common cases.

πŸ“œπŸ§ To refer back to the historic records, it seems like something similar to this was discussed in Homebrew/legacy-homebrew#50068. In that case, it had a lot to do with what they call a "requirement" versus a generic package name-based "dependency". The example given was with an old version of Python, which is considered a "requirement" as in depends_on :python (as opposed to: depends_on "python"). The string "python" as package name is a normal dependency, meanwhile the Ruby Symbol :python is a "requirement" and was a shortcut for depends_on PythonRequirement.

This seems related to your example, because it mentions [email protected] as a "dependency" if we assume output of brew deps's "deps" resolves to (or conflates to in the user's mind?) "dependency", but maybe it could have been pulled in as a "requirement" (in Homebrew terms) when first installed? 🀷 I'm not 100% sure, but I suspect that if that were the case, the requirement resolved to [email protected] when installed. If it never was specified as a requirement in the Formula Ruby code, as it seems was the case, then it was probably simply a "dependency" on that specific Python version. We do see evidence in the INSTALL_RECEIPT.json that it contains the [email protected] version. We also see that the backed-up Formula's Ruby code (.brew/git-imerge.rb), if we can trust this, has depends_on "[email protected]", which would make it a "dependency" on the package name: "[email protected]". In this case, if we can trust the INSTALL_RECEIPT.json and the .brew/*.rb formulae, then we must deduce that the old version of the formula specified a dependency on [email protected] and it was later updated to [email protected] in the Formula's Ruby code. πŸ”πŸ•΅οΈβ€β™‚οΈ

Meanwhile, the updated Ruby code Formula git-imerge.rb has depends_on "[email protected]", which appears to be a "dependency" on that specific version of python (3.12). I'm not sure which would have been better (e.g. "requirement" or "dependency") given that Homebrew's dependency reporting commands (brew uses --recursive X, brew deps --tree X) seem to be commonly misleading people in these types of situations. In the case of Homebrew/legacy-homebrew#50068, it was a "requirement" and brew uses --recursive X misleading the user. In your case, it was likely a "dependency" and brew deps --tree X misleading you (as a user).

This particular discrepancy when reporting dependencies is still manifesting in current Homebrew. Given these various dependency reporting discrepancies, perhaps it's worth discussing whether a general solution for Homebrew's dependency & reverse dependency commands should be reworked to return what most people using them might expect? 🀷

As a user, they should be able to invoke brew deps ... and brew uses ... commands to answer the following questions:

  1. "What's happening on my system now?"
  2. "What Formula(e) depends on what (now)?"
  3. "What Formula(e) historically pulled in X dependency?" (e.g. "What depended on what back then?")
  4. "What am I safe to upgrade now?" vs. "What must I hold back?" (e.g. something still needs python@2 as a common example)

The output of such commands should be unsurprising to the majority of users... and for those users who think it is still surprising... at least make it consistent and document it so they can become informed as to what to expect.

For users, it seems there are two major areas of concern in answering those questions:

  1. What's happening now? (e.g. Current Formulae / Casks dependency resolution both forwards & reverse recursively)
  2. What happened in the past / historically? (e.g. INSTALL_RECIEPT.json, "tab", .brew/*.rb, Git repos: homebrew-core, homebrew-cask etc...)

I hope this helps enliven the discussion surrounding the more general dependency reporting & resolving commands inside brew! Happy sleuthing! πŸ”πŸ•΅οΈβ€β™‚οΈ

trinitronx avatar Jul 23 '24 06:07 trinitronx