class MswinArgs
MSVC-specific clang configuration. Discovers libclang and system include paths from the Visual Studio installation:
- Find the VS installation path via vswhere.exe
- Call vcvarsall.bat to set up the MSVC developer environment
- Run clang-cl -v -E -x c++ NUL in that environment
- Parse the "#include <...> search starts here:" block from the output
- Inject each discovered path as -I into parse_translation_unit
Definitions
def post_load(library)
Pin libclang in memory so Windows will not unload it at exit.
LLVM's rpmalloc allocator registers Fiber Local Storage (FLS) callbacks via FlsAlloc but does not call FlsFree on DLL_PROCESS_DETACH (LLVM bug #154361, fixed in LLVM 22.1.0 by https://github.com/llvm/llvm-project/pull/171465). Pinning with GET_MODULE_HANDLE_EX_FLAG_PIN prevents the unload so the FLS callbacks remain valid through process shutdown.
Signature
-
parameter
libraryFFI::DynamicLibrary The loaded libclang library.
Implementation
def post_load(library)
symbol = library.find_symbol("clang_getClangVersion")
return unless symbol
kernel32 = FFI::DynamicLibrary.open("kernel32", 0)
get_module_handle_ex_w = kernel32.find_function("GetModuleHandleExW")
return unless get_module_handle_ex_w
get_module_handle_ex_flag_from_address = 0x4
get_module_handle_ex_flag_pin = 0x1
flags = get_module_handle_ex_flag_from_address | get_module_handle_ex_flag_pin
handle_out = FFI::MemoryPointer.new(:pointer)
pin = FFI::Function.new(:bool, [:uint, :pointer, :pointer], get_module_handle_ex_w)
pin.call(flags, symbol, handle_out)
end
def find_resource_dir
Mswin skips "clang on PATH" — uses clang-cl probe instead.
Implementation
def find_resource_dir
# 1. Explicit override via environment variable.
env = ENV["LIBCLANG_RESOURCE_DIR"]
return env if valid_resource_dir?(env)
# 2. clang-cl next to the loaded libclang.
if @libclang_loaded_path
clang_cl = ::File.join(::File.dirname(@libclang_loaded_path), "clang-cl.exe")
if ::File.exist?(clang_cl) && (dir = resource_dir_from_clang(clang_cl))
return dir
end
end
# 3. Probe relative to the loaded libclang shared library.
if @libclang_loaded_path && (dir = probe_from_libclang(@libclang_loaded_path))
return dir
end
nil
end
def system_includes
Parse system include paths from clang-cl running in a VS developer environment.
Signature
-
returns
Array(String) System include directories.
Implementation
def system_includes
@system_includes ||= find_system_includes
end
def find_clang_cl
Find clang-cl.exe — next to loaded libclang, or in VS LLVM dir.
Signature
-
returns
String | Nil Path to clang-cl.exe, or nil.
Implementation
def find_clang_cl
if @libclang_loaded_path
path = ::File.join(::File.dirname(@libclang_loaded_path), "clang-cl.exe")
return path if ::File.exist?(path)
end
if (vs_llvm = vs_llvm_dir)
path = ::File.join(vs_llvm, "bin", "clang-cl.exe")
return path if ::File.exist?(path)
end
nil
end
def parse_include_paths(output)
Parse the #include <...> search paths from clang -v output.
Signature
-
parameter
outputString The combined stdout/stderr from clang-cl -v.
-
returns
Array(String) The include directories.
Implementation
def parse_include_paths(output)
paths = []
in_search_list = false
output.each_line do |line|
line = line.strip
if line == "#include <...> search starts here:"
in_search_list = true
elsif line == "End of search list."
break
elsif in_search_list && !line.empty?
paths << line
end
end
paths
end
def vs_installation_path
Find the VS installation path using vswhere.
Signature
-
returns
String | Nil Path like "C:/Program Files/Microsoft Visual Studio/18/Insiders", or nil.
Implementation
def vs_installation_path
if defined?(@vs_installation_path)
return @vs_installation_path
end
@vs_installation_path = find_vs_installation_path
end
def vs_llvm_dir
Find the VS-bundled LLVM directory.
Signature
-
returns
String | Nil Path like ".../VC/Tools/Llvm/x64", or nil.
Implementation
def vs_llvm_dir
vs_path = vs_installation_path
return nil unless vs_path
arch = RbConfig::CONFIG["target_cpu"] == "x64" ? "x64" : "ARM64"
llvm_dir = ::File.join(vs_path, "VC", "Tools", "Llvm", arch)
::File.directory?(llvm_dir) ? llvm_dir : nil
end