| ... | ... | @@ -550,7 +550,6 @@ pub const ZigWindowsSDK = struct { |
| 550 | 550 | |
| 551 | 551 | const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(allocator) catch |err| switch (err) { |
| 552 | 552 | error.MsvcLibDirNotFound => null, |
| 553 | | error.PathTooLong => null, |
| 554 | 553 | error.OutOfMemory => return error.OutOfMemory, |
| 555 | 554 | }; |
| 556 | 555 | errdefer allocator.free(msvc_lib_dir); |
| ... | ... | @@ -576,6 +575,112 @@ pub const ZigWindowsSDK = struct { |
| 576 | 575 | }; |
| 577 | 576 | |
| 578 | 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 | 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 | 685 | fn findViaRegistry(allocator: std.mem.Allocator) error{ OutOfMemory, PathNotFound }![]const u8 { |
| 581 | 686 | |
| ... | ... | @@ -661,6 +766,10 @@ const MsvcLibDir = struct { |
| 661 | 766 | }; |
| 662 | 767 | errdefer allocator.free(msvc_dir); |
| 663 | 768 | |
| 769 | if (!verifyLibDir(msvc_dir)) { |
| 770 | return error.PathNotFound; |
| 771 | } |
| 772 | |
| 664 | 773 | return msvc_dir; |
| 665 | 774 | } |
| 666 | 775 | |
| ... | ... | @@ -720,36 +829,192 @@ const MsvcLibDir = struct { |
| 720 | 829 | }; |
| 721 | 830 | try base_path.appendSlice(folder_with_arch); |
| 722 | 831 | |
| 832 | if (!verifyLibDir(base_path.items)) { |
| 833 | return error.PathNotFound; |
| 834 | } |
| 835 | |
| 723 | 836 | const full_path = try base_path.toOwnedSlice(); |
| 724 | 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 | 853 | /// Find path to MSVC's `lib/` directory. |
| 728 | 854 | /// Caller owns the result. |
| 729 | | pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound, PathTooLong }![]const u8 { |
| 730 | | const full_path = MsvcLibDir.findViaRegistry(allocator) catch |err1| switch (err1) { |
| 855 | pub fn find(allocator: std.mem.Allocator) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { |
| 856 | const full_path = MsvcLibDir.findViaCOM(allocator) catch |err1| switch (err1) { |
| 731 | 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 | 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 | 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) { |
| 741 | | error.NameTooLong => return error.PathTooLong, |
| 742 | | else => return error.MsvcLibDirNotFound, |
| 868 | return full_path; |
| 869 | } |
| 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) { |
| 747 | | error.NameTooLong => return error.PathTooLong, |
| 748 | | else => return error.MsvcLibDirNotFound, |
| 895 | const ISetupConfiguration = extern struct { |
| 896 | vtable: *extern struct { |
| 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) |
| 751 | | return error.MsvcLibDirNotFound; |
| 920 | } |
| 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 | }; |