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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Library/Homebrew/cmd/deps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
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."
Expand Down Expand Up @@ -90,7 +93,8 @@
!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?
Expand Down Expand Up @@ -197,8 +201,8 @@
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)

Check warning on line 205 in Library/Homebrew/cmd/deps.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cmd/deps.rb#L205

Added line #L205 was not covered by tests
end

deps + reqs.to_a
Expand Down Expand Up @@ -269,8 +273,8 @@
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)

Check warning on line 276 in Library/Homebrew/cmd/deps.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/cmd/deps.rb#L276

Added line #L276 was not covered by tests
reqs = select_includes(formula.requirements, ignores, includes) if args.include_requirements?
reqs ||= []
reqs + deps
end
Expand Down
24 changes: 19 additions & 5 deletions Library/Homebrew/cmd/uses.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def self.uses_args
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."
Expand All @@ -33,13 +35,13 @@ def self.uses_args
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",
Expand Down Expand Up @@ -120,6 +122,18 @@ def self.intersection_of_dependents(use_runtime_dependents, used_formulae, args:
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
Expand All @@ -129,7 +143,7 @@ def self.select_used_dependents(dependents, used_formulae, recursive, includes,
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|
Expand Down
54 changes: 17 additions & 37 deletions Library/Homebrew/dependencies_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,14 @@
# @api private
module DependenciesHelpers
def args_includes_ignores(args)
includes = []
ignores = []

if args.include_build?
includes << "build?"
else
ignores << "build?"
end

if args.include_test?
includes << "test?"
else
ignores << "test?"
end
includes = [:required?, :recommended?] # included by default
includes << :build? if args.include_build?
includes << :test? if args.include_test?
includes << :optional? if args.include_optional?

if args.include_optional?
includes << "optional?"
else
ignores << "optional?"
end

ignores << "recommended?" if args.skip_recommended?
ignores << "satisfied?" if args.missing?
ignores = []
ignores << :recommended? if args.skip_recommended?
ignores << :satisfied? if args.missing?

[includes, ignores]
end
Expand All @@ -41,17 +26,12 @@
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
Expand All @@ -60,11 +40,11 @@
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|

Check warning on line 44 in Library/Homebrew/dependencies_helpers.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/dependencies_helpers.rb#L44

Added line #L44 was not covered by tests
next false if ignores.any? { |ignore| dep.public_send(ignore) }

includes.none? { |include| dep.send(include) }
includes.any? { |include| dep.public_send(include) }

Check warning on line 47 in Library/Homebrew/dependencies_helpers.rb

View check run for this annotation

Codecov / codecov/patch

Library/Homebrew/dependencies_helpers.rb#L47

Added line #L47 was not covered by tests
end
end

Expand Down
24 changes: 21 additions & 3 deletions Library/Homebrew/test/cmd/deps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,34 @@
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"

# 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" }
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
34 changes: 28 additions & 6 deletions Library/Homebrew/test/cmd/uses_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,37 @@
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

# 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", "--eval-all", "--recursive", "foo" }
.to output(/(bar\nbaz|baz\nbar)/).to_stdout
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@ def install

# 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"
Expand All @@ -180,6 +176,10 @@ def install
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|
Expand Down
8 changes: 4 additions & 4 deletions completions/fish/brew.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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)'

Expand Down
8 changes: 4 additions & 4 deletions completions/zsh/_brew
Original file line number Diff line number Diff line change
Expand Up @@ -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]' \
Expand Down
15 changes: 10 additions & 5 deletions docs/Manpage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`:
Expand All @@ -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`:
Expand Down
Loading
Loading