Merge pull request #3789 from alyssais/undeclared_runtime_dependencies

Undeclared dependencies in runtime_dependencies
This commit is contained in:
Mike McQuaid 2018-04-25 13:10:07 +01:00 committed by GitHub
commit 0fd2db347b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 17 deletions

View File

@ -472,9 +472,17 @@ class Pathname
} }
end end
def binary_executable?
false
end
def mach_o_bundle? def mach_o_bundle?
false false
end end
def dylib?
false
end
end end
require "extend/os/pathname" require "extend/os/pathname"

View File

@ -12,6 +12,7 @@ require "install_renamed"
require "pkg_version" require "pkg_version"
require "keg" require "keg"
require "migrator" require "migrator"
require "linkage_checker"
require "extend/ENV" require "extend/ENV"
require "language/python" require "language/python"
require "tab" require "tab"
@ -1486,22 +1487,50 @@ class Formula
Requirement.expand(self, &block) Requirement.expand(self, &block)
end 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. # Returns a list of Dependency objects that are required at runtime.
# @private # @private
def runtime_dependencies(read_from_tab: true) def runtime_dependencies(read_from_tab: true)
if read_from_tab && if read_from_tab &&
installed_prefix.directory? && installed_prefix.directory? &&
(keg = Keg.new(installed_prefix)) && (keg = opt_or_installed_prefix_keg) &&
(tab_deps = keg.runtime_dependencies) (tab_deps = keg.runtime_dependencies)
return tab_deps.map { |d| Dependency.new d["full_name"] }.compact return tab_deps.map { |d| Dependency.new d["full_name"] }.compact
end 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| recursive_dependencies do |_, dependency|
Dependency.prune if dependency.build? Dependency.prune if dependency.build?
Dependency.prune if !dependency.required? && build.without?(dependency) Dependency.prune if !dependency.required? && build.without?(dependency)
end end
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 # Returns a list of formulae depended on by this formula that aren't
# installed # installed
def missing_dependencies(hide: nil) def missing_dependencies(hide: nil)

View File

@ -2,6 +2,8 @@ require "keg"
require "formula" require "formula"
class LinkageChecker class LinkageChecker
attr_reader :undeclared_deps
def initialize(keg, formula = nil) def initialize(keg, formula = nil)
@keg = keg @keg = keg
@formula = formula || resolve_formula(keg) @formula = formula || resolve_formula(keg)
@ -64,7 +66,7 @@ class LinkageChecker
checked_dylibs = Set.new checked_dylibs = Set.new
@keg.find do |file| @keg.find do |file|
next if file.symlink? || file.directory? 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 # weakly loaded dylibs may not actually exist on disk, so skip them
# when checking for broken linkage # when checking for broken linkage
@ -108,10 +110,17 @@ class LinkageChecker
next false unless dep.optional? || dep.recommended? next false unless dep.optional? || dep.recommended?
formula.build.without?(dep) formula.build.without?(dep)
end end
declared_deps = formula.deps.reject { |dep| filter_out.call(dep) }.map(&:name) 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 } 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 = [] indirect_deps = []
undeclared_deps = [] undeclared_deps = []
@brewed_dylibs.each_key do |full_name| @brewed_dylibs.each_key do |full_name|
@ -123,15 +132,19 @@ class LinkageChecker
undeclared_deps << full_name undeclared_deps << full_name
end end
end end
sort_by_formula_full_name!(indirect_deps) sort_by_formula_full_name!(indirect_deps)
sort_by_formula_full_name!(undeclared_deps) sort_by_formula_full_name!(undeclared_deps)
unnecessary_deps = declared_dep_names.reject do |full_name| unnecessary_deps = declared_dep_names.reject do |full_name|
name = full_name.split("/").last name = full_name.split("/").last
next true if Formula[name].bin.directory? next true if Formula[name].bin.directory?
@brewed_dylibs.keys.map { |x| x.split("/").last }.include?(name) @brewed_dylibs.keys.map { |x| x.split("/").last }.include?(name)
end end
missing_deps = @broken_deps.values.flatten.map { |d| dylib_to_dep(d) } missing_deps = @broken_deps.values.flatten.map { |d| dylib_to_dep(d) }
unnecessary_deps -= missing_deps unnecessary_deps -= missing_deps
[indirect_deps, undeclared_deps, unnecessary_deps] [indirect_deps, undeclared_deps, unnecessary_deps]
end end

View File

@ -685,26 +685,42 @@ describe Formula do
expect(f5.runtime_dependencies.map(&:name)).to eq(["f1", "f4"]) expect(f5.runtime_dependencies.map(&:name)).to eq(["f1", "f4"])
end end
specify "runtime dependencies with optional deps from tap" do describe "#runtime_dependencies" do
tap_loader = double 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(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) 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") stub_formula_loader(formula("f2") { url("f2-1.0") }, "baz/qux/f2")
f3 = formula "f3" do f3 = formula "f3" do
url "f3-1.0" url "f3-1.0"
depends_on "foo/bar/f1" => :optional depends_on "foo/bar/f1" => :optional
depends_on "baz/qux/f2" 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 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") formula.brew { formula.install }
f3.build = BuildOptions.new(Options.create(["--with-f1"]), f3.options) 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 end
specify "requirements" do specify "requirements" do