structstd.zig.WindowsSdk.Installation[src]

Fields

path: []const u8
version: []const u8

Source Code

Source code
pub const Installation = struct {
    path: []const u8,
    version: []const u8,

    /// Find path and version of Windows SDK.
    /// Caller owns the result's fields.
    /// After finishing work, call `free(allocator)`.
    fn find(
        allocator: std.mem.Allocator,
        roots_key: RegistryWtf8,
        roots_subkey: []const u8,
        prefix: []const u8,
        version_key_name: []const u8,
    ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
        roots: {
            const installation = findFromRoot(allocator, roots_key, roots_subkey, prefix) catch
                break :roots;
            if (installation.isValidVersion()) return installation;
            installation.free(allocator);
        }
        {
            const installation = try findFromInstallationFolder(allocator, version_key_name);
            if (installation.isValidVersion()) return installation;
            installation.free(allocator);
        }
        return error.InstallationNotFound;
    }

    fn findFromRoot(
        allocator: std.mem.Allocator,
        roots_key: RegistryWtf8,
        roots_subkey: []const u8,
        prefix: []const u8,
    ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
        const path = path: {
            const path_maybe_with_trailing_slash = roots_key.getString(allocator, "", roots_subkey) catch |err| switch (err) {
                error.NotAString => return error.InstallationNotFound,
                error.ValueNameNotFound => return error.InstallationNotFound,
                error.StringNotFound => return error.InstallationNotFound,

                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();
            break :path try path.toOwnedSlice();
        };
        errdefer allocator.free(path);

        const version = version: {
            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.InstallationNotFound;

            // enumerate files in sdk path looking for latest version
            var sdk_lib_dir = std.fs.openDirAbsolute(sdk_lib_dir_path, .{
                .iterate = true,
            }) catch |err| switch (err) {
                error.NameTooLong => return error.PathTooLong,
                else => return error.InstallationNotFound,
            };
            defer sdk_lib_dir.close();

            var iterator = sdk_lib_dir.iterate();
            const versions = try iterateAndFilterByVersion(&iterator, allocator, prefix);
            if (versions.len == 0) return error.InstallationNotFound;
            defer {
                for (versions[1..]) |version| allocator.free(version);
                allocator.free(versions);
            }
            break :version versions[0];
        };
        errdefer allocator.free(version);

        return .{ .path = path, .version = version };
    }

    fn findFromInstallationFolder(
        allocator: std.mem.Allocator,
        version_key_name: []const u8,
    ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation {
        var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined;
        const key_name = std.fmt.bufPrint(
            &key_name_buf,
            "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\{s}",
            .{version_key_name},
        ) catch unreachable;
        const key = key: for ([_]bool{ true, false }) |wow6432node| {
            for ([_]windows.HKEY{ windows.HKEY_LOCAL_MACHINE, windows.HKEY_CURRENT_USER }) |hkey| {
                break :key RegistryWtf8.openKey(hkey, key_name, .{ .wow64_32 = wow6432node }) catch |err| switch (err) {
                    error.KeyNotFound => return error.InstallationNotFound,
                };
            }
        } else return error.InstallationNotFound;
        defer key.closeKey();

        const path: []const u8 = path: {
            const path_maybe_with_trailing_slash = key.getString(allocator, "", "InstallationFolder") catch |err| switch (err) {
                error.NotAString => return error.InstallationNotFound,
                error.ValueNameNotFound => return error.InstallationNotFound,
                error.StringNotFound => return error.InstallationNotFound,

                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 :path path_without_trailing_slash;
        };
        errdefer allocator.free(path);

        const version: []const u8 = version: {

            // note(dimenus): Microsoft doesn't include the .0 in the ProductVersion key....
            const version_without_0 = key.getString(allocator, "", "ProductVersion") catch |err| switch (err) {
                error.NotAString => return error.InstallationNotFound,
                error.ValueNameNotFound => return error.InstallationNotFound,
                error.StringNotFound => return error.InstallationNotFound,

                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 :version version_with_0;
        };
        errdefer allocator.free(version);

        return .{ .path = path, .version = version };
    }

    /// Check whether this version is enumerated in registry.
    fn isValidVersion(installation: Installation) bool {
        var buf: [std.fs.max_path_bytes]u8 = undefined;
        const reg_query_as_wtf8 = std.fmt.bufPrint(buf[0..], "{s}\\{s}\\Installed Options", .{
            windows_kits_reg_key,
            installation.version,
        }) catch |err| switch (err) {
            error.NoSpaceLeft => return false,
        };

        const options_key = RegistryWtf8.openKey(
            windows.HKEY_LOCAL_MACHINE,
            reg_query_as_wtf8,
            .{ .wow64_32 = true },
        ) catch |err| switch (err) {
            error.KeyNotFound => return false,
        };
        defer options_key.closeKey();

        const option_name = comptime switch (builtin.target.cpu.arch) {
            .thumb => "OptionId.DesktopCPParm",
            .aarch64 => "OptionId.DesktopCPParm64",
            .x86 => "OptionId.DesktopCPPx86",
            .x86_64 => "OptionId.DesktopCPPx64",
            else => |tag| @compileError("Windows SDK cannot be detected on architecture " ++ tag),
        };

        const reg_value = options_key.getDword("", option_name) catch return false;
        return (reg_value == 1);
    }

    fn free(install: Installation, allocator: std.mem.Allocator) void {
        allocator.free(install.path);
        allocator.free(install.version);
    }
}