Merge pull request #15892 from apainintheneck/rework-recursive-dependency-resolution

dependency_helpers: rework recursive dependency resolution
This commit is contained in:
Mike McQuaid 2023-08-30 08:29:24 +01:00 committed by GitHub
commit 851df262a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 79 deletions

View File

@ -15,7 +15,10 @@ module Homebrew
description <<~EOS
Show dependencies for <formula>. Additional options specific to <formula>
may be appended to the command. When given multiple formula arguments,
show the intersection of dependencies for each formula.
show the intersection of dependencies for each formula. By default, `deps`
shows all required and recommended dependencies.
Note: `--missing` and `--skip-recommended` have precedence over `--include-*`.
EOS
switch "-n", "--topological",
description: "Sort dependencies in topological order."
@ -90,7 +93,8 @@ module Homebrew
!args.include_build? &&
!args.include_test? &&
!args.include_optional? &&
!args.skip_recommended?
!args.skip_recommended? &&
!args.missing?
if args.tree? || args.graph?
dependents = if args.named.present?
@ -197,8 +201,8 @@ module Homebrew
deps ||= recursive_includes(Dependency, dependency, includes, ignores)
reqs = recursive_includes(Requirement, dependency, includes, ignores)
else
deps ||= reject_ignores(dependency.deps, ignores, includes)
reqs = reject_ignores(dependency.requirements, ignores, includes)
deps ||= select_includes(dependency.deps, ignores, includes)
reqs = select_includes(dependency.requirements, ignores, includes)
end
deps + reqs.to_a
@ -269,8 +273,8 @@ module Homebrew
def self.dependables(formula, args:)
includes, ignores = args_includes_ignores(args)
deps = @use_runtime_dependencies ? formula.runtime_dependencies : formula.deps
deps = reject_ignores(deps, ignores, includes)
reqs = reject_ignores(formula.requirements, ignores, includes) if args.include_requirements?
deps = select_includes(deps, ignores, includes)
reqs = select_includes(formula.requirements, ignores, includes) if args.include_requirements?
reqs ||= []
reqs + deps
end

View File

@ -22,6 +22,8 @@ module Homebrew
of <formula>. When given multiple formula arguments, show the intersection
of formulae that use <formula>. By default, `uses` shows all formulae and casks that
specify <formula> as a required or recommended dependency for their stable builds.
Note: `--missing` and `--skip-recommended` have precedence over `--include-*`.
EOS
switch "--recursive",
description: "Resolve more than one level of dependencies."
@ -33,13 +35,13 @@ module Homebrew
description: "Evaluate all available formulae and casks, whether installed or not, to show " \
"their dependents."
switch "--include-build",
description: "Include all formulae that specify <formula> as `:build` type dependency."
description: "Include formulae that specify <formula> as a `:build` dependency."
switch "--include-test",
description: "Include all formulae that specify <formula> as `:test` type dependency."
description: "Include formulae that specify <formula> as a `:test` dependency."
switch "--include-optional",
description: "Include all formulae that specify <formula> as `:optional` type dependency."
description: "Include formulae that specify <formula> as an `:optional` dependency."
switch "--skip-recommended",
description: "Skip all formulae that specify <formula> as `:recommended` type dependency."
description: "Skip all formulae that specify <formula> as a `:recommended` dependency."
switch "--formula", "--formulae",
description: "Include only formulae."
switch "--cask", "--casks",
@ -120,6 +122,18 @@ module Homebrew
deps += args.installed? ? Cask::Caskroom.casks : Cask::Cask.all
end
if args.missing?
deps.reject! do |dep|
case dep
when Formula
dep.any_version_installed?
when Cask::Cask
dep.installed?
end
end
ignores.delete(:satisfied?)
end
select_used_dependents(dependents(deps), used_formulae, recursive, includes, ignores)
end
end
@ -129,7 +143,7 @@ module Homebrew
deps = if recursive
recursive_includes(Dependency, d, includes, ignores)
else
reject_ignores(d.deps, ignores, includes)
select_includes(d.deps, ignores, includes)
end
used_formulae.all? do |ff|

View File

@ -8,29 +8,14 @@ require "cask_dependent"
# @api private
module DependenciesHelpers
def args_includes_ignores(args)
includes = []
includes = [:required?, :recommended?] # included by default
includes << :build? if args.include_build?
includes << :test? if args.include_test?
includes << :optional? if args.include_optional?
ignores = []
if args.include_build?
includes << "build?"
else
ignores << "build?"
end
if args.include_test?
includes << "test?"
else
ignores << "test?"
end
if args.include_optional?
includes << "optional?"
else
ignores << "optional?"
end
ignores << "recommended?" if args.skip_recommended?
ignores << "satisfied?" if args.missing?
ignores << :recommended? if args.skip_recommended?
ignores << :satisfied? if args.missing?
[includes, ignores]
end
@ -41,17 +26,12 @@ module DependenciesHelpers
cache_key = "recursive_includes_#{includes}_#{ignores}"
klass.expand(root_dependent, cache_key: cache_key) do |dependent, dep|
if dep.recommended?
klass.prune if ignores.include?("recommended?") || dependent.build.without?(dep)
elsif dep.optional?
klass.prune if includes.exclude?("optional?") && !dependent.build.with?(dep)
elsif dep.build? || dep.test?
keep = false
keep ||= dep.test? && includes.include?("test?") && dependent == root_dependent
keep ||= dep.build? && includes.include?("build?")
klass.prune unless keep
elsif dep.satisfied?
klass.prune if ignores.include?("satisfied?")
klass.prune if ignores.any? { |ignore| dep.public_send(ignore) }
klass.prune if includes.none? do |include|
# Ignore indirect test dependencies
next if include == :test? && dependent != root_dependent
dep.public_send(include)
end
# If a tap isn't installed, we can't find the dependencies of one of
@ -60,11 +40,11 @@ module DependenciesHelpers
end
end
def reject_ignores(dependables, ignores, includes)
dependables.reject do |dep|
next false unless ignores.any? { |ignore| dep.send(ignore) }
def select_includes(dependables, ignores, includes)
dependables.select do |dep|
next false if ignores.any? { |ignore| dep.public_send(ignore) }
includes.none? { |include| dep.send(include) }
includes.any? { |include| dep.public_send(include) }
end
end

View File

@ -6,16 +6,34 @@ describe "brew deps" do
it_behaves_like "parseable arguments"
it "outputs all of a Formula's dependencies and their dependencies on separate lines", :integration_test do
setup_test_formula "foo"
# Included in output
setup_test_formula "bar"
setup_test_formula "foo"
setup_test_formula "test"
# Excluded from output
setup_test_formula "baz", <<~RUBY
url "https://brew.sh/baz-1.0"
depends_on "bar"
depends_on "build" => :build
depends_on "test" => :test
depends_on "optional" => :optional
depends_on "recommended_test" => [:recommended, :test]
depends_on "installed"
RUBY
setup_test_formula "build"
setup_test_formula "optional"
setup_test_formula "recommended_test"
setup_test_formula "installed"
expect { brew "deps", "baz" }
# Mock `Formula#any_version_installed?` by creating the tab in a plausible keg directory
keg_dir = HOMEBREW_CELLAR/"installed"/"1.0"
keg_dir.mkpath
touch keg_dir/Tab::FILENAME
expect { brew "deps", "baz", "--include-test", "--missing", "--skip-recommended" }
.to be_a_success
.and output("bar\nfoo\n").to_stdout
.and output("bar\nfoo\ntest\n").to_stdout
.and not_to_output.to_stderr
end
end

View File

@ -6,15 +6,37 @@ describe "brew uses" do
it_behaves_like "parseable arguments"
it "prints the Formulae a given Formula is used by", :integration_test do
setup_test_formula "foo"
# Included in output
setup_test_formula "bar"
setup_test_formula "baz", <<~RUBY
url "https://brew.sh/baz-1.0"
depends_on "bar"
setup_test_formula "optional", <<~RUBY
url "https://brew.sh/optional-1.0"
depends_on "bar" => :optional
RUBY
expect { brew "uses", "--eval-all", "--recursive", "foo" }
.to output(/(bar\nbaz|baz\nbar)/).to_stdout
# Excluded from output
setup_test_formula "foo"
setup_test_formula "test", <<~RUBY
url "https://brew.sh/test-1.0"
depends_on "foo" => :test
RUBY
setup_test_formula "build", <<~RUBY
url "https://brew.sh/build-1.0"
depends_on "foo" => :build
RUBY
setup_test_formula "installed", <<~RUBY
url "https://brew.sh/installed-1.0"
depends_on "foo"
RUBY
# Mock `Formula#any_version_installed?` by creating the tab in a plausible keg directory
%w[foo installed].each do |formula_name|
keg_dir = HOMEBREW_CELLAR/formula_name/"1.0"
keg_dir.mkpath
touch keg_dir/Tab::FILENAME
end
expect { brew "uses", "foo", "--eval-all", "--include-optional", "--missing", "--recursive" }
.to output(/^(bar\noptional|optional\nbar)$/).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end

View File

@ -166,10 +166,6 @@ RSpec.shared_context "integration test" do # rubocop:disable RSpec/ContextWordin
# something here
RUBY
when "foo", "gnupg"
content = <<~RUBY
url "https://brew.sh/#{name}-1.0"
RUBY
when "bar"
content = <<~RUBY
url "https://brew.sh/#{name}-1.0"
@ -180,6 +176,10 @@ RSpec.shared_context "integration test" do # rubocop:disable RSpec/ContextWordin
url "https://brew.sh/#patchelf-1.0"
license "0BSD"
RUBY
else
content ||= <<~RUBY
url "https://brew.sh/#{name}-1.0"
RUBY
end
Formulary.core_path(name).tap do |formula_path|

View File

@ -1700,14 +1700,14 @@ __fish_brew_complete_arg 'uses' -l debug -d 'Display any debugging information'
__fish_brew_complete_arg 'uses' -l eval-all -d 'Evaluate all available formulae and casks, whether installed or not, to show their dependents'
__fish_brew_complete_arg 'uses' -l formula -d 'Include only formulae'
__fish_brew_complete_arg 'uses' -l help -d 'Show this message'
__fish_brew_complete_arg 'uses' -l include-build -d 'Include all formulae that specify formula as `:build` type dependency'
__fish_brew_complete_arg 'uses' -l include-optional -d 'Include all formulae that specify formula as `:optional` type dependency'
__fish_brew_complete_arg 'uses' -l include-test -d 'Include all formulae that specify formula as `:test` type dependency'
__fish_brew_complete_arg 'uses' -l include-build -d 'Include formulae that specify formula as a `:build` dependency'
__fish_brew_complete_arg 'uses' -l include-optional -d 'Include formulae that specify formula as an `:optional` dependency'
__fish_brew_complete_arg 'uses' -l include-test -d 'Include formulae that specify formula as a `:test` dependency'
__fish_brew_complete_arg 'uses' -l installed -d 'Only list formulae and casks that are currently installed'
__fish_brew_complete_arg 'uses' -l missing -d 'Only list formulae and casks that are not currently installed'
__fish_brew_complete_arg 'uses' -l quiet -d 'Make some output more quiet'
__fish_brew_complete_arg 'uses' -l recursive -d 'Resolve more than one level of dependencies'
__fish_brew_complete_arg 'uses' -l skip-recommended -d 'Skip all formulae that specify formula as `:recommended` type dependency'
__fish_brew_complete_arg 'uses' -l skip-recommended -d 'Skip all formulae that specify formula as a `:recommended` dependency'
__fish_brew_complete_arg 'uses' -l verbose -d 'Make some output more verbose'
__fish_brew_complete_arg 'uses; and not __fish_seen_argument -l cask -l casks' -a '(__fish_brew_suggest_formulae_all)'

View File

@ -2092,14 +2092,14 @@ _brew_uses() {
'--debug[Display any debugging information]' \
'--eval-all[Evaluate all available formulae and casks, whether installed or not, to show their dependents]' \
'--help[Show this message]' \
'--include-build[Include all formulae that specify formula as `:build` type dependency]' \
'--include-optional[Include all formulae that specify formula as `:optional` type dependency]' \
'--include-test[Include all formulae that specify formula as `:test` type dependency]' \
'--include-build[Include formulae that specify formula as a `:build` dependency]' \
'--include-optional[Include formulae that specify formula as an `:optional` dependency]' \
'--include-test[Include formulae that specify formula as a `:test` dependency]' \
'(--all --missing)--installed[Only list formulae and casks that are currently installed]' \
'(--installed)--missing[Only list formulae and casks that are not currently installed]' \
'--quiet[Make some output more quiet]' \
'--recursive[Resolve more than one level of dependencies]' \
'--skip-recommended[Skip all formulae that specify formula as `:recommended` type dependency]' \
'--skip-recommended[Skip all formulae that specify formula as a `:recommended` dependency]' \
'--verbose[Make some output more verbose]' \
- formula \
'(--cask)--formula[Include only formulae]' \

View File

@ -139,7 +139,10 @@ a bug report, you will be required to provide this information.
Show dependencies for *`formula`*. Additional options specific to *`formula`*
may be appended to the command. When given multiple formula arguments,
show the intersection of dependencies for each formula.
show the intersection of dependencies for each formula. By default, `deps`
shows all required and recommended dependencies.
Note: `--missing` and `--skip-recommended` have precedence over `--include-*`.
* `-n`, `--topological`:
Sort dependencies in topological order.
@ -819,6 +822,8 @@ of *`formula`*. When given multiple formula arguments, show the intersection
of formulae that use *`formula`*. By default, `uses` shows all formulae and casks that
specify *`formula`* as a required or recommended dependency for their stable builds.
Note: `--missing` and `--skip-recommended` have precedence over `--include-*`.
* `--recursive`:
Resolve more than one level of dependencies.
* `--installed`:
@ -828,13 +833,13 @@ specify *`formula`* as a required or recommended dependency for their stable bui
* `--eval-all`:
Evaluate all available formulae and casks, whether installed or not, to show their dependents.
* `--include-build`:
Include all formulae that specify *`formula`* as `:build` type dependency.
Include formulae that specify *`formula`* as a `:build` dependency.
* `--include-test`:
Include all formulae that specify *`formula`* as `:test` type dependency.
Include formulae that specify *`formula`* as a `:test` dependency.
* `--include-optional`:
Include all formulae that specify *`formula`* as `:optional` type dependency.
Include formulae that specify *`formula`* as an `:optional` dependency.
* `--skip-recommended`:
Skip all formulae that specify *`formula`* as `:recommended` type dependency.
Skip all formulae that specify *`formula`* as a `:recommended` dependency.
* `--formula`:
Include only formulae.
* `--cask`:

View File

@ -155,7 +155,10 @@ Control whether Homebrew automatically links external tap shell completion files
Show Homebrew and system configuration info useful for debugging\. If you file a bug report, you will be required to provide this information\.
.
.SS "\fBdeps\fR [\fIoptions\fR] [\fIformula\fR|\fIcask\fR \.\.\.]"
Show dependencies for \fIformula\fR\. Additional options specific to \fIformula\fR may be appended to the command\. When given multiple formula arguments, show the intersection of dependencies for each formula\.
Show dependencies for \fIformula\fR\. Additional options specific to \fIformula\fR may be appended to the command\. When given multiple formula arguments, show the intersection of dependencies for each formula\. By default, \fBdeps\fR shows all required and recommended dependencies\.
.
.P
Note: \fB\-\-missing\fR and \fB\-\-skip\-recommended\fR have precedence over \fB\-\-include\-*\fR\.
.
.TP
\fB\-n\fR, \fB\-\-topological\fR
@ -1149,6 +1152,9 @@ Disable/enable quarantining of downloads (default: enabled)\.
.SS "\fBuses\fR [\fIoptions\fR] \fIformula\fR [\.\.\.]"
Show formulae and casks that specify \fIformula\fR as a dependency; that is, show dependents of \fIformula\fR\. When given multiple formula arguments, show the intersection of formulae that use \fIformula\fR\. By default, \fBuses\fR shows all formulae and casks that specify \fIformula\fR as a required or recommended dependency for their stable builds\.
.
.P
Note: \fB\-\-missing\fR and \fB\-\-skip\-recommended\fR have precedence over \fB\-\-include\-*\fR\.
.
.TP
\fB\-\-recursive\fR
Resolve more than one level of dependencies\.
@ -1167,19 +1173,19 @@ Evaluate all available formulae and casks, whether installed or not, to show the
.
.TP
\fB\-\-include\-build\fR
Include all formulae that specify \fIformula\fR as \fB:build\fR type dependency\.
Include formulae that specify \fIformula\fR as a \fB:build\fR dependency\.
.
.TP
\fB\-\-include\-test\fR
Include all formulae that specify \fIformula\fR as \fB:test\fR type dependency\.
Include formulae that specify \fIformula\fR as a \fB:test\fR dependency\.
.
.TP
\fB\-\-include\-optional\fR
Include all formulae that specify \fIformula\fR as \fB:optional\fR type dependency\.
Include formulae that specify \fIformula\fR as an \fB:optional\fR dependency\.
.
.TP
\fB\-\-skip\-recommended\fR
Skip all formulae that specify \fIformula\fR as \fB:recommended\fR type dependency\.
Skip all formulae that specify \fIformula\fR as a \fB:recommended\fR dependency\.
.
.TP
\fB\-\-formula\fR