diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 8c23b93eec..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| 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