-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
15aeaae
commit 3304b11
Showing
2 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#: * `linkage` [`--test`] [`--reverse`] <formula>: | ||
#: Checks the library links of an installed formula. | ||
#: | ||
#: Only works on installed formulae. An error is raised if it is run on | ||
#: uninstalled formulae. | ||
#: | ||
#: If `--test` is passed, only display missing libraries and exit with a | ||
#: non-zero exit code if any missing libraries were found. | ||
#: | ||
#: If `--reverse` is passed, print the dylib followed by the binaries | ||
#: which link to it for each library the keg references. | ||
|
||
require "os/mac/linkage_checker" | ||
|
||
module Homebrew | ||
module_function | ||
|
||
def linkage | ||
ARGV.kegs.each do |keg| | ||
ohai "Checking #{keg.name} linkage" if ARGV.kegs.size > 1 | ||
result = LinkageChecker.new(keg) | ||
if ARGV.include?("--test") | ||
result.display_test_output | ||
Homebrew.failed = true if result.broken_dylibs? | ||
elsif ARGV.include?("--reverse") | ||
result.display_reverse_output | ||
else | ||
result.display_normal_output | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
require "set" | ||
require "keg" | ||
require "formula" | ||
|
||
class LinkageChecker | ||
attr_reader :keg, :formula | ||
attr_reader :brewed_dylibs, :system_dylibs, :broken_dylibs, :variable_dylibs | ||
attr_reader :undeclared_deps, :reverse_links | ||
|
||
def initialize(keg, formula = nil) | ||
@keg = keg | ||
@formula = formula || resolve_formula(keg) | ||
@brewed_dylibs = Hash.new { |h, k| h[k] = Set.new } | ||
@system_dylibs = Set.new | ||
@broken_dylibs = Set.new | ||
@variable_dylibs = Set.new | ||
@undeclared_deps = [] | ||
@reverse_links = Hash.new { |h, k| h[k] = Set.new } | ||
check_dylibs | ||
end | ||
|
||
def check_dylibs | ||
@keg.find do |file| | ||
next if file.symlink? || file.directory? | ||
next unless file.dylib? || file.mach_o_executable? || file.mach_o_bundle? | ||
|
||
# weakly loaded dylibs may not actually exist on disk, so skip them | ||
# when checking for broken linkage | ||
file.dynamically_linked_libraries.each do |dylib| | ||
@reverse_links[dylib] << file | ||
if dylib.start_with? "@" | ||
@variable_dylibs << dylib | ||
else | ||
begin | ||
owner = Keg.for Pathname.new(dylib) | ||
rescue NotAKegError | ||
@system_dylibs << dylib | ||
rescue Errno::ENOENT | ||
@broken_dylibs << dylib | ||
else | ||
tap = Tab.for_keg(owner).tap | ||
f = if tap.nil? || tap.core_tap? | ||
owner.name | ||
else | ||
"#{tap}/#{owner.name}" | ||
end | ||
@brewed_dylibs[f] << dylib | ||
end | ||
end | ||
end | ||
end | ||
|
||
@undeclared_deps = check_undeclared_deps if formula | ||
end | ||
|
||
def check_undeclared_deps | ||
filter_out = proc do |dep| | ||
next true if dep.build? | ||
next false unless dep.optional? || dep.recommended? | ||
formula.build.without?(dep) | ||
end | ||
declared_deps = formula.deps.reject { |dep| filter_out.call(dep) }.map(&:name) | ||
declared_requirement_deps = formula.requirements.reject { |req| filter_out.call(req) }.map(&:default_formula).compact | ||
declared_dep_names = (declared_deps + declared_requirement_deps).map { |dep| dep.split("/").last } | ||
undeclared_deps = @brewed_dylibs.keys.select do |full_name| | ||
name = full_name.split("/").last | ||
next false if name == formula.name | ||
!declared_dep_names.include?(name) | ||
end | ||
undeclared_deps.sort do |a, b| | ||
if a.include?("/") && !b.include?("/") | ||
1 | ||
elsif !a.include?("/") && b.include?("/") | ||
-1 | ||
else | ||
a <=> b | ||
end | ||
end | ||
end | ||
|
||
def display_normal_output | ||
display_items "System libraries", @system_dylibs | ||
display_items "Homebrew libraries", @brewed_dylibs | ||
display_items "Variable-referenced libraries", @variable_dylibs | ||
display_items "Missing libraries", @broken_dylibs | ||
display_items "Possible undeclared dependencies", @undeclared_deps | ||
end | ||
|
||
def display_reverse_output | ||
return if @reverse_links.empty? | ||
sorted = @reverse_links.sort | ||
sorted.each do |dylib, files| | ||
puts dylib | ||
files.each do |f| | ||
unprefixed = f.to_s.strip_prefix "#{@keg}/" | ||
puts " #{unprefixed}" | ||
end | ||
puts unless dylib == sorted.last[0] | ||
end | ||
end | ||
|
||
def display_test_output | ||
display_items "Missing libraries", @broken_dylibs | ||
puts "No broken dylib links" if @broken_dylibs.empty? | ||
end | ||
|
||
def broken_dylibs? | ||
!@broken_dylibs.empty? | ||
end | ||
|
||
def undeclared_deps? | ||
!@undeclared_deps.empty? | ||
end | ||
|
||
private | ||
|
||
# Display a list of things. | ||
# Things may either be an array, or a hash of (label -> array) | ||
def display_items(label, things) | ||
return if things.empty? | ||
puts "#{label}:" | ||
if things.is_a? Hash | ||
things.sort.each do |list_label, list| | ||
list.sort.each do |item| | ||
puts " #{item} (#{list_label})" | ||
end | ||
end | ||
else | ||
things.sort.each do |item| | ||
puts " #{item}" | ||
end | ||
end | ||
end | ||
|
||
def resolve_formula(keg) | ||
f = Formulary.from_rack(keg.rack) | ||
f.build = Tab.for_keg(keg) | ||
f | ||
rescue FormulaUnavailableError | ||
opoo "Formula unavailable: #{keg.name}" | ||
end | ||
end |