diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb index 6a7348c792..a3bd2b8b58 100644 --- a/Library/Homebrew/cmd/deps.rb +++ b/Library/Homebrew/cmd/deps.rb @@ -75,29 +75,39 @@ module Homebrew raise FormulaUnspecifiedError if ARGV.named.empty? puts_deps_tree ARGV.formulae, !ARGV.one? end + return elsif mode.all? puts_deps Formula.sort - elsif ARGV.named.empty? + return + elsif !ARGV.named.empty? && mode.for_each? + puts_deps ARGV.formulae + return + end + + @only_installed_arg = ARGV.include?("--installed") && + !ARGV.include?("--include-build") && + !ARGV.include?("--include-test") && + !ARGV.include?("--include-optional") && + !ARGV.include?("--skip-recommended") + + if ARGV.named.empty? raise FormulaUnspecifiedError unless mode.installed? puts_deps Formula.installed.sort - elsif mode.for_each? - puts_deps ARGV.formulae - else - all_deps = deps_for_formulae(ARGV.formulae, !ARGV.one?, &(mode.union? ? :| : :&)) - all_deps = condense_requirements(all_deps) - all_deps = all_deps.select(&:installed?) if mode.installed? - all_deps = all_deps.map(&method(:dep_display_name)).uniq - all_deps.sort! unless mode.topo_order? - puts all_deps + return end + + all_deps = deps_for_formulae(ARGV.formulae, !ARGV.one?, &(mode.union? ? :| : :&)) + all_deps = condense_requirements(all_deps) + all_deps.select!(&:installed?) if mode.installed? + all_deps.map!(&method(:dep_display_name)) + all_deps.uniq! + all_deps.sort! unless mode.topo_order? + puts all_deps end def condense_requirements(deps) - if ARGV.include?("--include-requirements") - deps - else - deps.select { |dep| dep.is_a? Dependency } - end + return deps if ARGV.include?("--include-requirements") + deps.select { |dep| dep.is_a? Dependency } end def dep_display_name(dep) @@ -108,27 +118,33 @@ module Homebrew # This shouldn't happen, but we'll put something here to help debugging "::#{dep.name}" end + elsif ARGV.include?("--full-name") + dep.to_formula.full_name else - ARGV.include?("--full-name") ? dep.to_formula.full_name : dep.name + dep.name end + if ARGV.include?("--annotate") str = "#{str} [build]" if dep.build? str = "#{str} [test]" if dep.test? str = "#{str} [optional" if dep.optional? str = "#{str} [recommended]" if dep.recommended? end + str end def deps_for_formula(f, recursive = false) includes, ignores = argv_includes_ignores(ARGV) + deps = f.runtime_dependencies if @only_installed_arg + if recursive - deps = recursive_includes(Dependency, f, includes, ignores) - reqs = recursive_includes(Requirement, f, includes, ignores) + deps ||= recursive_includes(Dependency, f, includes, ignores) + reqs = recursive_includes(Requirement, f, includes, ignores) else - deps = reject_ignores(f.deps, ignores, includes) - reqs = reject_ignores(f.requirements, ignores, includes) + deps ||= reject_ignores(f.deps, ignores, includes) + reqs = reject_ignores(f.requirements, ignores, includes) end deps + reqs.to_a @@ -142,7 +158,8 @@ module Homebrew formulae.each do |f| deps = deps_for_formula(f) deps = condense_requirements(deps) - deps = deps.sort_by(&:name).map(&method(:dep_display_name)) + deps.sort_by!(&:name) + deps.map!(&method(:dep_display_name)) puts "#{f.full_name}: #{deps.join(" ")}" end end @@ -160,33 +177,39 @@ module Homebrew reqs = f.requirements deps = f.deps dependables = reqs + deps - dependables = dependables.reject(&:optional?) unless ARGV.include?("--include-optional") - dependables = dependables.reject(&:build?) unless ARGV.include?("--include-build") - dependables = dependables.reject(&:test?) unless ARGV.include?("--include-test") - dependables = dependables.reject(&:recommended?) if ARGV.include?("--skip-recommended") + dependables.reject!(&:optional?) unless ARGV.include?("--include-optional") + dependables.reject!(&:build?) unless ARGV.include?("--include-build") + dependables.reject!(&:test?) unless ARGV.include?("--include-test") + dependables.reject!(&:recommended?) if ARGV.include?("--skip-recommended") max = dependables.length - 1 @dep_stack.push f.name dependables.each_with_index do |dep, i| next if !ARGV.include?("--include-requirements") && dep.is_a?(Requirement) + tree_lines = if i == max "└──" else "├──" end + display_s = "#{tree_lines} #{dep_display_name(dep)}" is_circular = @dep_stack.include?(dep.name) display_s = "#{display_s} (CIRCULAR DEPENDENCY)" if is_circular puts "#{prefix}#{display_s}" + next if !recursive || is_circular + prefix_addition = if i == max " " else "│ " end + if dep.is_a? Dependency recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_addition, true) end end + @dep_stack.pop end end diff --git a/Library/Homebrew/cmd/leaves.rb b/Library/Homebrew/cmd/leaves.rb index ecebedbb35..ecc4394679 100644 --- a/Library/Homebrew/cmd/leaves.rb +++ b/Library/Homebrew/cmd/leaves.rb @@ -10,15 +10,12 @@ module Homebrew def leaves installed = Formula.installed.sort - deps_of_installed = Set.new - installed.each do |f| - deps = f.runtime_dependencies.map { |d| d.to_formula.full_name } - deps_of_installed.merge(deps) + deps_of_installed = installed.flat_map do |f| + f.runtime_dependencies.map(&:to_formula).map(&:full_name) end - installed.each do |f| - puts f.full_name unless deps_of_installed.include? f.full_name - end + leaves = installed.map(&:full_name) - deps_of_installed + leaves.each(&method(:puts)) end end diff --git a/Library/Homebrew/cmd/uses.rb b/Library/Homebrew/cmd/uses.rb index 416f6e9032..98ecca0f5d 100644 --- a/Library/Homebrew/cmd/uses.rb +++ b/Library/Homebrew/cmd/uses.rb @@ -42,14 +42,21 @@ module Homebrew formulae = ARGV.include?("--installed") ? Formula.installed : Formula recursive = ARGV.flag? "--recursive" + only_installed_arg = ARGV.include?("--installed") && + !ARGV.include?("--include-build") && + !ARGV.include?("--include-test") && + !ARGV.include?("--include-optional") && + !ARGV.include?("--skip-recommended") includes, ignores = argv_includes_ignores(ARGV) uses = formulae.select do |f| used_formulae.all? do |ff| begin + deps = f.runtime_dependencies if only_installed_arg if recursive - deps = recursive_includes(Dependency, f, includes, ignores) + deps ||= recursive_includes(Dependency, f, includes, ignores) + dep_formulae = deps.flat_map do |dep| begin dep.to_formula @@ -76,8 +83,8 @@ module Homebrew reqs = reqs_by_formula.map(&:last) else - deps = reject_ignores(f.deps, ignores, includes) - reqs = reject_ignores(f.requirements, ignores, includes) + deps ||= reject_ignores(f.deps, ignores, includes) + reqs = reject_ignores(f.requirements, ignores, includes) end next true if deps.any? do |dep| diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 706bb201a9..36df53794f 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -11,11 +11,9 @@ module Homebrew missing = {} ff.each do |f| missing_dependencies = f.missing_dependencies(hide: hide) - - unless missing_dependencies.empty? - yield f.full_name, missing_dependencies if block_given? - missing[f.full_name] = missing_dependencies - end + next if missing_dependencies.empty? + yield f.full_name, missing_dependencies if block_given? + missing[f.full_name] = missing_dependencies end missing end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index b41e5c5375..305a6398af 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -15,6 +15,7 @@ require "keg" require "migrator" require "extend/ENV" require "language/python" +require "tab" # A formula provides instructions and metadata for Homebrew to install a piece # of software. Every Homebrew formula is a {Formula}. @@ -438,7 +439,6 @@ class Formula # If at least one version of {Formula} is installed. # @private def any_version_installed? - require "tab" installed_prefixes.any? { |keg| (keg/Tab::FILENAME).file? } end @@ -1486,7 +1486,14 @@ class Formula # Returns a list of Dependency objects that are required at runtime. # @private - def runtime_dependencies + def runtime_dependencies(read_from_tab: true) + if read_from_tab && + installed_prefix.directory? && + (keg = Keg.new(installed_prefix)) && + (tab_deps = keg.runtime_dependencies) + return tab_deps.map { |d| Dependency.new d["full_name"] }.compact + end + recursive_dependencies do |_, dependency| Dependency.prune if dependency.build? Dependency.prune if !dependency.required? && build.without?(dependency) @@ -1497,20 +1504,9 @@ class Formula # installed def missing_dependencies(hide: nil) hide ||= [] - missing_dependencies = recursive_dependencies do |dependent, dep| - if dep.build? - Dependency.prune - elsif dep.optional? || dep.recommended? - tab = Tab.for_formula(dependent) - Dependency.prune unless tab.with?(dep) - end - end - - missing_dependencies.map!(&:to_formula) - missing_dependencies.select! do |d| + runtime_dependencies.map(&:to_formula).select do |d| hide.include?(d.name) || d.installed_prefixes.empty? end - missing_dependencies rescue FormulaUnavailableError [] end diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 2058d1e553..04c98c9556 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -102,47 +102,30 @@ class Keg # # For efficiency, we don't bother trying to get complete data. def self.find_some_installed_dependents(kegs) - # First, check in the tabs of installed Formulae. - kegs.each do |keg| - # Don't include dependencies of kegs that were in the given array. - dependents = keg.installed_dependents - kegs - dependents.map! { |d| "#{d.name} #{d.version}" } - return [keg], dependents if dependents.any? - end - - # Some kegs won't have modern Tabs with the dependencies listed. - # In this case, fall back to Formula#missing_dependencies. - - # Find formulae that didn't have dependencies saved in all of their kegs, - # so need them to be calculated now. - # - # This happens after the initial dependency check because it's sloooow. - remaining_formulae = Formula.installed.select do |f| - installed_kegs = f.installed_kegs - - # Don't include dependencies of kegs that were in the given array. - next false if (installed_kegs - kegs).empty? - - installed_kegs.any? { |k| Tab.for_keg(k).runtime_dependencies.nil? } - end - - keg_names = kegs.map(&:name) + keg_names = kegs.select(&:optlinked?).map(&:name) + keg_formulae = [] kegs_by_source = kegs.group_by do |keg| begin # First, attempt to resolve the keg to a formula # to get up-to-date name and tap information. f = keg.to_formula + keg_formulae << f [f.name, f.tap] rescue FormulaUnavailableError # If the formula for the keg can't be found, # fall back to the information in the tab. - [keg.name, Tab.for_keg(keg).tap] + [keg.name, keg.tab.tap] end end - remaining_formulae.each do |dependent| - required = dependent.missing_dependencies(hide: keg_names) + all_required_kegs = Set.new + all_dependents = [] + # Don't include dependencies of kegs that were in the given array. + formulae_to_check = Formula.installed - keg_formulae + + formulae_to_check.each do |dependent| + required = dependent.missing_dependencies(hide: keg_names) required_kegs = required.map do |f| f_kegs = kegs_by_source[[f.name, f.tap]] next unless f_kegs @@ -150,12 +133,16 @@ class Keg f_kegs.sort_by(&:version).last end.compact - next unless required_kegs.any? + next if required_kegs.empty? - return required_kegs, [dependent.to_s] + all_required_kegs += required_kegs + all_dependents << dependent.to_s end - nil + return if all_required_kegs.empty? + return if all_dependents.empty? + + [all_required_kegs.to_a, all_dependents.sort] end # if path is a file in a keg then this will return the containing Keg object @@ -390,25 +377,6 @@ class Keg Formulary.from_keg(self) end - def installed_dependents - return [] unless optlinked? - tap = Tab.for_keg(self).source["tap"] - Keg.all.select do |keg| - tab = Tab.for_keg(keg) - next if tab.runtime_dependencies.nil? - tab.runtime_dependencies.any? do |dep| - # Resolve formula rather than directly comparing names - # in case of conflicts between formulae from different taps. - begin - dep_formula = Formulary.factory(dep["full_name"]) - dep_formula == to_formula - rescue FormulaUnavailableError - next dep["full_name"] == "#{tap}/#{name}" - end - end - end - end - def oldname_opt_record @oldname_opt_record ||= if (opt_dir = HOMEBREW_PREFIX/"opt").directory? opt_dir.subdirs.detect do |dir| @@ -503,8 +471,16 @@ class Keg @oldname_opt_record = nil end + def tab + Tab.for_keg(self) + end + + def runtime_dependencies + tab.runtime_dependencies + end + def aliases - Tab.for_keg(self).aliases || [] + tab.aliases || [] end def optlink(mode = OpenStruct.new) diff --git a/Library/Homebrew/linkage_checker.rb b/Library/Homebrew/linkage_checker.rb index fb1aba067e..a33454a039 100644 --- a/Library/Homebrew/linkage_checker.rb +++ b/Library/Homebrew/linkage_checker.rb @@ -63,7 +63,8 @@ class LinkageChecker formula.build.without?(dep) end declared_deps = formula.deps.reject { |dep| filter_out.call(dep) }.map(&:name) - recursive_deps = keg.to_formula.runtime_dependencies.map { |dep| dep.to_formula.name } + runtime_deps = keg.to_formula.runtime_dependencies(read_from_tab: false) + recursive_deps = runtime_deps.map { |dep| dep.to_formula.name } declared_dep_names = declared_deps.map { |dep| dep.split("/").last } indirect_deps = [] undeclared_deps = [] diff --git a/Library/Homebrew/tab.rb b/Library/Homebrew/tab.rb index 02a7276683..7bfa2e36cd 100644 --- a/Library/Homebrew/tab.rb +++ b/Library/Homebrew/tab.rb @@ -17,6 +17,7 @@ class Tab < OpenStruct # Instantiates a Tab for a new installation of a formula. def self.create(formula, compiler, stdlib) build = formula.build + runtime_deps = formula.runtime_dependencies(read_from_tab: false) attributes = { "homebrew_version" => HOMEBREW_VERSION, "used_options" => build.used_options.as_flags, @@ -32,7 +33,7 @@ class Tab < OpenStruct "compiler" => compiler, "stdlib" => stdlib, "aliases" => formula.aliases, - "runtime_dependencies" => formula.runtime_dependencies.map do |dep| + "runtime_dependencies" => runtime_deps.map do |dep| f = dep.to_formula { "full_name" => f.full_name, "version" => f.version.to_s } end, @@ -185,7 +186,7 @@ class Tab < OpenStruct "stdlib" => nil, "compiler" => DevelopmentTools.default_compiler, "aliases" => [], - "runtime_dependencies" => [], + "runtime_dependencies" => nil, "source" => { "path" => nil, "tap" => nil, diff --git a/Library/Homebrew/test/keg_spec.rb b/Library/Homebrew/test/keg_spec.rb index 3bf1257e65..3aa5d5b211 100644 --- a/Library/Homebrew/test/keg_spec.rb +++ b/Library/Homebrew/test/keg_spec.rb @@ -373,29 +373,13 @@ describe Keg do let(:dependent) { setup_test_keg("bar", "1.0") } - # Test with a keg whose formula isn't known. - # This can happen if e.g. a formula is installed - # from a file path or URL. - specify "unknown Formula" do - allow(Formulary).to receive(:loader_for).and_call_original - alter_tab(keg) do |t| - t.source["tap"] = "some/tap" - t.source["path"] = nil - end - - dependencies [{ "full_name" => "some/tap/foo", "version" => "1.0" }] - expect(keg.installed_dependents).to eq([dependent]) - expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar 1.0"]]) - - dependencies nil - # It doesn't make sense for a keg with no formula to have any dependents, - # so that can't really be tested. - expect(described_class.find_some_installed_dependents([keg])).to be nil - end - specify "a dependency with no Tap in Tab" do tap_dep = setup_test_keg("baz", "1.0") + # allow tap_dep to be linked too + FileUtils.rm_r tap_dep/"bin" + tap_dep.link + alter_tab(keg) { |t| t.source["tap"] = nil } dependencies nil @@ -408,21 +392,18 @@ describe Keg do specify "no dependencies anywhere" do dependencies nil - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg])).to be nil end specify "missing Formula dependency" do dependencies nil Formula["bar"].class.depends_on "foo" - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar"]]) end specify "uninstalling dependent and dependency" do dependencies nil Formula["bar"].class.depends_on "foo" - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg, dependent])).to be nil end @@ -442,40 +423,34 @@ describe Keg do specify "empty dependencies in Tab" do dependencies [] - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg])).to be nil end specify "same name but different version in Tab" do dependencies [{ "full_name" => "foo", "version" => "1.1" }] - expect(keg.installed_dependents).to eq([dependent]) - expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar 1.0"]]) + expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar"]]) end specify "different name and same version in Tab" do stub_formula_name("baz") dependencies [{ "full_name" => "baz", "version" => keg.version.to_s }] - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg])).to be nil end specify "same name and version in Tab" do dependencies [{ "full_name" => "foo", "version" => "1.0" }] - expect(keg.installed_dependents).to eq([dependent]) - expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar 1.0"]]) + expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar"]]) end specify "fallback for old versions" do unreliable_dependencies [{ "full_name" => "baz", "version" => "1.0" }] Formula["bar"].class.depends_on "foo" - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar"]]) end specify "non-opt-linked" do keg.remove_opt_record dependencies [{ "full_name" => "foo", "version" => "1.0" }] - expect(keg.installed_dependents).to be_empty expect(described_class.find_some_installed_dependents([keg])).to be nil end @@ -483,8 +458,7 @@ describe Keg do keg.unlink Formula["foo"].class.keg_only "a good reason" dependencies [{ "full_name" => "foo", "version" => "1.1" }] # different version - expect(keg.installed_dependents).to eq([dependent]) - expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar 1.0"]]) + expect(described_class.find_some_installed_dependents([keg])).to eq([[keg], ["bar"]]) end end end diff --git a/Library/Homebrew/test/tab_spec.rb b/Library/Homebrew/test/tab_spec.rb index 93ae42ce49..faf778e05e 100644 --- a/Library/Homebrew/test/tab_spec.rb +++ b/Library/Homebrew/test/tab_spec.rb @@ -68,7 +68,7 @@ describe Tab do expect(tab.tap).to be nil expect(tab.time).to be nil expect(tab.HEAD).to be nil - expect(tab.runtime_dependencies).to be_empty + expect(tab.runtime_dependencies).to be nil expect(tab.stable_version).to be nil expect(tab.devel_version).to be nil expect(tab.head_version).to be nil