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

Class Method Details

.activate_vcvars_quietlyObject

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_pathObject



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.expand_path(exe)
  return version_cache[key] if version_cache.key?(key)

  version_cache[key] = capture_version(exe)
end

.resolveObject

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.expand_path(exe)
end

.version_cacheObject

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