| ... | @@ -550,7 +550,6 @@ pub const ZigWindowsSDK = struct { | ... | @@ -550,7 +550,6 @@ pub const ZigWindowsSDK = struct { |
| 550 | | 550 | |
| 551 | const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) { | 551 | const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) { |
| 552 | error.MsvcLibDirNotFound => null, | 552 | error.MsvcLibDirNotFound => null, |
| 553 | error.PathTooLong => null, | | |
| 554 | error.OutOfMemory => return error.OutOfMemory, | 553 | error.OutOfMemory => return error.OutOfMemory, |
| 555 | }; | 554 | }; |
| 556 | errdefer allocator.free(msvc_lib_dir); | 555 | errdefer allocator.free(msvc_lib_dir); |
| ... | @@ -576,6 +575,112 @@ pub const ZigWindowsSDK = struct { | ... | @@ -576,6 +575,112 @@ pub const ZigWindowsSDK = struct { |
| 576 | }; | 575 | }; |
| 577 | | 576 | |
| 578 | const MsvcLibDir = struct { | 577 | const MsvcLibDir = struct { |
| | 578 | // https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.setup.configuration |
| | 579 | fn findViaCOM(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { |
| | 580 | switch (windows.ole32.CoInitializeEx(null, windows.COINIT.MULTITHREADED)) { |
| | 581 | windows.S_OK, windows.S_FALSE => {}, |
| | 582 | windows.E_OUTOFMEMORY => return error.OutOfMemory, |
| | 583 | else => return error.PathNotFound, |
| | 584 | } |
| | 585 | // > To close the COM library gracefully on a thread, each successful |
| | 586 | // > call to CoInitialize or CoInitializeEx, including any call that |
| | 587 | // > returns S_FALSE, must be balanced by a corresponding call to CoUninitialize. |
| | 588 | // https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex |
| | 589 | defer windows.ole32.CoUninitialize(); |
| | 590 | |
| | 591 | var setup_config: *ISetupConfiguration = undefined; |
| | 592 | switch (CoCreateInstance( |
| | 593 | SetupConfiguration.CLSID, |
| | 594 | null, |
| | 595 | CLSCTX.INPROC_SERVER | CLSCTX.INPROC_HANDLER, |
| | 596 | ISetupConfiguration.IID, |
| | 597 | @ptrCast(&setup_config), |
| | 598 | )) { |
| | 599 | windows.S_OK => {}, |
| | 600 | windows.E_OUTOFMEMORY => return error.OutOfMemory, |
| | 601 | else => return error.PathNotFound, |
| | 602 | } |
| | 603 | defer _ = setup_config.vtable.unknown.Release(setup_config); |
| | 604 | |
| | 605 | var all_instances: *IEnumSetupInstances = undefined; |
| | 606 | switch (setup_config.vtable.setup_configuration.EnumInstances(setup_config, &all_instances)) { |
| | 607 | windows.S_OK => {}, |
| | 608 | windows.E_OUTOFMEMORY => return error.OutOfMemory, |
| | 609 | else => return error.PathNotFound, |
| | 610 | } |
| | 611 | defer _ = all_instances.vtable.unknown.Release(all_instances); |
| | 612 | |
| | 613 | while (true) { |
| | 614 | var cur: *ISetupInstance = undefined; |
| | 615 | switch (all_instances.vtable.enum_setup_instances.Next(all_instances, 1, &cur, null)) { |
| | 616 | windows.S_OK => {}, |
| | 617 | windows.S_FALSE => break, |
| | 618 | windows.E_OUTOFMEMORY => return error.OutOfMemory, |
| | 619 | else => return error.PathNotFound, |
| | 620 | } |
| | 621 | defer _ = cur.vtable.unknown.Release(cur); |
| | 622 | |
| | 623 | var installation_path_bstr: windows.BSTR = undefined; |
| | 624 | switch (cur.vtable.setup_instance.GetInstallationPath(cur, &installation_path_bstr)) { |
| | 625 | windows.S_OK => {}, |
| | 626 | windows.E_OUTOFMEMORY => return error.OutOfMemory, |
| | 627 | else => continue, |
| | 628 | } |
| | 629 | defer SysFreeString(installation_path_bstr); |
| | 630 | |
| | 631 | const installation_path_w = std.mem.span(installation_path_bstr); |
| | 632 | const lib_dir_path = libDirFromInstallationPath(allocator, installation_path_w) catch |err| switch (err) { |
| | 633 | error.OutOfMemory => |e| return e, |
| | 634 | error.PathNotFound => continue, |
| | 635 | }; |
| | 636 | errdefer allocator.free(lib_dir_path); |
| | 637 | |
| | 638 | return lib_dir_path; |
| | 639 | } |
| | 640 | return error.PathNotFound; |
| | 641 | } |
| | 642 | |
| | 643 | fn libDirFromInstallationPath(allocator: std.mem.Allocator, installation_path_w: []const u16) error{ OutOfMemory, PathNotFound }![]const u8 { |
| | 644 | // Each UTF-16LE code unit may be expanded to 3 UTF-8 bytes. |
| | 645 | var lib_dir_buf = try std.ArrayList(u8).initCapacity(allocator, installation_path_w.len * 3); |
| | 646 | errdefer lib_dir_buf.deinit(); |
| | 647 | |
| | 648 | lib_dir_buf.items.len = std.unicode.utf16leToUtf8(lib_dir_buf.unusedCapacitySlice(), installation_path_w) catch { |
| | 649 | return error.PathNotFound; |
| | 650 | }; |
| | 651 | |
| | 652 | if (!std.fs.path.isSep(lib_dir_buf.getLast())) { |
| | 653 | try lib_dir_buf.append('\\'); |
| | 654 | } |
| | 655 | const installation_path_with_trailing_sep_len = lib_dir_buf.items.len; |
| | 656 | |
| | 657 | try lib_dir_buf.appendSlice("VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); |
| | 658 | var default_tools_version_buf: [512]u8 = undefined; |
| | 659 | const default_tools_version_contents = std.fs.cwd().readFile(lib_dir_buf.items, &default_tools_version_buf) catch { |
| | 660 | return error.PathNotFound; |
| | 661 | }; |
| | 662 | var tokenizer = std.mem.tokenizeAny(u8, default_tools_version_contents, " \r\n"); |
| | 663 | const default_tools_version = tokenizer.next() orelse return error.PathNotFound; |
| | 664 | |
| | 665 | lib_dir_buf.shrinkRetainingCapacity(installation_path_with_trailing_sep_len); |
| | 666 | try lib_dir_buf.appendSlice("VC\\Tools\\MSVC\\"); |
| | 667 | try lib_dir_buf.appendSlice(default_tools_version); |
| | 668 | const folder_with_arch = "\\Lib\\" ++ comptime switch (builtin.target.cpu.arch) { |
| | 669 | .x86 => "x86", |
| | 670 | .x86_64 => "x64", |
| | 671 | .arm, .armeb => "arm", |
| | 672 | .aarch64 => "arm64", |
| | 673 | else => |tag| @compileError("MSVC lib dir cannot be detected on architecture " ++ tag), |
| | 674 | }; |
| | 675 | try lib_dir_buf.appendSlice(folder_with_arch); |
| | 676 | |
| | 677 | if (!verifyLibDir(lib_dir_buf.items)) { |
| | 678 | return error.PathNotFound; |
| | 679 | } |
| | 680 | |
| | 681 | return lib_dir_buf.toOwnedSlice(); |
| | 682 | } |
| | 683 | |
| 579 | // 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 | 684 | // 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 |
| 580 | fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { | 685 | fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { |
| 581 | | 686 | |
| ... | @@ -661,6 +766,10 @@ const MsvcLibDir = struct { | ... | @@ -661,6 +766,10 @@ const MsvcLibDir = struct { |
| 661 | }; | 766 | }; |
| 662 | errdefer allocator.free(msvc_dir); | 767 | errdefer allocator.free(msvc_dir); |
| 663 | | 768 | |
| | 769 | if (!verifyLibDir(msvc_dir)) { |
| | 770 | return error.PathNotFound; |
| | 771 | } |
| | 772 | |
| 664 | return msvc_dir; | 773 | return msvc_dir; |
| 665 | } | 774 | } |
| 666 | | 775 | |
| ... | @@ -720,36 +829,192 @@ const MsvcLibDir = struct { | ... | @@ -720,36 +829,192 @@ const MsvcLibDir = struct { |
| 720 | }; | 829 | }; |
| 721 | try base_path.appendSlice(folder_with_arch); | 830 | try base_path.appendSlice(folder_with_arch); |
| 722 | | 831 | |
| | 832 | if (!verifyLibDir(base_path.items)) { |
| | 833 | return error.PathNotFound; |
| | 834 | } |
| | 835 | |
| 723 | const full_path = try base_path.toOwnedSlice(); | 836 | const full_path = try base_path.toOwnedSlice(); |
| 724 | return full_path; | 837 | return full_path; |
| 725 | } | 838 | } |
| 726 | | 839 | |
| | 840 | fn verifyLibDir(lib_dir_path: []const u8) bool { |
| | 841 | std.debug.assert(std.fs.path.isAbsolute(lib_dir_path)); // should be already handled in `findVia*` |
| | 842 | |
| | 843 | var dir = std.fs.openDirAbsolute(lib_dir_path, .{}) catch return false; |
| | 844 | defer dir.close(); |
| | 845 | |
| | 846 | const stat = dir.statFile("vcruntime.lib") catch return false; |
| | 847 | if (stat.kind != .file) |
| | 848 | return false; |
| | 849 | |
| | 850 | return true; |
| | 851 | } |
| | 852 | |
| 727 | /// Find path to MSVC's `lib/` directory. | 853 | /// Find path to MSVC's `lib/` directory. |
| 728 | /// Caller owns the result. | 854 | /// Caller owns the result. |
| 729 | pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound, PathTooLong }![]const u8 { | 855 | pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { |
| 730 | const full_path = MsvcLibDir.findViaRegistry(allocator) catch |err1| switch (err1) { | 856 | const full_path = MsvcLibDir.findViaCOM(allocator) catch |err1| switch (err1) { |
| 731 | error.OutOfMemory => return error.OutOfMemory, | 857 | error.OutOfMemory => return error.OutOfMemory, |
| 732 | error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err2| switch (err2) { | 858 | error.PathNotFound => MsvcLibDir.findViaRegistry(allocator) catch |err2| switch (err2) { |
| 733 | error.OutOfMemory => return error.OutOfMemory, | 859 | error.OutOfMemory => return error.OutOfMemory, |
| 734 | error.PathNotFound => return error.MsvcLibDirNotFound, | 860 | error.PathNotFound => MsvcLibDir.findViaVs7Key(allocator) catch |err3| switch (err3) { |
| | 861 | error.OutOfMemory => return error.OutOfMemory, |
| | 862 | error.PathNotFound => return error.MsvcLibDirNotFound, |
| | 863 | }, |
| 735 | }, | 864 | }, |
| 736 | }; | 865 | }; |
| 737 | errdefer allocator.free(full_path); | 866 | errdefer allocator.free(full_path); |
| 738 | std.debug.assert(std.fs.path.isAbsolute(full_path)); // should be already handled in `findVia*` | | |
| 739 | | 867 | |
| 740 | var dir = std.fs.openDirAbsolute(full_path, .{}) catch |err| switch (err) { | 868 | return full_path; |
| 741 | error.NameTooLong => return error.PathTooLong, | 869 | } |
| 742 | else => return error.MsvcLibDirNotFound, | 870 | }; |
| | 871 | |
| | 872 | const IUnknown = extern struct { |
| | 873 | vtable: *VTable(IUnknown), |
| | 874 | |
| | 875 | const IID_Value = windows.GUID.parse("{00000000-0000-0000-c000-000000000046}"); |
| | 876 | pub const IID = &IID_Value; |
| | 877 | |
| | 878 | pub fn VTable(comptime T: type) type { |
| | 879 | return extern struct { |
| | 880 | QueryInterface: *const fn ( |
| | 881 | self: *T, |
| | 882 | riid: ?*const windows.GUID, |
| | 883 | ppvObject: ?*?*anyopaque, |
| | 884 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 885 | AddRef: *const fn ( |
| | 886 | self: *T, |
| | 887 | ) callconv(windows.WINAPI) u32, |
| | 888 | Release: *const fn ( |
| | 889 | self: *T, |
| | 890 | ) callconv(windows.WINAPI) u32, |
| 743 | }; | 891 | }; |
| 744 | defer dir.close(); | 892 | } |
| | 893 | }; |
| 745 | | 894 | |
| 746 | const stat = dir.statFile("vcruntime.lib") catch |err| switch (err) { | 895 | const ISetupConfiguration = extern struct { |
| 747 | error.NameTooLong => return error.PathTooLong, | 896 | vtable: *extern struct { |
| 748 | else => return error.MsvcLibDirNotFound, | 897 | unknown: IUnknown.VTable(ISetupConfiguration), |
| | 898 | setup_configuration: VTable(ISetupConfiguration), |
| | 899 | }, |
| | 900 | |
| | 901 | const IID_Value = windows.GUID.parse("{42843719-db4c-46c2-8e7c-64f1816efd5b}"); |
| | 902 | pub const IID = &IID_Value; |
| | 903 | |
| | 904 | pub fn VTable(comptime T: type) type { |
| | 905 | return extern struct { |
| | 906 | EnumInstances: *const fn ( |
| | 907 | self: *T, |
| | 908 | ppEnumInstances: **IEnumSetupInstances, // [out] |
| | 909 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 910 | GetInstanceForCurrentProcess: *const fn ( |
| | 911 | self: *T, |
| | 912 | ppInstance: **ISetupInstance, // [out] |
| | 913 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 914 | GetInstanceForPath: *const fn ( |
| | 915 | self: *T, |
| | 916 | wzPath: windows.LPCWSTR, // [in] |
| | 917 | ppInstance: **ISetupInstance, // [out] |
| | 918 | ) callconv(windows.WINAPI) windows.HRESULT, |
| 749 | }; | 919 | }; |
| 750 | if (stat.kind != .file) | 920 | } |
| 751 | return error.MsvcLibDirNotFound; | 921 | }; |
| 752 | | 922 | |
| 753 | return full_path; | 923 | const IEnumSetupInstances = extern struct { |
| | 924 | vtable: *extern struct { |
| | 925 | unknown: IUnknown.VTable(IEnumSetupInstances), |
| | 926 | enum_setup_instances: VTable(IEnumSetupInstances), |
| | 927 | }, |
| | 928 | |
| | 929 | const IID_Value = windows.GUID.parse("{6380bcff-41d3-4b2e-8b2e-bf8a6810c848}"); |
| | 930 | pub const IID = &IID_Value; |
| | 931 | |
| | 932 | pub fn VTable(comptime T: type) type { |
| | 933 | return extern struct { |
| | 934 | /// Returns S_OK if the number of elements were fetched, |
| | 935 | /// S_FALSE if nothing was fetched (at end of enumeration), |
| | 936 | /// E_INVALIDARG if `celt` is greater than 1 and pceltFetched is NULL, |
| | 937 | /// or E_OUTOFMEMORY if an ISetupInstance could not be allocated. |
| | 938 | Next: *const fn ( |
| | 939 | self: *T, |
| | 940 | /// The number of product instances to retrieve |
| | 941 | celt: windows.ULONG, // [in] |
| | 942 | /// A pointer to an array of ISetupInstance |
| | 943 | rgelt: **ISetupInstance, // [out] |
| | 944 | /// A pointer to the number of product instances retrieved. |
| | 945 | /// If `celt` is 1 this paramter may be NULL |
| | 946 | pceltFetched: ?*windows.ULONG, |
| | 947 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 948 | Skip: *const fn ( |
| | 949 | self: *T, |
| | 950 | /// The number of product instances to skip |
| | 951 | celt: windows.ULONG, // [in] |
| | 952 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 953 | Reset: *const fn ( |
| | 954 | self: *T, |
| | 955 | ) callconv(windows.WINAPI) void, |
| | 956 | Clone: *const fn ( |
| | 957 | self: *T, |
| | 958 | ppenum: **IEnumSetupInstances, // [out] |
| | 959 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 960 | }; |
| 754 | } | 961 | } |
| 755 | }; | 962 | }; |
| | 963 | |
| | 964 | const ISetupInstance = extern struct { |
| | 965 | vtable: *extern struct { |
| | 966 | unknown: IUnknown.VTable(ISetupInstance), |
| | 967 | setup_instance: VTable(ISetupInstance), |
| | 968 | }, |
| | 969 | |
| | 970 | const IID_Value = windows.GUID.parse("{b41463c3-8866-43b5-bc33-2b0676f7f42e}"); |
| | 971 | pub const IID = &IID_Value; |
| | 972 | |
| | 973 | pub fn VTable(comptime T: type) type { |
| | 974 | return extern struct { |
| | 975 | GetInstanceId: *const fn ( |
| | 976 | self: *T, |
| | 977 | pbstrInstanceId: *windows.BSTR, // [out] |
| | 978 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 979 | GetInstallDate: *const fn ( |
| | 980 | self: *T, |
| | 981 | pInstallDate: *windows.FILETIME, // [out] |
| | 982 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 983 | GetInstallationName: *const fn ( |
| | 984 | self: *T, |
| | 985 | pbstrInstallationName: *windows.BSTR, // [out] |
| | 986 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 987 | GetInstallationPath: *const fn ( |
| | 988 | self: *T, |
| | 989 | pbstrInstallationPath: *windows.BSTR, // [out] |
| | 990 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 991 | GetInstallationVersion: *const fn ( |
| | 992 | self: *T, |
| | 993 | pbstrInstallationVersion: *windows.BSTR, // [out] |
| | 994 | ) callconv(windows.WINAPI) windows.HRESULT, |
| | 995 | GetDisplayName: *anyopaque, |
| | 996 | GetDescription: *anyopaque, |
| | 997 | ResolvePath: *anyopaque, |
| | 998 | }; |
| | 999 | } |
| | 1000 | }; |
| | 1001 | |
| | 1002 | const SetupConfiguration = extern struct { |
| | 1003 | const CLSID_Value = windows.GUID.parse("{177f0c4a-1cd3-4de7-a32c-71dbbb9fa36d}"); |
| | 1004 | pub const CLSID = &CLSID_Value; |
| | 1005 | }; |
| | 1006 | |
| | 1007 | extern "ole32" fn CoCreateInstance( |
| | 1008 | rclsid: ?*const windows.GUID, // [in] |
| | 1009 | pUnkOuter: ?*IUnknown, // [in] |
| | 1010 | dwClsContext: windows.DWORD, // [in] |
| | 1011 | riid: ?*const windows.GUID, // [in] |
| | 1012 | ppv: **anyopaque, // [out] |
| | 1013 | ) callconv(windows.WINAPI) windows.HRESULT; |
| | 1014 | |
| | 1015 | extern "oleaut32" fn SysFreeString(bstrString: ?windows.BSTR) callconv(windows.WINAPI) void; |
| | 1016 | |
| | 1017 | const CLSCTX = struct { |
| | 1018 | const INPROC_SERVER = 0x1; |
| | 1019 | const INPROC_HANDLER = 0x2; |
| | 1020 | }; |