| ... | @@ -23,7 +23,7 @@ pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, NotFound, PathTooL | ... | @@ -23,7 +23,7 @@ pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, NotFound, PathTooL |
| 23 | if (builtin.os.tag != .windows) return error.NotFound; | 23 | if (builtin.os.tag != .windows) return error.NotFound; |
| 24 | | 24 | |
| 25 | //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed | 25 | //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed |
| 26 | const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, windows_kits_reg_key) catch |err| switch (err) { | 26 | const roots_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, windows_kits_reg_key, .{ .wow64_32 = true }) catch |err| switch (err) { |
| 27 | error.KeyNotFound => return error.NotFound, | 27 | error.KeyNotFound => return error.NotFound, |
| 28 | }; | 28 | }; |
| 29 | defer roots_key.closeKey(); | 29 | defer roots_key.closeKey(); |
| ... | @@ -137,11 +137,17 @@ fn iterateAndFilterByVersion( | ... | @@ -137,11 +137,17 @@ fn iterateAndFilterByVersion( |
| 137 | return dirs.toOwnedSlice(); | 137 | return dirs.toOwnedSlice(); |
| 138 | } | 138 | } |
| 139 | | 139 | |
| | 140 | const OpenOptions = struct { |
| | 141 | /// Sets the KEY_WOW64_32KEY access flag. |
| | 142 | /// https://learn.microsoft.com/en-us/windows/win32/winprog64/accessing-an-alternate-registry-view |
| | 143 | wow64_32: bool = false, |
| | 144 | }; |
| | 145 | |
| 140 | const RegistryWtf8 = struct { | 146 | const RegistryWtf8 = struct { |
| 141 | key: windows.HKEY, | 147 | key: windows.HKEY, |
| 142 | | 148 | |
| 143 | /// Assert that `key` is valid WTF-8 string | 149 | /// Assert that `key` is valid WTF-8 string |
| 144 | pub fn openKey(hkey: windows.HKEY, key: []const u8) error{KeyNotFound}!RegistryWtf8 { | 150 | pub fn openKey(hkey: windows.HKEY, key: []const u8, options: OpenOptions) error{KeyNotFound}!RegistryWtf8 { |
| 145 | const key_wtf16le: [:0]const u16 = key_wtf16le: { | 151 | const key_wtf16le: [:0]const u16 = key_wtf16le: { |
| 146 | var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; | 152 | var key_wtf16le_buf: [RegistryWtf16Le.key_name_max_len]u16 = undefined; |
| 147 | const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) { | 153 | const key_wtf16le_len: usize = std.unicode.wtf8ToWtf16Le(key_wtf16le_buf[0..], key) catch |err| switch (err) { |
| ... | @@ -151,7 +157,7 @@ const RegistryWtf8 = struct { | ... | @@ -151,7 +157,7 @@ const RegistryWtf8 = struct { |
| 151 | break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0]; | 157 | break :key_wtf16le key_wtf16le_buf[0..key_wtf16le_len :0]; |
| 152 | }; | 158 | }; |
| 153 | | 159 | |
| 154 | const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le); | 160 | const registry_wtf16le = try RegistryWtf16Le.openKey(hkey, key_wtf16le, options); |
| 155 | return .{ .key = registry_wtf16le.key }; | 161 | return .{ .key = registry_wtf16le.key }; |
| 156 | } | 162 | } |
| 157 | | 163 | |
| ... | @@ -239,15 +245,17 @@ const RegistryWtf16Le = struct { | ... | @@ -239,15 +245,17 @@ const RegistryWtf16Le = struct { |
| 239 | pub const value_name_max_len = 16_383; | 245 | pub const value_name_max_len = 16_383; |
| 240 | | 246 | |
| 241 | /// Under HKEY_LOCAL_MACHINE with flags: | 247 | /// Under HKEY_LOCAL_MACHINE with flags: |
| 242 | /// KEY_QUERY_VALUE, KEY_WOW64_32KEY, and KEY_ENUMERATE_SUB_KEYS. | 248 | /// KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, optionally KEY_WOW64_32KEY. |
| 243 | /// After finishing work, call `closeKey`. | 249 | /// After finishing work, call `closeKey`. |
| 244 | fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16) error{KeyNotFound}!RegistryWtf16Le { | 250 | fn openKey(hkey: windows.HKEY, key_wtf16le: [:0]const u16, options: OpenOptions) error{KeyNotFound}!RegistryWtf16Le { |
| 245 | var key: windows.HKEY = undefined; | 251 | var key: windows.HKEY = undefined; |
| | 252 | var access: windows.REGSAM = windows.KEY_QUERY_VALUE | windows.KEY_ENUMERATE_SUB_KEYS; |
| | 253 | if (options.wow64_32) access |= windows.KEY_WOW64_32KEY; |
| 246 | const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW( | 254 | const return_code_int: windows.HRESULT = windows.advapi32.RegOpenKeyExW( |
| 247 | hkey, | 255 | hkey, |
| 248 | key_wtf16le, | 256 | key_wtf16le, |
| 249 | 0, | 257 | 0, |
| 250 | windows.KEY_QUERY_VALUE | windows.KEY_WOW64_32KEY | windows.KEY_ENUMERATE_SUB_KEYS, | 258 | access, |
| 251 | &key, | 259 | &key, |
| 252 | ); | 260 | ); |
| 253 | const return_code: windows.Win32Error = @enumFromInt(return_code_int); | 261 | const return_code: windows.Win32Error = @enumFromInt(return_code_int); |
| ... | @@ -484,13 +492,14 @@ pub const Installation = struct { | ... | @@ -484,13 +492,14 @@ pub const Installation = struct { |
| 484 | version_key_name: []const u8, | 492 | version_key_name: []const u8, |
| 485 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { | 493 | ) error{ OutOfMemory, InstallationNotFound, PathTooLong, VersionTooLong }!Installation { |
| 486 | var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined; | 494 | var key_name_buf: [RegistryWtf16Le.key_name_max_len]u8 = undefined; |
| 487 | const key = key: for ([_][]const u8{ "\\Wow6432Node", "" }) |wow6432node| { | 495 | const key_name = std.fmt.bufPrint( |
| | 496 | &key_name_buf, |
| | 497 | "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\{s}", |
| | 498 | .{version_key_name}, |
| | 499 | ) catch unreachable; |
| | 500 | const key = key: for ([_]bool{ true, false }) |wow6432node| { |
| 488 | for ([_]windows.HKEY{ windows.HKEY_LOCAL_MACHINE, windows.HKEY_CURRENT_USER }) |hkey| { | 501 | for ([_]windows.HKEY{ windows.HKEY_LOCAL_MACHINE, windows.HKEY_CURRENT_USER }) |hkey| { |
| 489 | break :key RegistryWtf8.openKey(hkey, std.fmt.bufPrint( | 502 | break :key RegistryWtf8.openKey(hkey, key_name, .{ .wow64_32 = wow6432node }) catch |err| switch (err) { |
| 490 | &key_name_buf, | | |
| 491 | "SOFTWARE{s}\\Microsoft\\Microsoft SDKs\\Windows\\{s}", | | |
| 492 | .{ wow6432node, version_key_name }, | | |
| 493 | ) catch unreachable) catch |err| switch (err) { | | |
| 494 | error.KeyNotFound => return error.InstallationNotFound, | 503 | error.KeyNotFound => return error.InstallationNotFound, |
| 495 | }; | 504 | }; |
| 496 | } | 505 | } |
| ... | @@ -563,6 +572,7 @@ pub const Installation = struct { | ... | @@ -563,6 +572,7 @@ pub const Installation = struct { |
| 563 | const options_key = RegistryWtf8.openKey( | 572 | const options_key = RegistryWtf8.openKey( |
| 564 | windows.HKEY_LOCAL_MACHINE, | 573 | windows.HKEY_LOCAL_MACHINE, |
| 565 | reg_query_as_wtf8, | 574 | reg_query_as_wtf8, |
| | 575 | .{ .wow64_32 = true }, |
| 566 | ) catch |err| switch (err) { | 576 | ) catch |err| switch (err) { |
| 567 | error.KeyNotFound => return false, | 577 | error.KeyNotFound => return false, |
| 568 | }; | 578 | }; |
| ... | @@ -587,9 +597,34 @@ pub const Installation = struct { | ... | @@ -587,9 +597,34 @@ pub const Installation = struct { |
| 587 | }; | 597 | }; |
| 588 | | 598 | |
| 589 | const MsvcLibDir = struct { | 599 | const MsvcLibDir = struct { |
| | 600 | fn findInstancesDirViaSetup(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { |
| | 601 | const vs_setup_key_path = "SOFTWARE\\Microsoft\\VisualStudio\\Setup"; |
| | 602 | const vs_setup_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, vs_setup_key_path, .{}) catch |err| switch (err) { |
| | 603 | error.KeyNotFound => return error.PathNotFound, |
| | 604 | }; |
| | 605 | defer vs_setup_key.closeKey(); |
| | 606 | |
| | 607 | const packages_path = vs_setup_key.getString(allocator, "", "CachePath") catch |err| switch (err) { |
| | 608 | error.NotAString, |
| | 609 | error.ValueNameNotFound, |
| | 610 | error.StringNotFound, |
| | 611 | => return error.PathNotFound, |
| | 612 | |
| | 613 | error.OutOfMemory => return error.OutOfMemory, |
| | 614 | }; |
| | 615 | defer allocator.free(packages_path); |
| | 616 | |
| | 617 | if (!std.fs.path.isAbsolute(packages_path)) return error.PathNotFound; |
| | 618 | |
| | 619 | const instances_path = try std.fs.path.join(allocator, &.{ packages_path, "_Instances" }); |
| | 620 | defer allocator.free(instances_path); |
| | 621 | |
| | 622 | return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return error.PathNotFound; |
| | 623 | } |
| | 624 | |
| 590 | fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { | 625 | fn findInstancesDirViaCLSID(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { |
| 591 | const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; | 626 | const setup_configuration_clsid = "{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"; |
| 592 | const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid) catch |err| switch (err) { | 627 | const setup_config_key = RegistryWtf8.openKey(windows.HKEY_CLASSES_ROOT, "CLSID\\" ++ setup_configuration_clsid, .{}) catch |err| switch (err) { |
| 593 | error.KeyNotFound => return error.PathNotFound, | 628 | error.KeyNotFound => return error.PathNotFound, |
| 594 | }; | 629 | }; |
| 595 | defer setup_config_key.closeKey(); | 630 | defer setup_config_key.closeKey(); |
| ... | @@ -604,6 +639,8 @@ const MsvcLibDir = struct { | ... | @@ -604,6 +639,8 @@ const MsvcLibDir = struct { |
| 604 | }; | 639 | }; |
| 605 | defer allocator.free(dll_path); | 640 | defer allocator.free(dll_path); |
| 606 | | 641 | |
| | 642 | if (!std.fs.path.isAbsolute(dll_path)) return error.PathNotFound; |
| | 643 | |
| 607 | var path_it = std.fs.path.componentIterator(dll_path) catch return error.PathNotFound; | 644 | var path_it = std.fs.path.componentIterator(dll_path) catch return error.PathNotFound; |
| 608 | // the .dll filename | 645 | // the .dll filename |
| 609 | _ = path_it.last(); | 646 | _ = path_it.last(); |
| ... | @@ -622,22 +659,40 @@ const MsvcLibDir = struct { | ... | @@ -622,22 +659,40 @@ const MsvcLibDir = struct { |
| 622 | } | 659 | } |
| 623 | | 660 | |
| 624 | fn findInstancesDir(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { | 661 | fn findInstancesDir(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }!std.fs.Dir { |
| 625 | // First try to get the path from the .dll that would have been | 662 | // First, try getting the packages cache path from the registry. |
| | 663 | // This only seems to exist when the path is different from the default. |
| | 664 | method1: { |
| | 665 | return findInstancesDirViaSetup(allocator) catch |err| switch (err) { |
| | 666 | error.OutOfMemory => |e| return e, |
| | 667 | error.PathNotFound => break :method1, |
| | 668 | }; |
| | 669 | } |
| | 670 | // Otherwise, try to get the path from the .dll that would have been |
| 626 | // loaded via COM for SetupConfiguration. | 671 | // loaded via COM for SetupConfiguration. |
| 627 | return findInstancesDirViaCLSID(allocator) catch |orig_err| { | 672 | method2: { |
| 628 | // If that can't be found, fall back to manually appending | 673 | return findInstancesDirViaCLSID(allocator) catch |err| switch (err) { |
| 629 | // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% | 674 | error.OutOfMemory => |e| return e, |
| | 675 | error.PathNotFound => break :method2, |
| | 676 | }; |
| | 677 | } |
| | 678 | // If that can't be found, fall back to manually appending |
| | 679 | // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% |
| | 680 | method3: { |
| 630 | const program_data = std.process.getEnvVarOwned(allocator, "PROGRAMDATA") catch |err| switch (err) { | 681 | const program_data = std.process.getEnvVarOwned(allocator, "PROGRAMDATA") catch |err| switch (err) { |
| 631 | error.OutOfMemory => |e| return e, | 682 | error.OutOfMemory => |e| return e, |
| 632 | else => return orig_err, | 683 | error.InvalidWtf8 => unreachable, |
| | 684 | error.EnvironmentVariableNotFound => break :method3, |
| 633 | }; | 685 | }; |
| 634 | defer allocator.free(program_data); | 686 | defer allocator.free(program_data); |
| 635 | | 687 | |
| | 688 | if (!std.fs.path.isAbsolute(program_data)) break :method3; |
| | 689 | |
| 636 | const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); | 690 | const instances_path = try std.fs.path.join(allocator, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); |
| 637 | defer allocator.free(instances_path); | 691 | defer allocator.free(instances_path); |
| 638 | | 692 | |
| 639 | return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch return orig_err; | 693 | return std.fs.openDirAbsolute(instances_path, .{ .iterate = true }) catch break :method3; |
| 640 | }; | 694 | } |
| | 695 | return error.PathNotFound; |
| 641 | } | 696 | } |
| 642 | | 697 | |
| 643 | /// Intended to be equivalent to `ISetupHelper.ParseVersion` | 698 | /// Intended to be equivalent to `ISetupHelper.ParseVersion` |
| ... | @@ -896,7 +951,7 @@ const MsvcLibDir = struct { | ... | @@ -896,7 +951,7 @@ const MsvcLibDir = struct { |
| 896 | } | 951 | } |
| 897 | } | 952 | } |
| 898 | | 953 | |
| 899 | const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7") catch return error.PathNotFound; | 954 | const vs7_key = RegistryWtf8.openKey(windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", .{ .wow64_32 = true }) catch return error.PathNotFound; |
| 900 | defer vs7_key.closeKey(); | 955 | defer vs7_key.closeKey(); |
| 901 | try_vs7_key: { | 956 | try_vs7_key: { |
| 902 | const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) { | 957 | const path_maybe_with_trailing_slash = vs7_key.getString(allocator, "", "14.0") catch |err| switch (err) { |