From c1f6dd96f899b97ae1a3972cef7e453776df1dd6 Mon Sep 17 00:00:00 2001 From: Misty De Meo Date: Tue, 5 Jul 2016 15:15:28 +1000 Subject: Add command to diagnose common issues This new command, doctor, checks for common problems. Currently, it looks for broken dynamic library links in C extensions. It 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. --- lib/bundler/cli/doctor.rb | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 lib/bundler/cli/doctor.rb (limited to 'lib/bundler/cli') diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb new file mode 100644 index 00000000..370e46ac --- /dev/null +++ b/lib/bundler/cli/doctor.rb @@ -0,0 +1,85 @@ +# 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 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/ + dylibs_darwin(path) + when /(linux|solaris|bsd)/ + 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.error "This bundle's gems must be installed to run this command." + Bundler.ui.warn "Install missing gems with `bundle install`." + exit 2 + 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 -- cgit v1.2.3 From 7b6dff318f84c61f633c72c65f2d8e73b9ad9364 Mon Sep 17 00:00:00 2001 From: Misty De Meo Date: Mon, 1 Aug 2016 17:09:33 +1000 Subject: doctor: return stub result if no dylib tool installed This will eventually support other tests, so the dylib test needs to be able to to meaningfully return an empty result --- lib/bundler/cli/doctor.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib/bundler/cli') diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index 370e46ac..df49315b 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -13,6 +13,14 @@ module Bundler @options = options end + def otool_available? + system("otool --version 2>&1 >/dev/null") + end + + def ldd_available? + !system("ldd --help 2>&1 >/dev/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 @@ -32,8 +40,10 @@ module Bundler 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.") -- cgit v1.2.3 From 5d236a989f2f582800a754114224465784d662a8 Mon Sep 17 00:00:00 2001 From: Misty De Meo Date: Tue, 2 Aug 2016 11:20:37 +1000 Subject: doctor: gems not being installed isn't fatal --- lib/bundler/cli.rb | 3 --- lib/bundler/cli/doctor.rb | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'lib/bundler/cli') diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 5e568995..a20c76b3 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -479,9 +479,6 @@ module Bundler Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If missing dependencies are detected, Bundler prints them and exits status 1. Otherwise, Bundler prints a success message and exits with a status of 0. - - The bundle's gem dependencies must all be installed to run this command; if - they are not, Bundler prints an error message and exits with a status of 2. D method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index df49315b..b8aac1a3 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -66,9 +66,9 @@ module Bundler not_installed = definition.missing_specs raise GemNotFound if not_installed.any? rescue GemNotFound - Bundler.ui.error "This bundle's gems must be installed to run this command." + Bundler.ui.warn "This bundle's gems must be installed to run this command." Bundler.ui.warn "Install missing gems with `bundle install`." - exit 2 + exit 0 end definition.specs.each do |spec| -- cgit v1.2.3 From 6117de98bb696a68cc15963b40d9157925e175a2 Mon Sep 17 00:00:00 2001 From: Misty De Meo Date: Tue, 2 Aug 2016 12:39:00 +1000 Subject: doctor: use Bundler::NULL --- lib/bundler/cli/doctor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/bundler/cli') diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index b8aac1a3..8fd862a1 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -14,11 +14,11 @@ module Bundler end def otool_available? - system("otool --version 2>&1 >/dev/null") + system("otool --version 2>&1 >#{Bundler::NULL}") end def ldd_available? - !system("ldd --help 2>&1 >/dev/null").nil? + !system("ldd --help 2>&1 >#{Bundler::NULL}").nil? end def dylibs_darwin(path) -- cgit v1.2.3