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

dependency_helpers: rework recursive dependency resolution #15892

Conversation

apainintheneck
Copy link
Contributor

@apainintheneck apainintheneck commented Aug 19, 2023

  • 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 style with your changes locally?
  • Have you successfully run brew typecheck with your changes locally?
  • Have you successfully run brew tests with your changes locally?

Fixes #15842

This is a refactor/reworking of the dependency resolution methods in the DependencyHelpers module. These methods are used by both the brew deps and brew uses commands to get a specific set of dependencies for the user based on multiple criteria.

Additive Options:
--include-build
--include-test
--include-optional

Subtractive Options:
--skip-recommended
--missing

When a user runs either command the only dependencies that are included by default are recommended and runtime dependencies. This is largely unchanged though we only used to include recommended dependencies if they were build dependencies before. I'm not entirely sure why these were included by default before though.

The biggest change is that all installed dependencies are always removed from the list now if the --missing option is passed. This could get skipped before depending on the other options that were passed. Essentially subtractive options now will always be evaluated before additive ones (the docs will need to be updated to make this clear).

Beyond that we have no special handling for the optional command anymore. We used to check that the optional dependency was not needed to build the formula but that seems redundant and confusing. Essentially, the #recursive_includes command now behaves much more like the #reject_ignores command (essentially the non-recursive version) which is a good thing for consistency's sake. I updated the #reject_ignores command to make sure it aligns with the new goal of giving subtractive options priority here and also renamed it to #select_includes after refactoring.

I also updated the man pages for both the brew uses and brew deps commands to clarify how they work.

Note: These are technically breaking changes. I think these commands were broken before but still it's possible that someone is counting on the previous behavior. This should probably wait until the next minor release at the very least.

Todo:

  • Compare the results against the previous version of the command
    • I've only tested a handful of packages so far.
    • I've tested different variations of the brew deps command with the --for-each flag set and this returns the same results except for when --missing is set.
    • I've tested ~20 packages with brew uses, most of which are recommended or optional, and things look good here.
  • Add tests for expected behavior.
  • Update docs for brew uses and brew deps to make things clearer.

This is a refactor/reworking of the dependency resolution methods
in the DependencyHelpers module. These methods are used by both
the `brew deps` and `brew uses` commands to get a specific set
of dependencies for the user based on multiple criteria.

Additive Options:
--include-build
--include-test
--include-optional

Subtractive Options:
--skip-recommended
--missing

When a user runs either command the only dependencies that are
included by default are recommended and runtime dependencies.
This is largely unchanged though we don't include all non-build
dependencies as recommended by default anymore.

The biggest change is that all installed dependencies are always
removed from the list now if the --missing option is passed.
This could get skipped before depending on the other options
that were passed. Essentially subtractive options now will
always be evaluated before additive ones (the docs will need to
be updated to make this clear).

Beyond that we have no special handling for the optional command
anymore. We used to check that the optional dependency was not
needed to build the formula but that seems redundant and confusing.
Essentially, the #recursive_includes command now behaves much more
like the #reject_ignores command (essentially the non-recursive version)
which is a good thing for consistency's sake.
@apainintheneck apainintheneck added the bug Reproducible Homebrew/brew bug label Aug 19, 2023
@apainintheneck apainintheneck self-assigned this Aug 19, 2023
@apainintheneck
Copy link
Contributor Author

There's a part of me that wonders if we'd be better off just having a --include-recommended flag instead of --skip-recommended and just stop including recommended dependencies by default. It'd be a breaking change though so it'd be beyond the scope of this PR.

@MikeMcQuaid
Copy link
Member

There's a part of me that wonders if we'd be better off just having a --include-recommended flag instead of --skip-recommended and just stop including recommended dependencies by default.

I don't think so as recommended are installed by default so it would be weird for a default brew install and brew deps or brew uses to get different output.

@MikeMcQuaid
Copy link
Member

There is, though, an argument whether it even makes sense to support :recommended or even :optional based on wider ecosystem usage and whether they should be deprecated.

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

This looks great so far, I really like the refactoring approach.

Library/Homebrew/dependencies_helpers.rb Outdated Show resolved Hide resolved
@Bo98
Copy link
Member

Bo98 commented Aug 21, 2023

There is, though, an argument whether it even makes sense to support :recommended or even :optional based on wider ecosystem usage and whether they should be deprecated.

They're quite highly used in third-party taps, which makes sense given they don't necessarily use bottles like we do for homebrew-core, and are also usually only a few formulae rather than a few thousand.

homebrew-ffmpeg being a popular example (and the use of optional is exactly the reason that tap exists).

@MikeMcQuaid
Copy link
Member

They're quite highly used in third-party taps, which makes sense given they don't necessarily use bottles like we do for homebrew-core, and are also usually only a few formulae rather than a few thousand.

Yeh, I'm interested whether :recommended is used as I do see :optional usage now in e.g. the ffmpeg case.

@Bo98
Copy link
Member

Bo98 commented Aug 21, 2023

Yeh, I'm interested whether :recommended is used as I do see :optional usage now in e.g. the ffmpeg case.

Not as common as optional for sure. Here's a few that do: https://github.com/search?q=%3Arecommended+%22%3C+Formula%22+lang%3ARuby+-repo%3AHomebrew%2Fbrew+-repo%3Amistydemeo%2Ftigerbrew+-is%3Afork&type=code&p=1

@MikeMcQuaid
Copy link
Member

Cool, yeh, probably worth keeping both then, thanks @Bo98.

@apainintheneck
Copy link
Contributor Author

Thanks for feedback everyone.

I did some testing on the core repo and it seems like everything behaves the same way for the brew deps command on the core repos. I'm going to do some more testing of the brew uses commands though.

Beyond that I'll take a look at other third-party taps for testing how optional and recommended dependencies respond to these changes. I was able to find a few good ones that should be representative I hope.

I'll also change the code to use public_send instead of send.

@apainintheneck
Copy link
Contributor Author

Never mind, my earlier testing wasn't thorough enough. It looks like one of things the new algorithm wasn't handling well was depends_on cask: <cask_name>. I was not handling required dependencies correctly but now everything should be good.

I did a bit more looking around and it seems like the main difference will be with the missing flag. Other than that things should be pretty similar to before.

- Use .required? instead of .tags.empty?
- use .public_send
- modify .reject_ignores to be .select_includes
  - checks ignores first now
- Don't use runtime deps with --missing in `brew deps` command
@apainintheneck apainintheneck force-pushed the rework-recursive-dependency-resolution branch from b4be0f0 to e314a43 Compare August 25, 2023 07:26
Ignore all dependencies that are already installed before
checking if they use the dependency in question. Remove
the :satisfied? criteria before checking used dependents.
@apainintheneck
Copy link
Contributor Author

I updated the integration tests to cover more of the expected behavior. I thought about adding unit tests but that would involve lots of mocking of formulae so I'm not sure it's worth it. I'm surprised that while the behavior of the brew deps command seems very similar to before but a lot more results are appearing for the brew uses command. Looking at the results I'm not entirely sure why most of these didn't appear in the result before my changes (all of them seem legit).

For example:

/u/l/Homebrew (master|✔) $ brew deps chapel --tree --include-build
chapel
├── cmake
├── gmp
├── llvm@14
│   ├── cmake
│   └── [email protected]
│       ├── pkg-config
│       ├── mpdecimal
│       ├── openssl@3
│       │   └── ca-certificates
│       ├── sqlite
│       │   └── readline
│       └── xz
└── [email protected]
    ├── pkg-config
    ├── mpdecimal
    ├── openssl@3
    │   └── ca-certificates
    ├── sqlite
    │   └── readline
    └── xz

The chapel formula depends on cmake and I don't have it installed locally. Yet it doesn't show up with the brew uses command when paired with --missing.

/u/l/Homebrew (master|✔) $ brew uses cmake --eval-all --include-build --missing | grep chapel

I assume that brew uses should apply the --missing criteria only to the results of the search (that is to say the packages that use the named formula) instead of to the named formula itself. Otherwise, brew uses cmake --missing would always return zero results for me since I already have it installed locally. So I just decided to filter out all of the installed packages before checking their dependencies in that command.

@apainintheneck
Copy link
Contributor Author

I would have liked to test the installed package behavior with --missing but that increased the time taken by the integration test from 5 to 15 seconds on my machine. I wonder if there's a faster way to mock installation in this case. All I want is for one of the formula to respond with true when Formula#any_version_installed? is called.

@MikeMcQuaid
Copy link
Member

I wonder if there's a faster way to mock installation in this case.

Could try to just write the keg's directory on disk, that will likely be enough?

These tests were very simple before and now this should result
in more code coverage without affecting test performance.

The only tricky thing was testing the `--missing` option without
actually installing a package using `install_test_formula` because
that is very slow (around 10 seconds on my machine). I ended
up just writing the tab to a plausible keg directory for each
package I wanted to "install". This allows us to test the behavior
while also not increasing CI time by ~20 seconds (though it'd
probably be faster on CI than my local machine).
- add clarification about precedence of command options
- reword some options for readability
- clarify the defaults in `brew deps`
@apainintheneck apainintheneck force-pushed the rework-recursive-dependency-resolution branch from b05cade to b2b8f0e Compare August 29, 2023 05:16
@apainintheneck
Copy link
Contributor Author

I updated the pr description with some more info, added more stuff to the integration tests and updated the man pages.

These are breaking changes so I think they should probably wait until the next release though the previous behavior was somewhat broken to begin with.

@apainintheneck apainintheneck marked this pull request as ready for review August 29, 2023 05:28
Copy link
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.

Looks great, thanks again @apainintheneck!

@MikeMcQuaid
Copy link
Member

Don't think it's worth waiting for next release or considering these changes breaking considering these are non-default options on a developer command. If enough people loudly complain: we can revert or gate until the next release (but I appreciate the caution nonetheless @apainintheneck).

@MikeMcQuaid
Copy link
Member

@carlocab When you're ✅ here: feel free to 🚢

@Bo98
Copy link
Member

Bo98 commented Aug 29, 2023

To clarify: does this mean formula_options in brew deps no longer does anything?

a developer command

I'm fine with changing, but just to clarify here: this is not a dev-cmd.

@MikeMcQuaid
Copy link
Member

TIL that brew deps even had formula_options. I would be frankly amazed if that's widely used as it is undocumented.

@MikeMcQuaid
Copy link
Member

I'm fine with changing, but just to clarify here: this is not a dev-cmd.

Yes, fair point. It feels like a borderline one.

@apainintheneck
Copy link
Contributor Author

apainintheneck commented Aug 30, 2023

Good catch @Bo98! That was something that hadn't crossed my mind at all.

It looks like this was added in at user request to match how some undocumented behavior used to work (discussion: #5587; pr: #5648). I can look into adding this back in though I believe it's a super niche use case. It'd be nice to have a test for this too.

Note: This never worked before with the --direct option. It only worked when evaluating dependencies recursively.

@MikeMcQuaid
Copy link
Member

It looks like this was added in at user request to match how some undocumented behavior used to work (discussion: #5587; pr: #5648).

The relevant folks here no longer seem to be maintaining taps with options and this was shortly around the time Homebrew/homebrew-core removed all options.

Gonna merge as-is. We can adjust or revert as needed. Thanks @apainintheneck!

@MikeMcQuaid MikeMcQuaid merged commit 851df26 into Homebrew:master Aug 30, 2023
25 checks passed
@apainintheneck
Copy link
Contributor Author

In that case, we should remove the formula_options from the arg parser and the docs should be updated too.

diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb
index bd14a89c9..4ea97820d 100644
--- a/Library/Homebrew/cmd/deps.rb
+++ b/Library/Homebrew/cmd/deps.rb
@@ -13,8 +13,7 @@ module Homebrew
   def self.deps_args
     Homebrew::CLI::Parser.new do
       description <<~EOS
-        Show dependencies for <formula>. Additional options specific to <formula>
-        may be appended to the command. When given multiple formula arguments,
+        Show dependencies for <formula>. When given multiple formula arguments,
         show the intersection of dependencies for each formula. By default, `deps`
         shows all required and recommended dependencies.
 
@@ -71,7 +70,6 @@ module Homebrew
       conflicts "--installed", "--eval-all"
       conflicts "--installed", "--all"
       conflicts "--formula", "--cask"
-      formula_options
 
       named_args [:formula, :cask]
     end

CC: @blogabe and @RandomDSdevel since you both were involved in the original discussion to add formula_options to the brew deps command

@MikeMcQuaid
Copy link
Member

@apainintheneck whoops, sorry, follow-up PR in #15922

@RandomDSdevel
Copy link
Contributor

@MikeMcQuaid:

(Apologies for the mild delay in replying.)

CC: … @RandomDSdevel since you both were involved in the original discussion to add formula_options to the brew deps command

     My efforts remain stalled in extended development purgatory; including due to external factors; this can get revisited if/as needed.

@github-actions github-actions bot added the outdated PR was locked due to age label Oct 1, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Reproducible Homebrew/brew bug outdated PR was locked due to age
Projects
None yet
Development

Successfully merging this pull request may close these issues.

brew deps --include-build & --include-test flags ignore --missing
5 participants