diff options
author | Homu <homu@barosl.com> | 2016-08-04 14:53:12 +0900 |
---|---|---|
committer | Homu <homu@barosl.com> | 2016-08-04 14:53:12 +0900 |
commit | e511f9f4bcbcfc23e363a67c83b66386543cc02c (patch) | |
tree | f7076f3be3afd4e92fa2fd3ef8a0f092513620f4 /lib/bundler/cli | |
parent | dda6432b71043b8065080f2fb83358614c4d04e8 (diff) | |
parent | 6117de98bb696a68cc15963b40d9157925e175a2 (diff) | |
download | bundler-e511f9f4bcbcfc23e363a67c83b66386543cc02c.tar.gz |
Auto merge of #4765 - mistydemeo:linkage, r=indirect
WIP: add command to check dynamic library linkage
This new command, linkage, checks for broken dynamic library links in C extensions.
I'd heard there was some interest in adding this functionality, so I thought I'd submit a preliminary PR for discussion. In my experience, broken dylib linkage is a common issue with C extensions, so I think having a good way to diagnose it would be valuable.
This command scans all of the specifications in the bundle for .bundle files in C extensions. If any of the dynamic libraries linked against no longer exist, bundler will report a message to the console and exit non-0.
TODOs:
* Add support for non-Darwin OSs
* Improve tests
A few questions:
* Is there a good way to mock functionality in the tests? Doing it in the standard rspec way isn't working since `bundle :command` runs in a subprocess. I'd like to be able to stub out stuff that actually checks dylibs and the like.
* Is making this a new command the right approach? I assumed this wouldn't be ideal to include in, say, `check` because it would slow it down.
Diffstat (limited to 'lib/bundler/cli')
-rw-r--r-- | lib/bundler/cli/doctor.rb | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb new file mode 100644 index 00000000..8fd862a1 --- /dev/null +++ b/lib/bundler/cli/doctor.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Bundler + class CLI::Doctor + DARWIN_REGEX = /\s+(.+) \(compatibility / + LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/ + + attr_reader :options + + def initialize(options) + @options = options + end + + def otool_available? + system("otool --version 2>&1 >#{Bundler::NULL}") + end + + def ldd_available? + !system("ldd --help 2>&1 >#{Bundler::NULL}").nil? + end + + def dylibs_darwin(path) + output = `/usr/bin/otool -L "#{path}"`.chomp + dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq + # ignore @rpath and friends + dylibs.reject {|dylib| dylib.start_with? "@" } + end + + def dylibs_ldd(path) + output = `/usr/bin/ldd "#{path}"`.chomp + output.split("\n").map do |l| + match = l.match(LDD_REGEX) + next if match.nil? + match.captures[0] + end.compact + end + + def dylibs(path) + case RbConfig::CONFIG["host_os"] + when /darwin/ + return [] unless otool_available? + dylibs_darwin(path) + when /(linux|solaris|bsd)/ + return [] unless ldd_available? + dylibs_ldd(path) + else # Windows, etc. + Bundler.ui.warn("Dynamic library check not supported on this platform.") + [] + end + end + + def bundles_for_gem(spec) + Dir.glob("#{spec.full_gem_path}/**/*.bundle") + end + + def run + Bundler.ui.level = "error" if options[:quiet] + + broken_links = {} + + begin + definition = Bundler.definition + definition.validate_ruby! + not_installed = definition.missing_specs + raise GemNotFound if not_installed.any? + rescue GemNotFound + Bundler.ui.warn "This bundle's gems must be installed to run this command." + Bundler.ui.warn "Install missing gems with `bundle install`." + exit 0 + end + + definition.specs.each do |spec| + bundles_for_gem(spec).each do |bundle| + bad_paths = dylibs(bundle).select {|f| !File.exist?(f) } + if bad_paths.any? + broken_links[spec] ||= [] + broken_links[spec].concat(bad_paths) + end + end + end + + if broken_links.any? + Bundler.ui.error "The following gems are missing OS dependencies" + broken_links.each do |spec, paths| + paths.uniq.each do |path| + Bundler.ui.error " * #{spec.name}: #{path}" + end + end + exit 1 + end + end + end +end |