FFI::ClangSourceFFIClangMswinArgs

class MswinArgs

MSVC-specific clang configuration. Discovers libclang and system include paths from the Visual Studio installation:

  1. Find the VS installation path via vswhere.exe
  2. Call vcvarsall.bat to set up the MSVC developer environment
  3. Run clang-cl -v -E -x c++ NUL in that environment
  4. Parse the "#include <...> search starts here:" block from the output
  5. 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 library FFI::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 output String

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