Module: Vcdeps::Doctor
- Defined in:
- lib/vcdeps/doctor.rb
Overview
Diagnoses vcpkg acquisition + mkmf wiring, mirroring Vcvars::Doctor's Check/icon/Summary pattern. Delegates the toolchain checks to vcvars (a hard dependency whose Doctor is public API) and adds the vcdeps-specific checks (§2.9): resolution, manifest baseline, opt-dir shadowing, Ruby-bin DLL collisions, triplet sanity, binary cache, offline readiness, home hygiene.
Defined Under Namespace
Classes: Check
Class Method Summary collapse
-
.actions_root_checks ⇒ Object
-
.cache_checks ⇒ Object
-
.collision_checks(manifest) ⇒ Object
- .healthy?(checks) ⇒ Boolean
-
.home_checks(deep:) ⇒ Object
- .home_size_mb(dir) ⇒ Object
-
.manifest_checks(manifest) ⇒ Object
-
.manifest_dll_basenames(manifest) ⇒ Object
Best-effort: the DLL base names a manifest would vendor, read from an already-installed tree if present (so doctor stays offline and cheap).
-
.offline_checks ⇒ Object
-
.optdir_checks ⇒ Object
-
.run(manifest: nil, deep: true) ⇒ Object
Returns an Array
. -
.safe_locate ⇒ Object
--- internals -----------------------------------------------------------.
- .safe_tool ⇒ Object
-
.tool_checks ⇒ Object
-
.toolchain_checks ⇒ Object
-
.triplet_checks ⇒ Object
Class Method Details
.actions_root_checks ⇒ Object
- VCPKG_INSTALLATION_ROOT set but invalid (issue #9269).
89 90 91 92 93 94 95 96 97 98 |
# File 'lib/vcdeps/doctor.rb', line 89 def actions_root_checks root = ENV["VCPKG_INSTALLATION_ROOT"] return [] if root.nil? || root.empty? return [] if ToolFinder.valid_root(root) [Check.new(status: :warn, label: "VCPKG_INSTALLATION_ROOT is set but invalid", detail: "#{root.tr('/', '\\')} lacks vcpkg.exe/.vcpkg-root (the " \ "runner-images #9269 bug). vcdeps SKIPS it; nothing to fix " \ "unless you rely on it.")] end |
.cache_checks ⇒ Object
- Binary cache: %LOCALAPPDATA%\vcpkg\archives (or override) + entry count.
167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/vcdeps/doctor.rb', line 167 def cache_checks cache = ENV["VCPKG_DEFAULT_BINARY_CACHE"] cache = File.join(ENV["LOCALAPPDATA"] || "", "vcpkg", "archives") if cache.nil? || cache.empty? if File.directory?(cache) count = Dir[File.join(cache.tr("\\", "/"), "**", "*.zip")].size [Check.new(status: :info, label: "Binary cache present (#{count} archive(s))", detail: cache.tr("/", "\\"))] else [Check.new(status: :info, label: "Binary cache not yet created", detail: "#{cache.tr('/', '\\')} will hold built ports; first cold " \ "build populates it.")] end end |
.collision_checks(manifest) ⇒ Object
- Ruby-bin DLL collisions: scan the install's/vendored DLL base names against Ruby's bindir.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/vcdeps/doctor.rb', line 133 def collision_checks(manifest) return [] if manifest.nil? bindir = RbConfig::CONFIG["bindir"].to_s return [] if bindir.empty? names = manifest_dll_basenames(manifest) collisions = names.select { |n| File.exist?(File.join(bindir, n)) } return [] if collisions.empty? collisions.map do |n| Check.new(status: :warn, label: "DLL name collides with Ruby's bin: #{n}", detail: "#{File.join(bindir, n).tr('/', '\\')} exists; first-loaded " \ "wins process-wide (R§9.2). The preload shim runs first " \ "under normal require order.") end end |
.healthy?(checks) ⇒ Boolean
40 41 42 |
# File 'lib/vcdeps/doctor.rb', line 40 def healthy?(checks) checks.none? { |c| c.status == :fail } end |
.home_checks(deep:) ⇒ Object
- Vcdeps.home hygiene: spaces / non-ASCII WARN; size INFO on deep.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/vcdeps/doctor.rb', line 195 def home_checks(deep:) out = [] h = Vcdeps.home if h =~ /\s/ out << Check.new(status: :warn, label: "VCDEPS_HOME path contains spaces", detail: "#{h} — naked -I flags and some port builds mishandle " \ "spaces. Set VCDEPS_HOME to a short ASCII path.") elsif h =~ /[^\x00-\x7F]/ out << Check.new(status: :warn, label: "VCDEPS_HOME path has non-ASCII characters", detail: "#{h} — some port build scripts mishandle it. Set " \ "VCDEPS_HOME to a short ASCII path.") else out << Check.new(status: :ok, label: "vcdeps home path is clean", detail: h) end if deep && File.directory?(h) out << Check.new(status: :info, label: "vcdeps home size: #{home_size_mb(h)} MB", detail: h) end out end |
.home_size_mb(dir) ⇒ Object
253 254 255 256 257 258 259 260 261 |
# File 'lib/vcdeps/doctor.rb', line 253 def home_size_mb(dir) total = 0 Dir.glob(File.join(dir.tr("\\", "/"), "**", "*"), File::FNM_DOTMATCH) do |f| total += File.size(f) if File.file?(f) end (total / (1024.0 * 1024.0)).round(1) rescue StandardError 0 end |
.manifest_checks(manifest) ⇒ Object
- Manifest (when given): parses; builtin-baseline present or vcpkg-configuration.json beside it — else FAIL with the baseline remedy.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/vcdeps/doctor.rb', line 102 def manifest_checks(manifest) return [] if manifest.nil? dir = File.(manifest) path = File.join(dir, Manifest::MANIFEST_NAME) return [Check.new(status: :fail, label: "No manifest at #{path.tr('/', '\\')}", detail: "Create a vcpkg.json there.")] unless File.exist?(path) begin Manifest.load!(dir) [Check.new(status: :ok, label: "Manifest is valid and baselined", detail: path.tr("/", "\\"))] rescue ManifestError => e [Check.new(status: :fail, label: "Manifest problem", detail: e.)] end end |
.manifest_dll_basenames(manifest) ⇒ Object
Best-effort: the DLL base names a manifest would vendor, read from an already-installed tree if present (so doctor stays offline and cheap).
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/vcdeps/doctor.rb', line 233 def manifest_dll_basenames(manifest) dir = File.(manifest) tool = safe_tool return [] unless tool triplet = ENV["VCDEPS_TRIPLET"] triplet = (Triplet.default rescue nil) if triplet.nil? || triplet.empty? return [] unless triplet key = Manifest.key(dir, triplet, tool.version) root = File.join(Vcdeps.home, "installed", key) return [] unless File.directory?(root) inst = Installed.new(root: root, triplet: triplet, manifest_dir: dir, key: key, tool: tool) inst.dlls.map { |d| File.basename(d) } rescue StandardError [] end |
.offline_checks ⇒ Object
- Registry/offline readiness: baseline tree under registries.
182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/vcdeps/doctor.rb', line 182 def offline_checks reg = File.join(ENV["LOCALAPPDATA"] || "", "vcpkg", "registries") if File.directory?(reg) && !Dir.empty?(reg.tr("\\", "/")) [Check.new(status: :info, label: "Registry cache present (offline-capable)", detail: reg.tr("/", "\\"))] else [Check.new(status: :info, label: "Registry cache empty", detail: "First install of a baseline needs network for the registry " \ "git-tree fetch.")] end end |
.optdir_checks ⇒ Object
- opt-dir shadowing: parse configure_args for --with-opt-dir.
120 121 122 123 124 125 126 127 128 129 |
# File 'lib/vcdeps/doctor.rb', line 120 def optdir_checks args = RbConfig::CONFIG["configure_args"].to_s m = args.match(/--with-opt-dir=(\S+)/) return [] unless m [Check.new(status: :warn, label: "Ruby was built with --with-opt-dir", detail: "#{m[1]} is folded into EVERY extension's paths AHEAD of " \ "appended dirs (opt-dir shadowing, R§8.4). vcdeps PREPENDS " \ "its -I / -libpath so its vcpkg tree wins; no action needed.")] end |
.run(manifest: nil, deep: true) ⇒ Object
Returns an Arraymanifest (a dir) enables the manifest/triplet
checks; deep: true adds the home-size INFO.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/vcdeps/doctor.rb', line 25 def run(manifest: nil, deep: true) checks = [] checks.concat(toolchain_checks) checks.concat(tool_checks) checks.concat(actions_root_checks) checks.concat(manifest_checks(manifest)) checks.concat(optdir_checks) checks.concat(collision_checks(manifest)) checks.concat(triplet_checks) checks.concat(cache_checks) checks.concat(offline_checks) checks.concat(home_checks(deep: deep)) checks end |
.safe_locate ⇒ Object
--- internals -----------------------------------------------------------
219 220 221 222 223 |
# File 'lib/vcdeps/doctor.rb', line 219 def safe_locate Vcvars.locate rescue StandardError nil end |
.safe_tool ⇒ Object
225 226 227 228 229 |
# File 'lib/vcdeps/doctor.rb', line 225 def safe_tool Vcdeps.tool rescue StandardError nil end |
.tool_checks ⇒ Object
- vcpkg resolution: source/path/version, or FAIL + the three remedies.
74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/vcdeps/doctor.rb', line 74 def tool_checks tool = safe_tool if tool [Check.new(status: :ok, label: "vcpkg resolved (#{tool.source})", detail: "#{tool.exe}\n version #{tool.version}")] else [Check.new(status: :fail, label: "No usable vcpkg found", detail: "Remedies: (a) install the VS component " \ "Microsoft.VisualStudio.Component.Vcpkg; (b) set " \ "VCPKG_ROOT to an existing instance; (c) run " \ "`vcdeps bootstrap` (or VCDEPS_BOOTSTRAP=1).")] end end |
.toolchain_checks ⇒ Object
- mswin Ruby + CRT linkage (delegates to vcvars). 2. vcvars viability.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/vcdeps/doctor.rb', line 45 def toolchain_checks out = [] out << if Vcvars::Doctor.mswin? Check.new(status: :ok, label: "Ruby is a native MSVC (mswin) build", detail: "#{RUBY_VERSION} #{RbConfig::CONFIG['arch']}, CRT " \ "#{Vcvars::Doctor.crt_flag}") else Check.new(status: :fail, label: "Ruby is NOT an mswin build", detail: "vcdeps targets MSVC (mswin) Ruby only. Your Ruby is " \ "#{RbConfig::CONFIG['arch']}. Not supported on MinGW/UCRT.") end inst = safe_locate out << if inst Check.new(status: :ok, label: "Visual Studio located", detail: inst.to_s) else Check.new(status: :warn, label: "No Visual Studio located via vcvars", detail: "vcdeps needs cl.exe to build extensions and to find the " \ "VS-bundled vcpkg. Install \"Desktop development with " \ "C++\"; run `vcvars doctor`.") end out << Check.new(status: (Vcvars.active? ? :ok : :info), label: "Developer environment #{Vcvars.active? ? 'ACTIVE' : 'not yet active'}", detail: Vcvars.active? ? nil : "vcdeps activates it on demand " \ "(Vcvars.activate!); harmless if inactive now.") out end |
.triplet_checks ⇒ Object
- Triplet sanity: resolved triplet vs §2.6 rules.
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/vcdeps/doctor.rb', line 152 def triplet_checks triplet = ENV["VCDEPS_TRIPLET"] triplet = (Triplet.default rescue nil) if triplet.nil? || triplet.empty? return [Check.new(status: :warn, label: "Cannot derive a triplet for this arch", detail: "Set VCDEPS_TRIPLET.")] if triplet.nil? begin Triplet.validate!(triplet) [Check.new(status: :ok, label: "Triplet OK: #{triplet}", detail: nil)] rescue TripletError => e [Check.new(status: :fail, label: "Triplet rejected", detail: e.)] end end |