Module: Vcdeps::ToolFinder
- Defined in:
- lib/vcdeps/tool.rb
Overview
Locates a usable vcpkg instance (or bootstraps a private one). The resolution order and validation are the load-bearing integration logic (R§2.5): every candidate must have a real vcpkg.exe AND a ".vcpkg-root" marker before it is trusted, and a user-set VCPKG_INSTALLATION_ROOT that lies (runner-images #9269) is skipped rather than believed.
Constant Summary collapse
- BOOTSTRAP_URL =
The upstream standalone vcpkg.exe (the same artifact vcpkg-init downloads).
"https://github.com/microsoft/vcpkg-tool/releases/latest/download/vcpkg.exe"
Class Method Summary collapse
-
.activate_vcvars_quietly ⇒ Object
Vcvars.activate! is idempotent and mswin-only; any failure here is not fatal to resolution (steps 3-5 may still find a tool), so swallow it.
-
.build_tool(exe, source) ⇒ Object
Build a Tool for a resolved exe, reading (and memoizing) its version.
- .capture_version(exe) ⇒ Object
-
.find(bootstrap: false, out: $stderr) ⇒ Object
Resolve a Tool, or nil.
- .locate_vs_path ⇒ Object
-
.read_version(exe) ⇒ Object
Parse the date stamp from
vcpkg version's first line: "vcpkg package management program version 2026-02-21-..." -> "2026-02-21". -
.resolve ⇒ Object
The five-step resolution (no bootstrap).
-
.valid_root(root) ⇒ Object
A directory is a valid vcpkg root iff it holds vcpkg.exe AND a ".vcpkg-root" marker (R§2.2/R§2.3).
-
.version_cache ⇒ Object
Memoized version per resolved exe path: avoids re-spawning
vcpkg version. -
.win(path) ⇒ Object
Display-normalize to backslashes, like Vcvars::Locator.win.
Class Method Details
.activate_vcvars_quietly ⇒ Object
Vcvars.activate! is idempotent and mswin-only; any failure here is not fatal to resolution (steps 3-5 may still find a tool), so swallow it.
131 132 133 134 135 136 137 |
# File 'lib/vcdeps/tool.rb', line 131 def activate_vcvars_quietly return unless Vcvars.mswin? Vcvars.activate! rescue StandardError nil end |
.build_tool(exe, source) ⇒ Object
Build a Tool for a resolved exe, reading (and memoizing) its version.
96 97 98 99 |
# File 'lib/vcdeps/tool.rb', line 96 def build_tool(exe, source) Tool.new(exe: win(exe), root: win(File.dirname(exe)), version: read_version(exe), source: source) end |
.capture_version(exe) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/vcdeps/tool.rb', line 112 def capture_version(exe) out, status = Open3.capture2(exe, "version") return nil unless status&.success? line = out.to_s.lines.first.to_s # Grab a leading date "YYYY-MM-DD" from the version token if present. if (m = line.match(/version\s+(\d{4}-\d{2}-\d{2})/)) m[1] elsif (m = line.match(/(\d{4}-\d{2}-\d{2})/)) m[1] else line.strip.empty? ? nil : line.strip end rescue StandardError nil end |
.find(bootstrap: false, out: $stderr) ⇒ Object
Resolve a Tool, or nil. With bootstrap: true a miss bootstraps a private instance instead of returning nil. Resolution order is §2.3 steps 1-5.
52 53 54 55 56 57 58 |
# File 'lib/vcdeps/tool.rb', line 52 def find(bootstrap: false, out: $stderr) tool = resolve return tool if tool return nil unless bootstrap Vcdeps.bootstrap!(out: out) end |
.locate_vs_path ⇒ Object
139 140 141 142 143 144 |
# File 'lib/vcdeps/tool.rb', line 139 def locate_vs_path inst = Vcvars.locate inst&.vs_path rescue StandardError nil end |
.read_version(exe) ⇒ Object
Parse the date stamp from vcpkg version's first line:
"vcpkg package management program version 2026-02-21-<sha>..."
-> "2026-02-21". Falls back to the whole token if the shape differs. Memoized per exe path.
105 106 107 108 109 110 |
# File 'lib/vcdeps/tool.rb', line 105 def read_version(exe) key = File.(exe) return version_cache[key] if version_cache.key?(key) version_cache[key] = capture_version(exe) end |
.resolve ⇒ Object
The five-step resolution (no bootstrap). Returns a Tool or nil.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/vcdeps/tool.rb', line 61 def resolve # 1. Explicit VCPKG_ROOT — explicit beats implicit. if (exe = valid_root(ENV["VCPKG_ROOT"])) return build_tool(exe, :env) end # 2. Activate vcvars (idempotent, mswin only, errors swallowed) and read # the VCPKG_ROOT the VS developer env sets. After activation the VS # bundle's VCPKG_ROOT is usually already present (R§2.1). activate_vcvars_quietly if (exe = valid_root(ENV["VCPKG_ROOT"])) return build_tool(exe, :devenv) end # 3. Vcvars.locate.vs_path + "\VC\vcpkg" probed directly (covers a located # VS even if the activation env did not export the vcpkg extension). if (vs = locate_vs_path) && (exe = valid_root(File.join(vs, "VC", "vcpkg"))) return build_tool(exe, :vs) end # 4. VCPKG_INSTALLATION_ROOT (GitHub Actions) — VALIDATED; a stale/wrong # value (issue #9269) is skipped, not trusted. if (exe = valid_root(ENV["VCPKG_INSTALLATION_ROOT"])) return build_tool(exe, :actions) end # 5. A previously bootstrapped private instance under <home>\vcpkg. if (exe = valid_root(File.join(Vcdeps.home, "vcpkg"))) return build_tool(exe, :private) end nil end |
.valid_root(root) ⇒ Object
A directory is a valid vcpkg root iff it holds vcpkg.exe AND a ".vcpkg-root" marker (R§2.2/R§2.3). Returns the absolute exe path or nil.
40 41 42 43 44 45 46 47 48 |
# File 'lib/vcdeps/tool.rb', line 40 def valid_root(root) return nil if root.nil? || root.to_s.empty? exe = File.join(root, "vcpkg.exe") marker = File.join(root, ".vcpkg-root") return nil unless File.file?(exe) && File.exist?(marker) File.(exe) end |
.version_cache ⇒ Object
Memoized version per resolved exe path: avoids re-spawning vcpkg version.
29 30 31 |
# File 'lib/vcdeps/tool.rb', line 29 def version_cache @version_cache ||= {} end |
.win(path) ⇒ Object
Display-normalize to backslashes, like Vcvars::Locator.win.
34 35 36 |
# File 'lib/vcdeps/tool.rb', line 34 def win(path) path.nil? ? path : path.tr("/", "\\") end |