Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
3 tasks done
lorentzforces opened this issue Sep 25, 2023 · 17 comments
Open
3 tasks done
Labels
bug Reproducible Homebrew/brew bug help wanted We want help addressing this

Comments

@lorentzforces
Copy link

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

  • My "brew doctor output" above says Your system is ready to brew. and am still able to reproduce my issue.
  • I ran brew update twice and am still able to reproduce my issue.
  • 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 lorentzforces added the bug Reproducible Homebrew/brew bug label Sep 25, 2023
@lorentzforces
Copy link
Author

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

@apainintheneck
Copy link
Contributor

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 apainintheneck added the help wanted We want help addressing this label Sep 26, 2023
@ZhongRuoyu
Copy link
Member

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.

@apainintheneck
Copy link
Contributor

@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.

# Returns a list of Dependency objects that are required at runtime.
# @private
def runtime_dependencies(read_from_tab: true, undeclared: true)

@MikeMcQuaid MikeMcQuaid changed the title "brew deps" output shows inconsistent dependency information between tree and non-tree output (seen with tmux/openssl) brew deps output shows inconsistent dependency information between tree and non-tree output Sep 26, 2023
@MikeMcQuaid
Copy link
Member

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.

@apainintheneck
Copy link
Contributor

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
Copy link
Contributor

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.

@lorentzforces
Copy link
Author

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)?

@MikeMcQuaid
Copy link
Member

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.

@apainintheneck
Copy link
Contributor

@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.

def self.deps_for_dependent(dependency, args:, recursive: false)
includes, ignores = args_includes_ignores(args)
deps = dependency.runtime_dependencies if @use_runtime_dependencies

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.

@lorentzforces
Copy link
Author

lorentzforces commented Oct 1, 2023

@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.

@apainintheneck
Copy link
Contributor

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.

@Bo98
Copy link
Member

Bo98 commented Oct 2, 2023

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
Copy link
Member

Bo98 commented Oct 2, 2023

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)

@MikeMcQuaid
Copy link
Member

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.

@EricFromCanada
Copy link
Member

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.

@zenspider
Copy link

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Reproducible Homebrew/brew bug help wanted We want help addressing this
Projects
None yet
Development

No branches or pull requests

7 participants