Module: Vcdeps::Vendor
- Defined in:
- lib/vcdeps/vendor.rb
Overview
Syncs an Installed's runtime DLLs, per-port copyright files, and a standalone
Fiddle-preload shim into a vendor directory (§2.2/§2.7). This is a SYNC, not
a copy: vendor! OWNS into
is touched, so authors may keep unrelated assets there.
Constant Summary collapse
- SHIM_NAME =
"preload.rb"- LICENSES_DIR =
"licenses"
Class Method Summary collapse
-
.shim_source ⇒ Object
The generated preload shim (exact §2.7 template).
-
.sweep_stale!(into, kept_dll_names, kept_license_names, write_shim) ⇒ Object
Delete any *.dll directly in
intonot on the kept list, any licenses* file not kept, and a stale shim when no shim is being written. -
.sync!(installed, into:, licenses: true, shim: true, out: $stderr) ⇒ Object
Returns the list of files written (absolute, backslashed).
-
.warn_on_ruby_bin_collision(base, out) ⇒ Object
Warn when a vendored DLL base name also exists in Ruby's bindir (zlib1.dll/ffi-8.dll/yaml.dll shadowing, R§9.2) — first-loaded wins.
- .win(path) ⇒ Object
Class Method Details
.shim_source ⇒ Object
The generated preload shim (exact §2.7 template). Standalone: stdlib only, no vcdeps require, safe to require twice, safe with zero DLLs present.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/vcdeps/vendor.rb', line 83 def shim_source <<~SHIM # frozen_string_literal: true # # Generated by vcdeps #{Vcdeps::VERSION}. Do not edit; regenerate with `vcdeps vendor`. # # Preloads the vendored vcpkg runtime DLLs by ABSOLUTE path before the C # extension loads. Windows resolves an extension's dependent DLLs via the # standard search order, which checks the loaded-module list (step 4) long # before PATH (step 12) and NEVER checks the .so's own directory — so loading # each DLL here, by full path, is what makes `require "<gem>/<gem>"` work. # # The dlopen handles are intentionally leaked for process lifetime: the DLLs # must stay loaded as long as the extension is loaded. require "fiddle" pending = Dir[File.join(__dir__, "*.dll")].sort until pending.empty? progressed = false pending.delete_if do |dll| Fiddle.dlopen(dll) progressed = true true rescue Fiddle::DLError false # depends on a sibling not yet loaded — retry next pass end break unless progressed # truly unloadable; let the require raise its own LoadError end SHIM end |
.sweep_stale!(into, kept_dll_names, kept_license_names, write_shim) ⇒ Object
Delete any *.dll directly in into not on the kept list, any licenses*
file not kept, and a stale shim when no shim is being written.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/vcdeps/vendor.rb', line 118 def sweep_stale!(into, kept_dll_names, kept_license_names, write_shim) Dir[File.join(into.tr("\\", "/"), "*.dll")].each do |dll| File.delete(dll) unless kept_dll_names.include?(File.basename(dll).downcase) end ldir = File.join(into.tr("\\", "/"), LICENSES_DIR) if File.directory?(ldir) Dir[File.join(ldir, "*")].each do |f| next unless File.file?(f) File.delete(f) unless kept_license_names.include?(File.basename(f).downcase) end # Remove an empty licenses dir we no longer populate. begin Dir.rmdir(ldir) if Dir.empty?(ldir) rescue SystemCallError nil end end shim = File.join(into.tr("\\", "/"), SHIM_NAME) File.delete(shim) if !write_shim && File.exist?(shim) end |
.sync!(installed, into:, licenses: true, shim: true, out: $stderr) ⇒ Object
Returns the list of files written (absolute, backslashed). See §2.2 for the
four-case table; stale owned files are deleted whenever into exists.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 72 73 74 75 76 77 78 79 |
# File 'lib/vcdeps/vendor.rb', line 23 def sync!(installed, into:, licenses: true, shim: true, out: $stderr) dlls = installed.dlls copyrights = licenses ? installed.copyright_files : {} # The write plan: a DLL is vendored only when there is something to vendor; # the shim is written only when DLLs exist; licenses ship whenever copyright # files exist (including static-md, where there are no DLLs). write_dlls = dlls write_shim = shim && !dlls.empty? write_copies = copyrights # Nothing to write AND nothing to clean (into absent) -> [] without # creating `into` (§2.2 last case). if write_dlls.empty? && write_copies.empty? && !write_shim && !File.directory?(into) return [] end written = [] FileUtils.mkdir_p(into) # Owned DLLs we intend to keep (base names), for the stale sweep. kept_dll_names = [] write_dlls.each do |src| base = File.basename(src) dest = File.join(into, base) FileUtils.cp(src, dest) kept_dll_names << base.downcase written << win(dest) warn_on_ruby_bin_collision(base, out) end # Licenses. kept_license_names = [] unless write_copies.empty? ldir = File.join(into, LICENSES_DIR) FileUtils.mkdir_p(ldir) write_copies.each do |port, src| dest = File.join(ldir, "#{port}-copyright.txt") FileUtils.cp(src, dest) kept_license_names << File.basename(dest).downcase written << win(dest) end end # Shim. shim_path = File.join(into, SHIM_NAME) if write_shim File.write(shim_path, shim_source) written << win(shim_path) end # Stale sweep of OWNED files only. sweep_stale!(into, kept_dll_names, kept_license_names, write_shim) written end |
.warn_on_ruby_bin_collision(base, out) ⇒ Object
Warn when a vendored DLL base name also exists in Ruby's bindir (zlib1.dll/ffi-8.dll/yaml.dll shadowing, R§9.2) — first-loaded wins.
144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/vcdeps/vendor.rb', line 144 def warn_on_ruby_bin_collision(base, out) bindir = RbConfig::CONFIG["bindir"].to_s return if bindir.empty? ruby_copy = File.join(bindir, base) return unless File.exist?(ruby_copy) out&.puts("[vcdeps] WARNING: #{base} also ships in #{bindir.tr('/', '\\')} " \ "— the copy loaded first wins process-wide; preload.rb runs " \ "first under normal require order.") end |
.win(path) ⇒ Object
156 157 158 |
# File 'lib/vcdeps/vendor.rb', line 156 def win(path) path.nil? ? path : path.tr("/", "\\") end |