|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | | -require "rbconfig" |
4 | | -require "shellwords" |
5 | | - |
6 | 3 | module Bundler |
7 | | - class CLI::Doctor |
8 | | - DARWIN_REGEX = /\s+(.+) \(compatibility / |
9 | | - LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/ |
10 | | - |
11 | | - attr_reader :options |
12 | | - |
13 | | - def initialize(options) |
14 | | - @options = options |
15 | | - end |
16 | | - |
17 | | - def otool_available? |
18 | | - Bundler.which("otool") |
19 | | - end |
20 | | - |
21 | | - def ldd_available? |
22 | | - Bundler.which("ldd") |
23 | | - end |
24 | | - |
25 | | - def dylibs_darwin(path) |
26 | | - output = `/usr/bin/otool -L #{path.shellescape}`.chomp |
27 | | - dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq |
28 | | - # ignore @rpath and friends |
29 | | - dylibs.reject {|dylib| dylib.start_with? "@" } |
30 | | - end |
31 | | - |
32 | | - def dylibs_ldd(path) |
33 | | - output = `/usr/bin/ldd #{path.shellescape}`.chomp |
34 | | - output.split("\n").filter_map do |l| |
35 | | - match = l.match(LDD_REGEX) |
36 | | - next if match.nil? |
37 | | - match.captures[0] |
38 | | - end |
39 | | - end |
40 | | - |
41 | | - def dylibs(path) |
42 | | - case RbConfig::CONFIG["host_os"] |
43 | | - when /darwin/ |
44 | | - return [] unless otool_available? |
45 | | - dylibs_darwin(path) |
46 | | - when /(linux|solaris|bsd)/ |
47 | | - return [] unless ldd_available? |
48 | | - dylibs_ldd(path) |
49 | | - else # Windows, etc. |
50 | | - Bundler.ui.warn("Dynamic library check not supported on this platform.") |
51 | | - [] |
52 | | - end |
53 | | - end |
54 | | - |
55 | | - def bundles_for_gem(spec) |
56 | | - Dir.glob("#{spec.full_gem_path}/**/*.bundle") |
57 | | - end |
58 | | - |
59 | | - def lookup_with_fiddle(path) |
60 | | - require "fiddle" |
61 | | - Fiddle.dlopen(path) |
62 | | - false |
63 | | - rescue Fiddle::DLError |
64 | | - true |
65 | | - end |
66 | | - |
67 | | - def check! |
68 | | - require_relative "check" |
69 | | - Bundler::CLI::Check.new({}).run |
70 | | - end |
71 | | - |
72 | | - def run |
73 | | - Bundler.ui.level = "warn" if options[:quiet] |
74 | | - Bundler.settings.validate! |
75 | | - check! |
76 | | - |
77 | | - definition = Bundler.definition |
78 | | - broken_links = {} |
79 | | - |
80 | | - definition.specs.each do |spec| |
81 | | - bundles_for_gem(spec).each do |bundle| |
82 | | - bad_paths = dylibs(bundle).select do |f| |
83 | | - lookup_with_fiddle(f) |
84 | | - end |
85 | | - if bad_paths.any? |
86 | | - broken_links[spec] ||= [] |
87 | | - broken_links[spec].concat(bad_paths) |
88 | | - end |
89 | | - end |
90 | | - end |
91 | | - |
92 | | - permissions_valid = check_home_permissions |
93 | | - |
94 | | - if broken_links.any? |
95 | | - message = "The following gems are missing OS dependencies:" |
96 | | - broken_links.flat_map do |spec, paths| |
97 | | - paths.uniq.map do |path| |
98 | | - "\n * #{spec.name}: #{path}" |
99 | | - end |
100 | | - end.sort.each {|m| message += m } |
101 | | - raise ProductionError, message |
102 | | - elsif permissions_valid |
103 | | - Bundler.ui.info "No issues found with the installed bundle" |
104 | | - end |
105 | | - end |
106 | | - |
107 | | - private |
108 | | - |
109 | | - def check_home_permissions |
110 | | - require "find" |
111 | | - files_not_readable = [] |
112 | | - files_not_readable_and_owned_by_different_user = [] |
113 | | - files_not_owned_by_current_user_but_still_readable = [] |
114 | | - broken_symlinks = [] |
115 | | - Find.find(Bundler.bundle_path.to_s).each do |f| |
116 | | - if !File.exist?(f) |
117 | | - broken_symlinks << f |
118 | | - elsif !File.readable?(f) |
119 | | - if File.stat(f).uid != Process.uid |
120 | | - files_not_readable_and_owned_by_different_user << f |
121 | | - else |
122 | | - files_not_readable << f |
123 | | - end |
124 | | - elsif File.stat(f).uid != Process.uid |
125 | | - files_not_owned_by_current_user_but_still_readable << f |
126 | | - end |
127 | | - end |
128 | | - |
129 | | - ok = true |
130 | | - |
131 | | - if broken_symlinks.any? |
132 | | - Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}" |
133 | | - |
134 | | - ok = false |
135 | | - end |
136 | | - |
137 | | - if files_not_owned_by_current_user_but_still_readable.any? |
138 | | - Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ |
139 | | - "user, but are still readable. These files are:\n - #{files_not_owned_by_current_user_but_still_readable.join("\n - ")}" |
140 | | - |
141 | | - ok = false |
142 | | - end |
143 | | - |
144 | | - if files_not_readable_and_owned_by_different_user.any? |
145 | | - Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ |
146 | | - "user, and are not readable. These files are:\n - #{files_not_readable_and_owned_by_different_user.join("\n - ")}" |
147 | | - |
148 | | - ok = false |
149 | | - end |
150 | | - |
151 | | - if files_not_readable.any? |
152 | | - Bundler.ui.warn "Files exist in the Bundler home that are not " \ |
153 | | - "readable by the current user. These files are:\n - #{files_not_readable.join("\n - ")}" |
154 | | - |
155 | | - ok = false |
156 | | - end |
157 | | - |
158 | | - ok |
| 4 | + class CLI::Doctor < Thor |
| 5 | + default_command(:diagnose) |
| 6 | + |
| 7 | + desc "diagnose [OPTIONS]", "Checks the bundle for common problems" |
| 8 | + long_desc <<-D |
| 9 | + Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If |
| 10 | + missing dependencies are detected, Bundler prints them and exits status 1. |
| 11 | + Otherwise, Bundler prints a success message and exits with a status of 0. |
| 12 | + D |
| 13 | + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" |
| 14 | + method_option "quiet", type: :boolean, banner: "Only output warnings and errors." |
| 15 | + method_option "ssl", type: :boolean, default: false, banner: "Diagnose SSL problems." |
| 16 | + def diagnose |
| 17 | + require_relative "doctor/diagnose" |
| 18 | + Diagnose.new(options).run |
| 19 | + end |
| 20 | + |
| 21 | + desc "ssl [OPTIONS]", "Diagnose SSL problems" |
| 22 | + long_desc <<-D |
| 23 | + Diagnose SSL problems, especially related to certificates or TLS version while connecting to https://rubygems.org. |
| 24 | + D |
| 25 | + method_option "host", type: :string, banner: "The host to diagnose." |
| 26 | + method_option "tls-version", type: :string, banner: "Specify the SSL/TLS version when running the diagnostic. Accepts either <1.1> or <1.2>" |
| 27 | + method_option "verify-mode", type: :string, banner: "Specify the mode used for certification verification. Accepts either <peer> or <none>" |
| 28 | + def ssl |
| 29 | + require_relative "doctor/ssl" |
| 30 | + SSL.new(options).run |
159 | 31 | end |
160 | 32 | end |
161 | 33 | end |
0 commit comments