diff --git a/Library/Homebrew/extend/pathname.rb b/Library/Homebrew/extend/pathname.rb index ec7293496e..dfe24b7886 100644 --- a/Library/Homebrew/extend/pathname.rb +++ b/Library/Homebrew/extend/pathname.rb @@ -472,9 +472,17 @@ class Pathname } end + def binary_executable? + false + end + def mach_o_bundle? false end + + def dylib? + false + end end require "extend/os/pathname" diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 72bf39c565..5a146d4bcc 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -12,6 +12,7 @@ require "install_renamed" require "pkg_version" require "keg" require "migrator" +require "linkage_checker" require "extend/ENV" require "language/python" require "tab" @@ -1486,22 +1487,50 @@ class Formula Requirement.expand(self, &block) end + # Returns a Keg for the opt_prefix or installed_prefix if they exist. + # If not, return nil. + # @private + def opt_or_installed_prefix_keg + if optlinked? && opt_prefix.exist? + Keg.new(opt_prefix) + elsif installed_prefix.directory? + Keg.new(installed_prefix) + end + end + # Returns a list of Dependency objects that are required at runtime. # @private def runtime_dependencies(read_from_tab: true) if read_from_tab && installed_prefix.directory? && - (keg = Keg.new(installed_prefix)) && + (keg = opt_or_installed_prefix_keg) && (tab_deps = keg.runtime_dependencies) return tab_deps.map { |d| Dependency.new d["full_name"] }.compact end + declared_runtime_dependencies | undeclared_runtime_dependencies + end + + # Returns a list of Dependency objects that are declared in the formula. + # @private + def declared_runtime_dependencies recursive_dependencies do |_, dependency| Dependency.prune if dependency.build? Dependency.prune if !dependency.required? && build.without?(dependency) end end + # Returns a list of Dependency objects that are not declared in the formula + # but the formula links to. + # @private + def undeclared_runtime_dependencies + keg = opt_or_installed_prefix_keg + return [] unless keg + + linkage_checker = LinkageChecker.new(keg, self) + linkage_checker.undeclared_deps.map { |n| Dependency.new(n) } + end + # Returns a list of formulae depended on by this formula that aren't # installed def missing_dependencies(hide: nil) diff --git a/Library/Homebrew/linkage_checker.rb b/Library/Homebrew/linkage_checker.rb index 09f784bd77..f0318d82c8 100644 --- a/Library/Homebrew/linkage_checker.rb +++ b/Library/Homebrew/linkage_checker.rb @@ -100,7 +100,7 @@ class LinkageChecker checked_dylibs = Set.new @keg.find do |file| next if file.symlink? || file.directory? - next unless file.dylib? || file.binary_executable? || file.mach_o_bundle? + next if !file.dylib? && !file.binary_executable? && !file.mach_o_bundle? # weakly loaded dylibs may not actually exist on disk, so skip them # when checking for broken linkage @@ -145,10 +145,17 @@ class LinkageChecker next false unless dep.optional? || dep.recommended? formula.build.without?(dep) end + declared_deps = formula.deps.reject { |dep| filter_out.call(dep) }.map(&: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 } + recursive_deps = formula.declared_runtime_dependencies.map do |dep| + begin + dep.to_formula.name + rescue FormulaUnavailableError + nil + end + end.compact + indirect_deps = [] undeclared_deps = [] @brewed_dylibs.each_key do |full_name| @@ -160,15 +167,19 @@ class LinkageChecker undeclared_deps << full_name end end + sort_by_formula_full_name!(indirect_deps) sort_by_formula_full_name!(undeclared_deps) + unnecessary_deps = declared_dep_names.reject do |full_name| name = full_name.split("/").last next true if Formula[name].bin.directory? @brewed_dylibs.keys.map { |x| x.split("/").last }.include?(name) end + missing_deps = @broken_deps.values.flatten.map { |d| dylib_to_dep(d) } unnecessary_deps -= missing_deps + [indirect_deps, undeclared_deps, unnecessary_deps] end diff --git a/Library/Homebrew/missing_formula.rb b/Library/Homebrew/missing_formula.rb index 8a80e90d57..3b4682d668 100644 --- a/Library/Homebrew/missing_formula.rb +++ b/Library/Homebrew/missing_formula.rb @@ -93,7 +93,7 @@ module Homebrew new_tap = old_tap.tap_migrations[name] next unless new_tap - new_tap_user, new_tap_repo, = new_tap.split("/") + new_tap_user, new_tap_repo, new_tap_new_name = new_tap.split("/") new_tap_name = "#{new_tap_user}/#{new_tap_repo}" message = <<~EOS @@ -101,9 +101,18 @@ module Homebrew EOS break if new_tap_name == CoreTap.instance.name + install_cmd = if new_tap_user == "caskroom" + "cask install" + else + "install" + end + new_tap_new_name ||= name + message += <<~EOS You can access it again by running: brew tap #{new_tap_name} + And then you can install it by running: + brew #{install_cmd} #{new_tap_new_name} EOS break end diff --git a/Library/Homebrew/rubocops/components_order_cop.rb b/Library/Homebrew/rubocops/components_order_cop.rb index a1a576177b..912f4d3804 100644 --- a/Library/Homebrew/rubocops/components_order_cop.rb +++ b/Library/Homebrew/rubocops/components_order_cop.rb @@ -2,7 +2,7 @@ require_relative "./extend/formula_cop" module RuboCop module Cop - module FormulaAuditStrict + module FormulaAudit # This cop checks for correct order of components in a Formula # # - component_precedence_list has component hierarchy in a nested list @@ -62,8 +62,14 @@ module RuboCop end end + # `aspell`: options and resources should be grouped by language + WHITELIST = %w[ + aspell + ].freeze + # Method to format message for reporting component precedence violations def component_problem(c1, c2) + return if WHITELIST.include?(@formula_name) problem "`#{format_component(c1)}` (line #{line_number(c1)}) should be put before `#{format_component(c2)}` (line #{line_number(c2)})" end diff --git a/Library/Homebrew/rubocops/components_redundancy_cop.rb b/Library/Homebrew/rubocops/components_redundancy_cop.rb index 7495b986b7..e22ec6a910 100644 --- a/Library/Homebrew/rubocops/components_redundancy_cop.rb +++ b/Library/Homebrew/rubocops/components_redundancy_cop.rb @@ -2,7 +2,7 @@ require_relative "./extend/formula_cop" module RuboCop module Cop - module FormulaAuditStrict + module FormulaAudit # This cop checks if redundant components are present and other component errors # # - `url|checksum|mirror` should be inside `stable` block diff --git a/Library/Homebrew/rubocops/formula_desc_cop.rb b/Library/Homebrew/rubocops/formula_desc_cop.rb index 240a280723..faffd42f6c 100644 --- a/Library/Homebrew/rubocops/formula_desc_cop.rb +++ b/Library/Homebrew/rubocops/formula_desc_cop.rb @@ -3,7 +3,7 @@ require_relative "../extend/string" module RuboCop module Cop - module FormulaAuditStrict + module FormulaAudit # This cop audits `desc` in Formulae # # - Checks for existence of `desc` @@ -33,7 +33,9 @@ module RuboCop "Length is calculated as #{@formula_name} + desc. (currently #{desc_length})" end end + end + module FormulaAuditStrict # This cop audits `desc` in Formulae # # - Checks if `desc` begins with an article diff --git a/Library/Homebrew/rubocops/urls_cop.rb b/Library/Homebrew/rubocops/urls_cop.rb index 0ffa19bdf2..30420a793a 100644 --- a/Library/Homebrew/rubocops/urls_cop.rb +++ b/Library/Homebrew/rubocops/urls_cop.rb @@ -191,8 +191,7 @@ module RuboCop end end end - end - module FormulaAuditStrict + class PyPiUrls < FormulaCop def audit_formula(_node, _class_node, _parent_class_node, body_node) urls = find_every_func_call_by_name(body_node, :url) diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index f0be3c2c0f..6ad102756e 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -685,26 +685,42 @@ describe Formula do expect(f5.runtime_dependencies.map(&:name)).to eq(["f1", "f4"]) end - specify "runtime dependencies with optional deps from tap" do - tap_loader = double + describe "#runtime_dependencies" do + specify "runtime dependencies with optional deps from tap" do + tap_loader = double - allow(tap_loader).to receive(:get_formula).and_raise(RuntimeError, "tried resolving tap formula") - allow(Formulary).to receive(:loader_for).with("foo/bar/f1", from: nil).and_return(tap_loader) - stub_formula_loader(formula("f2") { url("f2-1.0") }, "baz/qux/f2") + allow(tap_loader).to receive(:get_formula).and_raise(RuntimeError, "tried resolving tap formula") + allow(Formulary).to receive(:loader_for).with("foo/bar/f1", from: nil).and_return(tap_loader) + stub_formula_loader(formula("f2") { url("f2-1.0") }, "baz/qux/f2") - f3 = formula "f3" do - url "f3-1.0" + f3 = formula "f3" do + url "f3-1.0" - depends_on "foo/bar/f1" => :optional - depends_on "baz/qux/f2" + depends_on "foo/bar/f1" => :optional + depends_on "baz/qux/f2" + end + + expect(f3.runtime_dependencies.map(&:name)).to eq(["baz/qux/f2"]) + + stub_formula_loader(formula("f1") { url("f1-1.0") }, "foo/bar/f1") + f3.build = BuildOptions.new(Options.create(["--with-f1"]), f3.options) + + expect(f3.runtime_dependencies.map(&:name)).to eq(["foo/bar/f1", "baz/qux/f2"]) end - expect(f3.runtime_dependencies.map(&:name)).to eq(["baz/qux/f2"]) + it "includes non-declared direct dependencies", :focus do + formula = Class.new(Testball).new + dependency = formula("dependency") { url "f-1.0" } - stub_formula_loader(formula("f1") { url("f1-1.0") }, "foo/bar/f1") - f3.build = BuildOptions.new(Options.create(["--with-f1"]), f3.options) + formula.brew { formula.install } + keg = Keg.for(formula.installed_prefix) + keg.link - expect(f3.runtime_dependencies.map(&:name)).to eq(["foo/bar/f1", "baz/qux/f2"]) + linkage_checker = double("linkage checker", undeclared_deps: [dependency.name]) + allow(LinkageChecker).to receive(:new).and_return(linkage_checker) + + expect(formula.runtime_dependencies.map(&:name)).to eq [dependency.name] + end end specify "requirements" do diff --git a/Library/Homebrew/test/rubocops/components_order_cop_spec.rb b/Library/Homebrew/test/rubocops/components_order_cop_spec.rb index cd7cc5893a..7466f2d6b6 100644 --- a/Library/Homebrew/test/rubocops/components_order_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/components_order_cop_spec.rb @@ -1,6 +1,6 @@ require_relative "../../rubocops/components_order_cop" -describe RuboCop::Cop::FormulaAuditStrict::ComponentsOrder do +describe RuboCop::Cop::FormulaAudit::ComponentsOrder do subject(:cop) { described_class.new } context "When auditing formula components order" do diff --git a/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb b/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb index d8878ae797..fd325e5846 100644 --- a/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/components_redundancy_cop_spec.rb @@ -1,6 +1,6 @@ require_relative "../../rubocops/components_redundancy_cop" -describe RuboCop::Cop::FormulaAuditStrict::ComponentsRedundancy do +describe RuboCop::Cop::FormulaAudit::ComponentsRedundancy do subject(:cop) { described_class.new } context "When auditing formula components common errors" do diff --git a/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb b/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb index aacb52ebb4..20600f071d 100644 --- a/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/formula_desc_cop_spec.rb @@ -1,6 +1,6 @@ require_relative "../../rubocops/formula_desc_cop" -describe RuboCop::Cop::FormulaAuditStrict::DescLength do +describe RuboCop::Cop::FormulaAudit::DescLength do subject(:cop) { described_class.new } context "When auditing formula desc" do diff --git a/Library/Homebrew/test/rubocops/urls_cop_spec.rb b/Library/Homebrew/test/rubocops/urls_cop_spec.rb index 494028bd50..9f9f6ded1e 100644 --- a/Library/Homebrew/test/rubocops/urls_cop_spec.rb +++ b/Library/Homebrew/test/rubocops/urls_cop_spec.rb @@ -164,7 +164,7 @@ describe RuboCop::Cop::FormulaAudit::Urls do end end -describe RuboCop::Cop::FormulaAuditStrict::PyPiUrls do +describe RuboCop::Cop::FormulaAudit::PyPiUrls do subject(:cop) { described_class.new } context "when a pypi.python.org URL is used" do diff --git a/README.md b/README.md index 240774ed2e..d9bc5dc48c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Second, read the [Troubleshooting Checklist](https://docs.brew.sh/Troubleshootin [![Travis](https://img.shields.io/travis/Homebrew/brew.svg)](https://travis-ci.org/Homebrew/brew) [![Codecov](https://img.shields.io/codecov/c/github/Homebrew/brew.svg)](https://codecov.io/gh/Homebrew/brew) -We'd love you to contribute to Homebrew. First, please read our [Contribution Guide](https://github.com/Homebrew/brew/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/Homebrew/brew/blob/master/CODE_OF_CONDUCT.md#code-of-conduct). +We'd love you to contribute to Homebrew. First, please read our [Contribution Guide](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md#code-of-conduct). We explicitly welcome contributions from people who have never contributed to open-source before: we were all beginners once! We can help build on a partially working pull request with the aim of getting it merged. We are also actively seeking to diversify our contributors and especially welcome contributions from women from all backgrounds and people of colour. @@ -52,7 +52,7 @@ Former maintainers with significant contributions include [Tim Smith](https://gi - [@MacHomebrew (Twitter)](https://twitter.com/MacHomebrew) ## License -Code is under the [BSD 2-clause "Simplified" License](https://github.com/Homebrew/brew/tree/master/LICENSE.txt). +Code is under the [BSD 2-clause "Simplified" License](LICENSE.txt). Documentation is under the [Creative Commons Attribution license](https://creativecommons.org/licenses/by/4.0/). ## Donations