mirror of
https://github.com/ziglang/zig.git
synced 2025-12-27 16:43:07 +00:00
Before, iteration would stop whenever an installation with vcruntime.lib was found, but that may not be the most recent installed version. Instead, we now iterate all installed instances and choose the one with the newest version.
1076 lines
47 KiB
Zig
1076 lines
47 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
const windows = std.os.windows;
|
|
const RRF = windows.advapi32.RRF;
|
|
|
|
const WINDOWS_KIT_REG_KEY = "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots";
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/msi/productversion
|
|
const version_major_minor_max_length = "255.255".len;
|
|
// note(bratishkaerik): i think ProductVersion in registry (created by Visual Studio installer) also follows this rule
|
|
const product_version_max_length = version_major_minor_max_length + ".65535".len;
|
|
|
|
/// Iterates via `iterator` and collects all folders with names starting with `optional_prefix`
|
|
/// and similar to SemVer. Returns slice of folder names sorted in descending order.
|
|
/// Caller owns result.
|
|
fn iterateAndFilterBySemVer(iterator: *std.fs.IterableDir.Iterator, allocator: std.mem.Allocator, comptime optional_prefix: ?[]const u8) error{ OutOfMemory, VersionNotFound }![][]const u8 {
|
|
var dirs_filtered_list = std.ArrayList([]const u8).init(allocator);
|
|
errdefer {
|
|
for (dirs_filtered_list.items) |filtered_dir| allocator.free(filtered_dir);
|
|
dirs_filtered_list.deinit();
|
|
}
|
|
|
|
var normalized_name_buf: [std.fs.MAX_NAME_BYTES + ".0+build.0".len]u8 = undefined;
|
|
var normalized_name_fbs = std.io.fixedBufferStream(&normalized_name_buf);
|
|
const normalized_name_w = normalized_name_fbs.writer();
|
|
iterate_folder: while (true) : (normalized_name_fbs.reset()) {
|
|
const maybe_entry = iterator.next() catch continue :iterate_folder;
|
|
const entry = maybe_entry orelse break :iterate_folder;
|
|
|
|
if (entry.kind != .directory)
|
|
continue :iterate_folder;
|
|
|
|
// invalidated on next iteration
|
|
const subfolder_name = blk: {
|
|
if (comptime optional_prefix) |prefix| {
|
|
if (!std.mem.startsWith(u8, entry.name, prefix)) continue :iterate_folder;
|
|
break :blk entry.name[prefix.len..];
|
|
} else break :blk entry.name;
|
|
};
|
|
|
|
{ // check if subfolder name looks similar to SemVer
|
|
switch (std.mem.count(u8, subfolder_name, ".")) {
|
|
0 => normalized_name_w.print("{s}.0.0+build.0", .{subfolder_name}) catch unreachable, // 17 => 17.0.0+build.0
|
|
1 => if (std.mem.indexOfScalar(u8, subfolder_name, '_')) |underscore_pos| blk: { // 17.0_9e9cbb98 => 17.0.1+build.9e9cbb98
|
|
var subfolder_name_tmp_copy_buf: [std.fs.MAX_NAME_BYTES]u8 = undefined;
|
|
const subfolder_name_tmp_copy = subfolder_name_tmp_copy_buf[0..subfolder_name.len];
|
|
@memcpy(subfolder_name_tmp_copy, subfolder_name);
|
|
|
|
subfolder_name_tmp_copy[underscore_pos] = '.'; // 17.0_9e9cbb98 => 17.0.9e9cbb98
|
|
var subfolder_name_parts = std.mem.splitScalar(u8, subfolder_name_tmp_copy, '.'); // [ 17, 0, 9e9cbb98 ]
|
|
|
|
const first = subfolder_name_parts.first(); // 17
|
|
const second = subfolder_name_parts.next().?; // 0
|
|
const third = subfolder_name_parts.rest(); // 9e9cbb98
|
|
|
|
break :blk normalized_name_w.print("{s}.{s}.1+build.{s}", .{ first, second, third }) catch unreachable; // [ 17, 0, 9e9cbb98 ] => 17.0.1+build.9e9cbb98
|
|
} else normalized_name_w.print("{s}.0+build.0", .{subfolder_name}) catch unreachable, // 17.0 => 17.0.0+build.0
|
|
else => normalized_name_w.print("{s}+build.0", .{subfolder_name}) catch unreachable, // 17.0.0 => 17.0.0+build.0
|
|
}
|
|
const subfolder_name_normalized: []const u8 = normalized_name_fbs.getWritten();
|
|
const sem_ver = std.SemanticVersion.parse(subfolder_name_normalized);
|
|
_ = sem_ver catch continue :iterate_folder;
|
|
}
|
|
// entry.name passed check
|
|
|
|
const subfolder_name_allocated = try allocator.dupe(u8, subfolder_name);
|
|
errdefer allocator.free(subfolder_name_allocated);
|
|
try dirs_filtered_list.append(subfolder_name_allocated);
|
|
}
|
|
|
|
var dirs_filtered_slice = try dirs_filtered_list.toOwnedSlice();
|
|
// Keep in mind that order of these names is not guaranteed by Windows,
|
|
// so we cannot just reverse or "while (popOrNull())" this ArrayList.
|
|
std.mem.sortUnstable([]const u8, dirs_filtered_slice, {}, struct {
|
|
fn desc(_: void, lhs: []const u8, rhs: []const u8) bool {
|
|
return std.mem.order(u8, lhs, rhs) == .gt;
|
|
}
|
|
}.desc);
|
|
return dirs_filtered_slice;
|
|
}
|
|
|
|
const RegistryUtf8 = struct {
|
|
key: windows.HKEY,
|
|
|
|
/// Assert that `key` is valid UTF-8 string
|
|
pub fn openKey(key: []const u8) error{KeyNotFound}!RegistryUtf8 {
|
|
const key_utf16le: [:0]const u16 = key_utf16le: {
|
|
var key_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
|
|
const key_utf16le_len: usize = std.unicode.utf8ToUtf16Le(key_utf16le_buf[0..], key) catch |err| switch (err) {
|
|
error.InvalidUtf8 => unreachable,
|
|
};
|
|
key_utf16le_buf[key_utf16le_len] = 0;
|
|
break :key_utf16le key_utf16le_buf[0..key_utf16le_len :0];
|
|
};
|
|
|
|
const registry_utf16le = try RegistryUtf16Le.openKey(key_utf16le);
|
|
return RegistryUtf8{ .key = registry_utf16le.key };
|
|
}
|
|
|
|
/// Closes key, after that usage is invalid
|
|
pub fn closeKey(self: *const RegistryUtf8) void {
|
|
const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
|
|
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => {},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
/// Get string from registry.
|
|
/// Caller owns result.
|
|
pub fn getString(self: *const RegistryUtf8, allocator: std.mem.Allocator, subkey: []const u8, value_name: []const u8) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]u8 {
|
|
const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
|
|
var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
|
|
const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
|
|
subkey_utf16le_buf[subkey_utf16le_len] = 0;
|
|
break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
|
|
};
|
|
|
|
const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
|
|
var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
|
|
const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
|
|
value_name_utf16le_buf[value_name_utf16le_len] = 0;
|
|
break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
|
|
};
|
|
|
|
const registry_utf16le = RegistryUtf16Le{ .key = self.key };
|
|
const value_utf16le = try registry_utf16le.getString(allocator, subkey_utf16le, value_name_utf16le);
|
|
defer allocator.free(value_utf16le);
|
|
|
|
var value_utf8: []u8 = std.unicode.utf16leToUtf8Alloc(allocator, value_utf16le) catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => return error.StringNotFound,
|
|
};
|
|
errdefer allocator.free(value_utf8);
|
|
|
|
return value_utf8;
|
|
}
|
|
|
|
/// Get DWORD (u32) from registry.
|
|
pub fn getDword(self: *const RegistryUtf8, subkey: []const u8, value_name: []const u8) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
|
|
const subkey_utf16le: [:0]const u16 = subkey_utf16le: {
|
|
var subkey_utf16le_buf: [RegistryUtf16Le.key_name_max_len]u16 = undefined;
|
|
const subkey_utf16le_len: usize = std.unicode.utf8ToUtf16Le(subkey_utf16le_buf[0..], subkey) catch unreachable;
|
|
subkey_utf16le_buf[subkey_utf16le_len] = 0;
|
|
break :subkey_utf16le subkey_utf16le_buf[0..subkey_utf16le_len :0];
|
|
};
|
|
|
|
const value_name_utf16le: [:0]const u16 = value_name_utf16le: {
|
|
var value_name_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
|
|
const value_name_utf16le_len: usize = std.unicode.utf8ToUtf16Le(value_name_utf16le_buf[0..], value_name) catch unreachable;
|
|
value_name_utf16le_buf[value_name_utf16le_len] = 0;
|
|
break :value_name_utf16le value_name_utf16le_buf[0..value_name_utf16le_len :0];
|
|
};
|
|
|
|
const registry_utf16le = RegistryUtf16Le{ .key = self.key };
|
|
return try registry_utf16le.getDword(subkey_utf16le, value_name_utf16le);
|
|
}
|
|
|
|
/// Under private space with flags:
|
|
/// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
|
|
/// After finishing work, call `closeKey`.
|
|
pub fn loadFromPath(absolute_path: []const u8) error{KeyNotFound}!RegistryUtf8 {
|
|
const absolute_path_utf16le: [:0]const u16 = absolute_path_utf16le: {
|
|
var absolute_path_utf16le_buf: [RegistryUtf16Le.value_name_max_len]u16 = undefined;
|
|
const absolute_path_utf16le_len: usize = std.unicode.utf8ToUtf16Le(absolute_path_utf16le_buf[0..], absolute_path) catch unreachable;
|
|
absolute_path_utf16le_buf[absolute_path_utf16le_len] = 0;
|
|
break :absolute_path_utf16le absolute_path_utf16le_buf[0..absolute_path_utf16le_len :0];
|
|
};
|
|
|
|
const registry_utf16le = try RegistryUtf16Le.loadFromPath(absolute_path_utf16le);
|
|
return RegistryUtf8{ .key = registry_utf16le.key };
|
|
}
|
|
};
|
|
|
|
const RegistryUtf16Le = struct {
|
|
key: windows.HKEY,
|
|
|
|
/// Includes root key (f.e. HKEY_LOCAL_MACHINE).
|
|
/// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
|
|
pub const key_name_max_len = 255;
|
|
/// In Unicode characters.
|
|
/// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
|
|
pub const value_name_max_len = 16_383;
|
|
|
|
/// Under HKEY_LOCAL_MACHINE with flags:
|
|
/// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS.
|
|
/// After finishing work, call `closeKey`.
|
|
fn openKey(key_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
|
|
var key: windows.HKEY = undefined;
|
|
const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW(
|
|
windows.HKEY_LOCAL_MACHINE,
|
|
key_utf16le,
|
|
0,
|
|
windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS,
|
|
&key,
|
|
);
|
|
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => {},
|
|
.FILE_NOT_FOUND => return error.KeyNotFound,
|
|
|
|
else => return error.KeyNotFound,
|
|
}
|
|
return RegistryUtf16Le{ .key = key };
|
|
}
|
|
|
|
/// Closes key, after that usage is invalid
|
|
fn closeKey(self: *const RegistryUtf16Le) void {
|
|
const return_code_int: windows.HRESULT = windows.advapi32.RegCloseKey(self.key);
|
|
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => {},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
/// Get string ([:0]const u16) from registry.
|
|
fn getString(self: *const RegistryUtf16Le, allocator: std.mem.Allocator, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ OutOfMemory, ValueNameNotFound, NotAString, StringNotFound }![]const u16 {
|
|
var actual_type: windows.ULONG = undefined;
|
|
|
|
// Calculating length to allocate
|
|
var value_utf16le_buf_size: u32 = 0; // in bytes, including any terminating NUL character or characters.
|
|
var return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
|
|
self.key,
|
|
subkey_utf16le,
|
|
value_name_utf16le,
|
|
RRF.RT_REG_SZ,
|
|
&actual_type,
|
|
null,
|
|
&value_utf16le_buf_size,
|
|
);
|
|
|
|
// Check returned code and type
|
|
var return_code: windows.Win32Error = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => std.debug.assert(value_utf16le_buf_size != 0),
|
|
.MORE_DATA => unreachable, // We are only reading length
|
|
.FILE_NOT_FOUND => return error.ValueNameNotFound,
|
|
.INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
|
|
else => return error.StringNotFound,
|
|
}
|
|
switch (actual_type) {
|
|
windows.REG.SZ => {},
|
|
else => return error.NotAString,
|
|
}
|
|
|
|
var value_utf16le_buf: []u16 = try allocator.alloc(u16, std.math.divCeil(u32, value_utf16le_buf_size, 2) catch unreachable);
|
|
errdefer allocator.free(value_utf16le_buf);
|
|
|
|
return_code_int = windows.advapi32.RegGetValueW(
|
|
self.key,
|
|
subkey_utf16le,
|
|
value_name_utf16le,
|
|
RRF.RT_REG_SZ,
|
|
&actual_type,
|
|
value_utf16le_buf.ptr,
|
|
&value_utf16le_buf_size,
|
|
);
|
|
|
|
// Check returned code and (just in case) type again.
|
|
return_code = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => {},
|
|
.MORE_DATA => unreachable, // Calculated first time length should be enough, even overestimated
|
|
.FILE_NOT_FOUND => return error.ValueNameNotFound,
|
|
.INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
|
|
else => return error.StringNotFound,
|
|
}
|
|
switch (actual_type) {
|
|
windows.REG.SZ => {},
|
|
else => return error.NotAString,
|
|
}
|
|
|
|
const value_utf16le: []const u16 = value_utf16le: {
|
|
// note(bratishkaerik): somehow returned value in `buf_len` is overestimated by Windows and contains extra space
|
|
// we will just search for zero termination and forget length
|
|
// Windows sure is strange
|
|
const value_utf16le_overestimated: [*:0]const u16 = @ptrCast(value_utf16le_buf.ptr);
|
|
break :value_utf16le std.mem.span(value_utf16le_overestimated);
|
|
};
|
|
|
|
_ = allocator.resize(value_utf16le_buf, value_utf16le.len);
|
|
return value_utf16le;
|
|
}
|
|
|
|
/// Get DWORD (u32) from registry.
|
|
fn getDword(self: *const RegistryUtf16Le, subkey_utf16le: [:0]const u16, value_name_utf16le: [:0]const u16) error{ ValueNameNotFound, NotADword, DwordTooLong, DwordNotFound }!u32 {
|
|
var actual_type: windows.ULONG = undefined;
|
|
var reg_size: u32 = @sizeOf(u32);
|
|
var reg_value: u32 = 0;
|
|
|
|
const return_code_int: windows.HRESULT = windows.advapi32.RegGetValueW(
|
|
self.key,
|
|
subkey_utf16le,
|
|
value_name_utf16le,
|
|
RRF.RT_REG_DWORD,
|
|
&actual_type,
|
|
®_value,
|
|
®_size,
|
|
);
|
|
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => {},
|
|
.MORE_DATA => return error.DwordTooLong,
|
|
.FILE_NOT_FOUND => return error.ValueNameNotFound,
|
|
.INVALID_PARAMETER => unreachable, // We didn't combine RRF.SUBKEY_WOW6464KEY and RRF.SUBKEY_WOW6432KEY
|
|
else => return error.DwordNotFound,
|
|
}
|
|
|
|
switch (actual_type) {
|
|
windows.REG.DWORD => {},
|
|
else => return error.NotADword,
|
|
}
|
|
|
|
return reg_value;
|
|
}
|
|
|
|
/// Under private space with flags:
|
|
/// KEY_QUERY_VALUE and KEY_ENUMERATE_SUB_KEYS.
|
|
/// After finishing work, call `closeKey`.
|
|
fn loadFromPath(absolute_path_as_utf16le: [:0]const u16) error{KeyNotFound}!RegistryUtf16Le {
|
|
var key: windows.HKEY = undefined;
|
|
|
|
const return_code_int: windows.HRESULT = std.os.windows.advapi32.RegLoadAppKeyW(
|
|
absolute_path_as_utf16le,
|
|
&key,
|
|
windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS,
|
|
0,
|
|
0,
|
|
);
|
|
const return_code: windows.Win32Error = @enumFromInt(return_code_int);
|
|
switch (return_code) {
|
|
.SUCCESS => {},
|
|
else => return error.KeyNotFound,
|
|
}
|
|
|
|
return RegistryUtf16Le{ .key = key };
|
|
}
|
|
};
|
|
|
|
pub const Windows10Sdk = struct {
|
|
path: []const u8,
|
|
version: []const u8,
|
|
|
|
/// Find path and version of Windows 10 SDK.
|
|
/// Caller owns the result's fields.
|
|
/// After finishing work, call `free(allocator)`.
|
|
fn find(allocator: std.mem.Allocator) error{ OutOfMemory, Windows10SdkNotFound, PathTooLong, VersionTooLong }!Windows10Sdk {
|
|
const v10_key = RegistryUtf8.openKey("SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0") catch |err| switch (err) {
|
|
error.KeyNotFound => return error.Windows10SdkNotFound,
|
|
};
|
|
defer v10_key.closeKey();
|
|
|
|
const path: []const u8 = path10: {
|
|
var path_maybe_with_trailing_slash = v10_key.getString(allocator, "", "InstallationFolder") catch |err| switch (err) {
|
|
error.NotAString => return error.Windows10SdkNotFound,
|
|
error.ValueNameNotFound => return error.Windows10SdkNotFound,
|
|
error.StringNotFound => return error.Windows10SdkNotFound,
|
|
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
|
|
if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) {
|
|
allocator.free(path_maybe_with_trailing_slash);
|
|
return error.PathTooLong;
|
|
}
|
|
|
|
var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash);
|
|
errdefer path.deinit();
|
|
|
|
// String might contain trailing slash, so trim it here
|
|
if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
|
|
|
|
const path_without_trailing_slash = try path.toOwnedSlice();
|
|
break :path10 path_without_trailing_slash;
|
|
};
|
|
errdefer allocator.free(path);
|
|
|
|
const version: []const u8 = version10: {
|
|
|
|
// note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key....
|
|
var version_without_0 = v10_key.getString(allocator, "", "ProductVersion") catch |err| switch (err) {
|
|
error.NotAString => return error.Windows10SdkNotFound,
|
|
error.ValueNameNotFound => return error.Windows10SdkNotFound,
|
|
error.StringNotFound => return error.Windows10SdkNotFound,
|
|
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
if (version_without_0.len + ".0".len > product_version_max_length) {
|
|
allocator.free(version_without_0);
|
|
return error.VersionTooLong;
|
|
}
|
|
|
|
var version = std.ArrayList(u8).fromOwnedSlice(allocator, version_without_0);
|
|
errdefer version.deinit();
|
|
|
|
try version.appendSlice(".0");
|
|
|
|
const version_with_0 = try version.toOwnedSlice();
|
|
break :version10 version_with_0;
|
|
};
|
|
errdefer allocator.free(version);
|
|
|
|
return Windows10Sdk{ .path = path, .version = version };
|
|
}
|
|
|
|
/// Check whether this version is enumerated in registry.
|
|
fn isValidVersion(windows10sdk: *const Windows10Sdk) bool {
|
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const reg_query_as_utf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{ WINDOWS_KIT_REG_KEY, windows10sdk.version }) catch |err| switch (err) {
|
|
error.NoSpaceLeft => return false,
|
|
};
|
|
|
|
const options_key = RegistryUtf8.openKey(reg_query_as_utf8) catch |err| switch (err) {
|
|
error.KeyNotFound => return false,
|
|
};
|
|
defer options_key.closeKey();
|
|
|
|
const option_name = comptime switch (builtin.target.cpu.arch) {
|
|
.arm, .armeb => "OptionId.DesktopCPParm",
|
|
.aarch64 => "OptionId.DesktopCPParm64",
|
|
.x86_64 => "OptionId.DesktopCPPx64",
|
|
.x86 => "OptionId.DesktopCPPx86",
|
|
else => |tag| @compileError("Windows 10 SDK cannot be detected on architecture " ++ tag),
|
|
};
|
|
|
|
const reg_value = options_key.getDword("", option_name) catch return false;
|
|
return (reg_value == 1);
|
|
}
|
|
|
|
fn free(self: *const Windows10Sdk, allocator: std.mem.Allocator) void {
|
|
allocator.free(self.path);
|
|
allocator.free(self.version);
|
|
}
|
|
};
|
|
|
|
pub const Windows81Sdk = struct {
|
|
path: []const u8,
|
|
version: []const u8,
|
|
|
|
/// Find path and version of Windows 8.1 SDK.
|
|
/// Caller owns the result's fields.
|
|
/// After finishing work, call `free(allocator)`.
|
|
fn find(allocator: std.mem.Allocator, roots_key: *const RegistryUtf8) error{ OutOfMemory, Windows81SdkNotFound, PathTooLong, VersionTooLong }!Windows81Sdk {
|
|
const path: []const u8 = path81: {
|
|
var path_maybe_with_trailing_slash = roots_key.getString(allocator, "", "KitsRoot81") catch |err| switch (err) {
|
|
error.NotAString => return error.Windows81SdkNotFound,
|
|
error.ValueNameNotFound => return error.Windows81SdkNotFound,
|
|
error.StringNotFound => return error.Windows81SdkNotFound,
|
|
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) {
|
|
allocator.free(path_maybe_with_trailing_slash);
|
|
return error.PathTooLong;
|
|
}
|
|
|
|
var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash);
|
|
errdefer path.deinit();
|
|
|
|
// String might contain trailing slash, so trim it here
|
|
if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
|
|
|
|
const path_without_trailing_slash = try path.toOwnedSlice();
|
|
break :path81 path_without_trailing_slash;
|
|
};
|
|
errdefer allocator.free(path);
|
|
|
|
const version: []const u8 = version81: {
|
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const sdk_lib_dir_path = std.fmt.bufPrint(buf[0..], "{s}\\Lib\\", .{path}) catch |err| switch (err) {
|
|
error.NoSpaceLeft => return error.PathTooLong,
|
|
};
|
|
if (!std.fs.path.isAbsolute(sdk_lib_dir_path)) return error.Windows81SdkNotFound;
|
|
|
|
// enumerate files in sdk path looking for latest version
|
|
var sdk_lib_dir = std.fs.openIterableDirAbsolute(sdk_lib_dir_path, .{}) catch |err| switch (err) {
|
|
error.NameTooLong => return error.PathTooLong,
|
|
else => return error.Windows81SdkNotFound,
|
|
};
|
|
defer sdk_lib_dir.close();
|
|
|
|
var iterator = sdk_lib_dir.iterate();
|
|
const versions = iterateAndFilterBySemVer(&iterator, allocator, "winv") catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.VersionNotFound => return error.Windows81SdkNotFound,
|
|
};
|
|
defer {
|
|
for (versions) |version| allocator.free(version);
|
|
allocator.free(versions);
|
|
}
|
|
const latest_version = try allocator.dupe(u8, versions[0]);
|
|
break :version81 latest_version;
|
|
};
|
|
errdefer allocator.free(version);
|
|
|
|
return Windows81Sdk{ .path = path, .version = version };
|
|
}
|
|
|
|
fn free(self: *const Windows81Sdk, allocator: std.mem.Allocator) void {
|
|
allocator.free(self.path);
|
|
allocator.free(self.version);
|
|
}
|
|
};
|
|
|
|
pub const ZigWindowsSDK = struct {
|
|
windows10sdk: ?Windows10Sdk,
|
|
windows81sdk: ?Windows81Sdk,
|
|
msvc_lib_dir: ?[]const u8,
|
|
|
|
/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.
|
|
/// Caller owns the result's fields.
|
|
/// After finishing work, call `free(allocator)`.
|
|
pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, NotFound, PathTooLong }!ZigWindowsSDK {
|
|
if (builtin.os.tag != .windows) return error.NotFound;
|
|
|
|
//note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed
|
|
const roots_key = RegistryUtf8.openKey(WINDOWS_KIT_REG_KEY) catch |err| switch (err) {
|
|
error.KeyNotFound => return error.NotFound,
|
|
};
|
|
defer roots_key.closeKey();
|
|
|
|
const windows10sdk: ?Windows10Sdk = blk: {
|
|
const windows10sdk = Windows10Sdk.find(allocator) catch |err| switch (err) {
|
|
error.Windows10SdkNotFound,
|
|
error.PathTooLong,
|
|
error.VersionTooLong,
|
|
=> break :blk null,
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
const is_valid_version = windows10sdk.isValidVersion();
|
|
if (!is_valid_version) break :blk null;
|
|
break :blk windows10sdk;
|
|
};
|
|
errdefer if (windows10sdk) |*w| w.free(allocator);
|
|
|
|
const windows81sdk: ?Windows81Sdk = blk: {
|
|
const windows81sdk = Windows81Sdk.find(allocator, &roots_key) catch |err| switch (err) {
|
|
error.Windows81SdkNotFound => break :blk null,
|
|
error.PathTooLong => break :blk null,
|
|
error.VersionTooLong => break :blk null,
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
// no check
|
|
break :blk windows81sdk;
|
|
};
|
|
errdefer if (windows81sdk) |*w| w.free(allocator);
|
|
|
|
const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) {
|
|
error.MsvcLibDirNotFound => null,
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
errdefer allocator.free(msvc_lib_dir);
|
|
|
|
return ZigWindowsSDK{
|
|
.windows10sdk = windows10sdk,
|
|
.windows81sdk = windows81sdk,
|
|
.msvc_lib_dir = msvc_lib_dir,
|
|
};
|
|
}
|
|
|
|
pub fn free(self: *const ZigWindowsSDK, allocator: std.mem.Allocator) void {
|
|
if (self.windows10sdk) |*w10sdk| {
|
|
w10sdk.free(allocator);
|
|
}
|
|
if (self.windows81sdk) |*w81sdk| {
|
|
w81sdk.free(allocator);
|
|
}
|
|
if (self.msvc_lib_dir) |msvc_lib_dir| {
|
|
allocator.free(msvc_lib_dir);
|
|
}
|
|
}
|
|
};
|
|
|
|
const MsvcLibDir = struct {
|
|
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration
|
|
fn findViaCOM(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 {
|
|
switch (windows.ole32.CoInitializeEx(null, windows.COINIT.MULTITHREADED)) {
|
|
windows.S_OK, windows.S_FALSE => {},
|
|
windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
|
else => return error.PathNotFound,
|
|
}
|
|
// > To close the COM library gracefully on a thread, each successful
|
|
// > call to CoInitialize or CoInitializeEx, including any call that
|
|
// > returns S_FALSE, must be balanced by a corresponding call to CoUninitialize.
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
|
|
defer windows.ole32.CoUninitialize();
|
|
|
|
var setup_config: *ISetupConfiguration = undefined;
|
|
switch (CoCreateInstance(
|
|
SetupConfiguration.CLSID,
|
|
null,
|
|
CLSCTX.INPROC_SERVER | CLSCTX.INPROC_HANDLER,
|
|
ISetupConfiguration.IID,
|
|
@ptrCast(&setup_config),
|
|
)) {
|
|
windows.S_OK => {},
|
|
windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
|
else => return error.PathNotFound,
|
|
}
|
|
defer _ = setup_config.vtable.unknown.Release(setup_config);
|
|
|
|
var setup_helper: *ISetupHelper = undefined;
|
|
switch (setup_config.vtable.unknown.QueryInterface(
|
|
setup_config,
|
|
ISetupHelper.IID,
|
|
@ptrCast(&setup_helper),
|
|
)) {
|
|
windows.S_OK => {},
|
|
else => return error.PathNotFound,
|
|
}
|
|
|
|
var all_instances: *IEnumSetupInstances = undefined;
|
|
switch (setup_config.vtable.setup_configuration.EnumInstances(setup_config, &all_instances)) {
|
|
windows.S_OK => {},
|
|
windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
|
else => return error.PathNotFound,
|
|
}
|
|
defer _ = all_instances.vtable.unknown.Release(all_instances);
|
|
|
|
var latest_version: windows.ULONGLONG = 0;
|
|
var latest_version_lib_dir: ?[]const u8 = null;
|
|
while (true) {
|
|
var cur: *ISetupInstance = undefined;
|
|
switch (all_instances.vtable.enum_setup_instances.Next(all_instances, 1, &cur, null)) {
|
|
windows.S_OK => {},
|
|
windows.S_FALSE => break,
|
|
windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
|
else => return error.PathNotFound,
|
|
}
|
|
defer _ = cur.vtable.unknown.Release(cur);
|
|
|
|
var installation_version_bstr: windows.BSTR = undefined;
|
|
switch (cur.vtable.setup_instance.GetInstallationVersion(cur, &installation_version_bstr)) {
|
|
windows.S_OK => {},
|
|
windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
|
else => continue,
|
|
}
|
|
defer SysFreeString(installation_version_bstr);
|
|
|
|
var parsed_version: windows.ULONGLONG = undefined;
|
|
switch (setup_helper.vtable.setup_helper.ParseVersion(setup_helper, installation_version_bstr, &parsed_version)) {
|
|
windows.S_OK => {},
|
|
else => continue,
|
|
}
|
|
|
|
// We want to end up with the most recent version installed
|
|
if (parsed_version <= latest_version) continue;
|
|
|
|
var installation_path_bstr: windows.BSTR = undefined;
|
|
switch (cur.vtable.setup_instance.GetInstallationPath(cur, &installation_path_bstr)) {
|
|
windows.S_OK => {},
|
|
windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
|
else => continue,
|
|
}
|
|
defer SysFreeString(installation_path_bstr);
|
|
|
|
const installation_path_w = std.mem.span(installation_path_bstr);
|
|
const lib_dir_path = libDirFromInstallationPath(allocator, installation_path_w) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
error.PathNotFound => continue,
|
|
};
|
|
errdefer allocator.free(lib_dir_path);
|
|
|
|
if (latest_version_lib_dir) |prev_lib_dir| {
|
|
allocator.free(prev_lib_dir);
|
|
}
|
|
latest_version_lib_dir = lib_dir_path;
|
|
latest_version = parsed_version;
|
|
}
|
|
|
|
return latest_version_lib_dir orelse error.PathNotFound;
|
|
}
|
|
|
|
fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path_w: []const u16) error{ OutOfMemory, PathNotFound }![]const u8 {
|
|
// Each UTF-16LE code unit may be expanded to 3 UTF-8 bytes.
|
|
var lib_dir_buf = try std.ArrayList(u8).initCapacity(allocator, installation_path_w.len * 3);
|
|
errdefer lib_dir_buf.deinit();
|
|
|
|
lib_dir_buf.items.len = std.unicode.utf16leToUtf8(lib_dir_buf.unusedCapacitySlice(), installation_path_w) catch {
|
|
return error.PathNotFound;
|
|
};
|
|
|
|
if (!std.fs.path.isSep(lib_dir_buf.getLast())) {
|
|
try lib_dir_buf.append('\\');
|
|
}
|
|
const installation_path_with_trailing_sep_len = lib_dir_buf.items.len;
|
|
|
|
try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");
|
|
var default_tools_version_buf: [512]u8 = undefined;
|
|
const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch {
|
|
return error.PathNotFound;
|
|
};
|
|
var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n");
|
|
const default_tools_version = tokenizer.next() orelse return error.PathNotFound;
|
|
|
|
lib_dir_buf.shrinkRetainingCapacity(installation_path_with_trailing_sep_len);
|
|
try lib_dir_buf.appendSlice("VC\\Tools\\MSVC\\");
|
|
try lib_dir_buf.appendSlice(default_tools_version);
|
|
const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) {
|
|
.x86 => "x86",
|
|
.x86_64 => "x64",
|
|
.arm, .armeb => "arm",
|
|
.aarch64 => "arm64",
|
|
else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag),
|
|
};
|
|
try lib_dir_buf.appendSlice(folder_with_arch);
|
|
|
|
if (!verifyLibDir(lib_dir_buf.items)) {
|
|
return error.PathNotFound;
|
|
}
|
|
|
|
return lib_dir_buf.toOwnedSlice();
|
|
}
|
|
|
|
// https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance
|
|
fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 {
|
|
|
|
// %localappdata%\Microsoft\VisualStudio\
|
|
// %appdata%\Local\Microsoft\VisualStudio\
|
|
const visualstudio_folder_path = std.fs.getAppDataDir(allocator, "Microsoft\\VisualStudio\\") catch return error.PathNotFound;
|
|
defer allocator.free(visualstudio_folder_path);
|
|
|
|
const vs_versions: []const []const u8 = vs_versions: {
|
|
if (!std.fs.path.isAbsolute(visualstudio_folder_path)) return error.PathNotFound;
|
|
// enumerate folders that contain `privateregistry.bin`, looking for all versions
|
|
// f.i. %localappdata%\Microsoft\VisualStudio\17.0_9e9cbb98\
|
|
var visualstudio_folder = std.fs.openIterableDirAbsolute(visualstudio_folder_path, .{}) catch return error.PathNotFound;
|
|
defer visualstudio_folder.close();
|
|
|
|
var iterator = visualstudio_folder.iterate();
|
|
const versions = iterateAndFilterBySemVer(&iterator, allocator, null) catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.VersionNotFound => return error.PathNotFound,
|
|
};
|
|
break :vs_versions versions;
|
|
};
|
|
defer {
|
|
for (vs_versions) |vs_version| allocator.free(vs_version);
|
|
allocator.free(vs_versions);
|
|
}
|
|
var config_subkey_buf: [RegistryUtf16Le.key_name_max_len * 2]u8 = undefined;
|
|
const source_directories: []const u8 = source_directories: for (vs_versions) |vs_version| {
|
|
const privateregistry_absolute_path = std.fs.path.join(allocator, &.{ visualstudio_folder_path, vs_version, "privateregistry.bin" }) catch continue;
|
|
defer allocator.free(privateregistry_absolute_path);
|
|
if (!std.fs.path.isAbsolute(privateregistry_absolute_path)) continue;
|
|
|
|
const visualstudio_registry = RegistryUtf8.loadFromPath(privateregistry_absolute_path) catch continue;
|
|
defer visualstudio_registry.closeKey();
|
|
|
|
const config_subkey = std.fmt.bufPrint(config_subkey_buf[0..], "Software\\Microsoft\\VisualStudio\\{s}_Config", .{vs_version}) catch unreachable;
|
|
|
|
var source_directories_value = visualstudio_registry.getString(allocator, config_subkey, "Source Directories") catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => continue,
|
|
};
|
|
if (source_directories_value.len > (std.fs.MAX_PATH_BYTES * 30)) { // note(bratishkaerik): guessing from the fact that on my computer it has 15 pathes and at least some of them are not of max length
|
|
allocator.free(source_directories_value);
|
|
continue;
|
|
}
|
|
|
|
break :source_directories source_directories_value;
|
|
} else return error.PathNotFound;
|
|
defer allocator.free(source_directories);
|
|
|
|
var source_directories_splitted = std.mem.splitScalar(u8, source_directories, ';');
|
|
|
|
const msvc_dir: []const u8 = msvc_dir: {
|
|
var msvc_include_dir_maybe_with_trailing_slash = try allocator.dupe(u8, source_directories_splitted.first());
|
|
|
|
if (msvc_include_dir_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(msvc_include_dir_maybe_with_trailing_slash)) {
|
|
allocator.free(msvc_include_dir_maybe_with_trailing_slash);
|
|
return error.PathNotFound;
|
|
}
|
|
|
|
var msvc_dir = std.ArrayList(u8).fromOwnedSlice(allocator, msvc_include_dir_maybe_with_trailing_slash);
|
|
errdefer msvc_dir.deinit();
|
|
|
|
// String might contain trailing slash, so trim it here
|
|
if (msvc_dir.items.len > "C:\\".len and msvc_dir.getLast() == '\\') _ = msvc_dir.pop();
|
|
|
|
// Remove `\include` at the end of path
|
|
if (std.mem.endsWith(u8, msvc_dir.items, "\\include")) {
|
|
msvc_dir.shrinkRetainingCapacity(msvc_dir.items.len - "\\include".len);
|
|
}
|
|
|
|
const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) {
|
|
.x86 => "x86",
|
|
.x86_64 => "x64",
|
|
.arm, .armeb => "arm",
|
|
.aarch64 => "arm64",
|
|
else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag),
|
|
};
|
|
|
|
try msvc_dir.appendSlice(folder_with_arch);
|
|
const msvc_dir_with_arch = try msvc_dir.toOwnedSlice();
|
|
break :msvc_dir msvc_dir_with_arch;
|
|
};
|
|
errdefer allocator.free(msvc_dir);
|
|
|
|
if (!verifyLibDir(msvc_dir)) {
|
|
return error.PathNotFound;
|
|
}
|
|
|
|
return msvc_dir;
|
|
}
|
|
|
|
fn findViaVs7Key(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 {
|
|
var base_path: std.ArrayList(u8) = base_path: {
|
|
try_env: {
|
|
var env_map = std.process.getEnvMap(allocator) catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => break :try_env,
|
|
};
|
|
defer env_map.deinit();
|
|
|
|
if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| {
|
|
if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env;
|
|
if (!std.fs.path.isAbsolute(VS140COMNTOOLS)) break :try_env;
|
|
var list = std.ArrayList(u8).init(allocator);
|
|
errdefer list.deinit();
|
|
|
|
try list.appendSlice(VS140COMNTOOLS); // C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools
|
|
// String might contain trailing slash, so trim it here
|
|
if (list.items.len > "C:\\".len and list.getLast() == '\\') _ = list.pop();
|
|
list.shrinkRetainingCapacity(list.items.len - "\\Common7\\Tools".len); // C:\Program Files (x86)\Microsoft Visual Studio 14.0
|
|
break :base_path list;
|
|
}
|
|
}
|
|
|
|
const vs7_key = RegistryUtf8.openKey("SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound;
|
|
defer vs7_key.closeKey();
|
|
try_vs7_key: {
|
|
var path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => break :try_vs7_key,
|
|
};
|
|
|
|
if (path_maybe_with_trailing_slash.len > std.fs.MAX_PATH_BYTES or !std.fs.path.isAbsolute(path_maybe_with_trailing_slash)) {
|
|
allocator.free(path_maybe_with_trailing_slash);
|
|
break :try_vs7_key;
|
|
}
|
|
|
|
var path = std.ArrayList(u8).fromOwnedSlice(allocator, path_maybe_with_trailing_slash);
|
|
errdefer path.deinit();
|
|
|
|
// String might contain trailing slash, so trim it here
|
|
if (path.items.len > "C:\\".len and path.getLast() == '\\') _ = path.pop();
|
|
break :base_path path;
|
|
}
|
|
return error.PathNotFound;
|
|
};
|
|
errdefer base_path.deinit();
|
|
|
|
const folder_with_arch = "\\VC\\lib\\" ++ comptime switch (builtin.target.cpu.arch) {
|
|
.x86 => "", //x86 is in the root of the Lib folder
|
|
.x86_64 => "amd64",
|
|
.arm, .armeb => "arm",
|
|
.aarch64 => "arm64",
|
|
else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag),
|
|
};
|
|
try base_path.appendSlice(folder_with_arch);
|
|
|
|
if (!verifyLibDir(base_path.items)) {
|
|
return error.PathNotFound;
|
|
}
|
|
|
|
const full_path = try base_path.toOwnedSlice();
|
|
return full_path;
|
|
}
|
|
|
|
fn verifyLibDir(lib_dir_path: []const u8) bool {
|
|
std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*`
|
|
|
|
var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false;
|
|
defer dir.close();
|
|
|
|
const stat = dir.statFile("vcruntime.lib") catch return false;
|
|
if (stat.kind != .file)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Find path to MSVC's `lib/` directory.
|
|
/// Caller owns the result.
|
|
pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
|
|
const full_path = MsvcLibDir.findViaCOM(allocator) catch |err1| switch (err1) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.PathNotFound => MsvcLibDir.findViaRegistry(allocator) catch |err2| switch (err2) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err3| switch (err3) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.PathNotFound => return error.MsvcLibDirNotFound,
|
|
},
|
|
},
|
|
};
|
|
errdefer allocator.free(full_path);
|
|
|
|
return full_path;
|
|
}
|
|
};
|
|
|
|
const IUnknown = extern struct {
|
|
vtable: *VTable(IUnknown),
|
|
|
|
const IID_Value = windows.GUID.parse("{00000000-0000-0000-c000-000000000046}");
|
|
pub const IID = &IID_Value;
|
|
|
|
pub fn VTable(comptime T: type) type {
|
|
return extern struct {
|
|
QueryInterface: *const fn (
|
|
self: *T,
|
|
riid: ?*const windows.GUID,
|
|
ppvObject: ?*?*anyopaque,
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
AddRef: *const fn (
|
|
self: *T,
|
|
) callconv(windows.WINAPI) u32,
|
|
Release: *const fn (
|
|
self: *T,
|
|
) callconv(windows.WINAPI) u32,
|
|
};
|
|
}
|
|
};
|
|
|
|
const ISetupConfiguration = extern struct {
|
|
vtable: *extern struct {
|
|
unknown: IUnknown.VTable(ISetupConfiguration),
|
|
setup_configuration: VTable(ISetupConfiguration),
|
|
},
|
|
|
|
const IID_Value = windows.GUID.parse("{42843719-db4c-46c2-8e7c-64f1816efd5b}");
|
|
pub const IID = &IID_Value;
|
|
|
|
pub fn VTable(comptime T: type) type {
|
|
return extern struct {
|
|
EnumInstances: *const fn (
|
|
self: *T,
|
|
ppEnumInstances: **IEnumSetupInstances, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetInstanceForCurrentProcess: *const fn (
|
|
self: *T,
|
|
ppInstance: **ISetupInstance, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetInstanceForPath: *const fn (
|
|
self: *T,
|
|
wzPath: windows.LPCWSTR, // [in]
|
|
ppInstance: **ISetupInstance, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
};
|
|
}
|
|
};
|
|
|
|
const IEnumSetupInstances = extern struct {
|
|
vtable: *extern struct {
|
|
unknown: IUnknown.VTable(IEnumSetupInstances),
|
|
enum_setup_instances: VTable(IEnumSetupInstances),
|
|
},
|
|
|
|
const IID_Value = windows.GUID.parse("{6380bcff-41d3-4b2e-8b2e-bf8a6810c848}");
|
|
pub const IID = &IID_Value;
|
|
|
|
pub fn VTable(comptime T: type) type {
|
|
return extern struct {
|
|
/// Returns S_OK if the number of elements were fetched,
|
|
/// S_FALSE if nothing was fetched (at end of enumeration),
|
|
/// E_INVALIDARG if `celt` is greater than 1 and pceltFetched is NULL,
|
|
/// or E_OUTOFMEMORY if an ISetupInstance could not be allocated.
|
|
Next: *const fn (
|
|
self: *T,
|
|
/// The number of product instances to retrieve
|
|
celt: windows.ULONG, // [in]
|
|
/// A pointer to an array of ISetupInstance
|
|
rgelt: **ISetupInstance, // [out]
|
|
/// A pointer to the number of product instances retrieved.
|
|
/// If `celt` is 1 this paramter may be NULL
|
|
pceltFetched: ?*windows.ULONG,
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
Skip: *const fn (
|
|
self: *T,
|
|
/// The number of product instances to skip
|
|
celt: windows.ULONG, // [in]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
Reset: *const fn (
|
|
self: *T,
|
|
) callconv(windows.WINAPI) void,
|
|
Clone: *const fn (
|
|
self: *T,
|
|
ppenum: **IEnumSetupInstances, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
};
|
|
}
|
|
};
|
|
|
|
const ISetupInstance = extern struct {
|
|
vtable: *extern struct {
|
|
unknown: IUnknown.VTable(ISetupInstance),
|
|
setup_instance: VTable(ISetupInstance),
|
|
},
|
|
|
|
const IID_Value = windows.GUID.parse("{b41463c3-8866-43b5-bc33-2b0676f7f42e}");
|
|
pub const IID = &IID_Value;
|
|
|
|
pub fn VTable(comptime T: type) type {
|
|
return extern struct {
|
|
GetInstanceId: *const fn (
|
|
self: *T,
|
|
pbstrInstanceId: *windows.BSTR, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetInstallDate: *const fn (
|
|
self: *T,
|
|
pInstallDate: *windows.FILETIME, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetInstallationName: *const fn (
|
|
self: *T,
|
|
pbstrInstallationName: *windows.BSTR, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetInstallationPath: *const fn (
|
|
self: *T,
|
|
pbstrInstallationPath: *windows.BSTR, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetInstallationVersion: *const fn (
|
|
self: *T,
|
|
pbstrInstallationVersion: *windows.BSTR, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
GetDisplayName: *anyopaque,
|
|
GetDescription: *anyopaque,
|
|
ResolvePath: *anyopaque,
|
|
};
|
|
}
|
|
};
|
|
|
|
const ISetupHelper = extern struct {
|
|
vtable: *extern struct {
|
|
unknown: IUnknown.VTable(ISetupHelper),
|
|
setup_helper: VTable(ISetupHelper),
|
|
},
|
|
|
|
const IID_Value = windows.GUID.parse("{42b21b78-6192-463e-87bf-d577838f1d5c}");
|
|
pub const IID = &IID_Value;
|
|
|
|
pub fn VTable(comptime T: type) type {
|
|
return extern struct {
|
|
ParseVersion: *const fn (
|
|
self: *T,
|
|
pwszVersion: windows.BSTR, // [in]
|
|
pullVersion: *windows.ULONGLONG, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT,
|
|
ParseVersionRange: *anyopaque,
|
|
};
|
|
}
|
|
};
|
|
|
|
const SetupConfiguration = extern struct {
|
|
const CLSID_Value = windows.GUID.parse("{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}");
|
|
pub const CLSID = &CLSID_Value;
|
|
};
|
|
|
|
extern "ole32" fn CoCreateInstance(
|
|
rclsid: ?*const windows.GUID, // [in]
|
|
pUnkOuter: ?*IUnknown, // [in]
|
|
dwClsContext: windows.DWORD, // [in]
|
|
riid: ?*const windows.GUID, // [in]
|
|
ppv: **anyopaque, // [out]
|
|
) callconv(windows.WINAPI) windows.HRESULT;
|
|
|
|
extern "oleaut32" fn SysFreeString(bstrString: ?windows.BSTR) callconv(windows.WINAPI) void;
|
|
|
|
const CLSCTX = struct {
|
|
const INPROC_SERVER = 0x1;
|
|
const INPROC_HANDLER = 0x2;
|
|
};
|