Module: Vcdeps::Runner

Defined in:
lib/vcdeps/runner.rb

Overview

The ONLY place a vcpkg argv is constructed and spawned. Every --x-* flag lives in args so an upstream rename is a one-line fix (R§3/§5.13), and the runner test pins the exact argv. Combined child output is streamed to out line by line (never silent during a multi-minute port build); on any abnormal unwind the child is TerminateProcess'd and reaped so vcpkg.exe never outlives the call (§3.2).

Constant Summary collapse

MARKER_NAME =
".vcdeps-complete"
LOG_TAIL_MAX =
4000

Class Method Summary collapse

Class Method Details

.child_env(tool_root) ⇒ Object

The child environment: telemetry OFF by default (a gem install must not phone home, §5.15), VCPKG_ROOT pinned to the tool's own root. All other vcpkg env (binary cache, downloads) passes through untouched (§5.18).



37
38
39
40
41
42
# File 'lib/vcdeps/runner.rb', line 37

def child_env(tool_root)
  env = {}
  env["VCPKG_DISABLE_METRICS"] = "1" unless ENV["VCDEPS_METRICS"] == "1"
  env["VCPKG_ROOT"] = tool_root if tool_root
  env
end

.install_args(exe, triplet, manifest_dir, install_root, home) ⇒ Object

Build the vcpkg install argv (§5.13 pin). All --x-* roots redirect out of the gem tree into (R§7 path-length trap).



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/vcdeps/runner.rb', line 22

def install_args(exe, triplet, manifest_dir, install_root, home)
  [
    exe,
    "install",
    "--triplet", triplet,
    "--x-manifest-root=#{manifest_dir}",
    "--x-install-root=#{install_root}",
    "--x-buildtrees-root=#{File.join(home, 'blds')}",
    "--x-packages-root=#{File.join(home, 'pkgs')}"
  ]
end

.install_error_message(command, status, combined) ⇒ Object

Compose the InstallError message, appending the offline hint when the output looks network-shaped (§5.17).



111
112
113
114
115
116
117
118
# File 'lib/vcdeps/runner.rb', line 111

def install_error_message(command, status, combined)
  msg = "vcdeps: `#{File.basename(command.first)} #{command[1]}` exited #{status}."
  if combined =~ /(failed to (fetch|download)|could not resolve|network)/i
    msg += " First install of this baseline requires network access; " \
           "see `vcdeps doctor` checks 9-10."
  end
  msg
end

.log_tail(combined) ⇒ Object

Last <= LOG_TAIL_MAX chars, UTF-8 scrubbed.



104
105
106
107
# File 'lib/vcdeps/runner.rb', line 104

def log_tail(combined)
  tail = combined.length > LOG_TAIL_MAX ? combined[-LOG_TAIL_MAX..] : combined
  tail.to_s.dup.force_encoding("UTF-8").scrub("")
end

.marker_path(install_root) ⇒ Object

---- completion marker (§2.5) -------------------------------------------



122
123
124
# File 'lib/vcdeps/runner.rb', line 122

def marker_path(install_root)
  File.join(install_root, MARKER_NAME)
end

.marker_present?(install_root) ⇒ Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/vcdeps/runner.rb', line 126

def marker_present?(install_root)
  File.exist?(marker_path(install_root))
end

.run(command, env:, chdir:, out:) ⇒ Object

Generic streamed runner shared by install! and baseline!. Raises InstallError on nonzero exit; the network-failure hint (§5.17) is appended by the caller via install_error so this stays generic.



54
55
56
57
58
59
60
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
94
95
96
97
98
99
100
101
# File 'lib/vcdeps/runner.rb', line 54

def run(command, env:, chdir:, out:)
  combined = +""

  Open3.popen2e(env, *command, chdir: chdir) do |_stdin, oe, wt|
    pid = wt.pid
    oe.binmode
    begin
      # Buffered each_line dispatches to a scheduler's io_read hook on mswin
      # (§6); reads block in VM I/O with the GVL released. Scrub to UTF-8.
      oe.each_line do |raw|
        line = raw.dup.force_encoding("UTF-8").scrub("")
        combined << line
        out&.print(line)
        out&.flush if out.respond_to?(:flush)
      end
      status = wt.value
      unless status.success?
        raise InstallError.new(
          install_error_message(command, status.exitstatus, combined),
          command: command, status: status.exitstatus,
          log_tail: log_tail(combined)
        )
      end
    rescue InstallError
      raise
    rescue Exception # rubocop:disable Lint/RescueException
      # ANY abnormal unwind (Interrupt, Timeout, a raise from the out block):
      # kill THIS vcpkg.exe so its install-root lock handle is released. We
      # do it HERE, before Open3's block teardown tries to drain the pipe
      # (which would otherwise block on a still-running child). TerminateProcess
      # is not recursive — helper processes vcpkg spawned may briefly survive
      # (§3.2/§5.16). The pipe is then closed so the drain returns at once.
      begin
        Process.kill(:KILL, pid)
      rescue Errno::ESRCH, RangeError
        nil
      end
      begin
        oe.close
      rescue IOError
        nil
      end
      raise
    end
  end

  combined
end

.run_install(command, env:, chdir:, out:) ⇒ Object

Run vcpkg install. Streams combined output to out (nil to silence), returns the captured combined output on success, raises InstallError on a nonzero exit. command is the full argv (already built by install_args).



47
48
49
# File 'lib/vcdeps/runner.rb', line 47

def run_install(command, env:, chdir:, out:)
  run(command, env: env, chdir: chdir, out: out)
end

.write_marker!(install_root, key, tool_version) ⇒ Object

Write the marker atomically: temp file then File.rename (atomic on NTFS).



131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/vcdeps/runner.rb', line 131

def write_marker!(install_root, key, tool_version)
  payload = JSON.generate(
    "key" => key,
    "tool_version" => tool_version.to_s,
    "finished_at" => Time.now.utc.iso8601
  )
  final = marker_path(install_root)
  tmp = "#{final}.tmp.#{Process.pid}"
  File.write(tmp, payload)
  File.rename(tmp, final)
ensure
  File.unlink(tmp) if tmp && File.exist?(tmp) && !File.exist?(final)
end