|
| 1 | +// mcpp.toolchain.msvc — MSVC / Visual Studio discovery on Windows. |
| 2 | +// |
| 3 | +// Provides reliable discovery of Visual Studio installations and MSVC |
| 4 | +// toolchain components (std.ixx, cl.exe, lib.exe, etc.) using multiple |
| 5 | +// strategies: |
| 6 | +// 1. vswhere.exe (Microsoft's official VS locator) |
| 7 | +// 2. Environment variables (VSINSTALLDIR, VS*COMNTOOLS) |
| 8 | +// 3. Well-known installation paths (fallback) |
| 9 | +// |
| 10 | +// This module is used by clang.cppm to find MSVC STL's std.ixx when |
| 11 | +// Clang targets x86_64-pc-windows-msvc. It will also serve as the |
| 12 | +// foundation for future native MSVC (cl.exe) toolchain support. |
| 13 | + |
| 14 | +module; |
| 15 | +#include <cstdio> |
| 16 | +#include <cstdlib> |
| 17 | +#if defined(_WIN32) |
| 18 | +#define popen _popen |
| 19 | +#define pclose _pclose |
| 20 | +#endif |
| 21 | + |
| 22 | +export module mcpp.toolchain.msvc; |
| 23 | + |
| 24 | +import std; |
| 25 | + |
| 26 | +export namespace mcpp::toolchain::msvc { |
| 27 | + |
| 28 | +// Find a Visual Studio installation path (returns the newest found). |
| 29 | +std::optional<std::filesystem::path> find_vs_install_path(); |
| 30 | + |
| 31 | +// Find the MSVC tools directory: <VS>/VC/Tools/MSVC/<latest_version>/ |
| 32 | +std::optional<std::filesystem::path> find_msvc_tools_dir(); |
| 33 | + |
| 34 | +// Find MSVC STL's std.ixx module source file. |
| 35 | +std::optional<std::filesystem::path> find_std_module_source(); |
| 36 | + |
| 37 | +// Find cl.exe (for future MSVC toolchain support). |
| 38 | +std::optional<std::filesystem::path> find_cl(); |
| 39 | + |
| 40 | +} // namespace mcpp::toolchain::msvc |
| 41 | + |
| 42 | +namespace mcpp::toolchain::msvc { |
| 43 | + |
| 44 | +namespace { |
| 45 | + |
| 46 | +#if defined(_WIN32) |
| 47 | + |
| 48 | +// Run a command and capture stdout (first line, trimmed). |
| 49 | +std::string run_capture_line(const std::string& cmd) { |
| 50 | + std::array<char, 4096> buf{}; |
| 51 | + std::string out; |
| 52 | + std::FILE* fp = ::popen(cmd.c_str(), "r"); |
| 53 | + if (!fp) return {}; |
| 54 | + while (std::fgets(buf.data(), buf.size(), fp) != nullptr) |
| 55 | + out += buf.data(); |
| 56 | + ::pclose(fp); |
| 57 | + // Trim trailing whitespace/newlines |
| 58 | + while (!out.empty() && (out.back() == '\n' || out.back() == '\r' || out.back() == ' ')) |
| 59 | + out.pop_back(); |
| 60 | + // Take first line only |
| 61 | + auto nl = out.find('\n'); |
| 62 | + if (nl != std::string::npos) out.resize(nl); |
| 63 | + return out; |
| 64 | +} |
| 65 | + |
| 66 | +// Strategy 1: Use vswhere.exe to find VS installation. |
| 67 | +std::optional<std::filesystem::path> find_vs_via_vswhere() { |
| 68 | + // vswhere.exe ships with the VS Installer at a well-known path |
| 69 | + std::filesystem::path vswhere = |
| 70 | + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"; |
| 71 | + if (!std::filesystem::exists(vswhere)) return std::nullopt; |
| 72 | + |
| 73 | + auto result = run_capture_line( |
| 74 | + "\"" + vswhere.string() + "\" -latest -products * " |
| 75 | + "-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 " |
| 76 | + "-property installationPath 2>nul"); |
| 77 | + |
| 78 | + if (!result.empty() && std::filesystem::exists(result)) |
| 79 | + return std::filesystem::path(result); |
| 80 | + return std::nullopt; |
| 81 | +} |
| 82 | + |
| 83 | +// Strategy 2: Use environment variables. |
| 84 | +std::optional<std::filesystem::path> find_vs_via_env() { |
| 85 | + // VSINSTALLDIR is set inside VS Developer Command Prompt |
| 86 | + if (auto* dir = std::getenv("VSINSTALLDIR"); dir && *dir) { |
| 87 | + std::filesystem::path p{dir}; |
| 88 | + if (std::filesystem::exists(p / "VC" / "Tools" / "MSVC")) |
| 89 | + return p; |
| 90 | + } |
| 91 | + |
| 92 | + // VS*COMNTOOLS: VS170COMNTOOLS (2022), VS160COMNTOOLS (2019), VS150COMNTOOLS (2017) |
| 93 | + for (auto* var : {"VS170COMNTOOLS", "VS160COMNTOOLS", "VS150COMNTOOLS"}) { |
| 94 | + if (auto* val = std::getenv(var); val && *val) { |
| 95 | + // Common7/Tools/ → go up two levels to VS root |
| 96 | + std::filesystem::path p{val}; |
| 97 | + auto root = p.parent_path().parent_path(); |
| 98 | + if (std::filesystem::exists(root / "VC" / "Tools" / "MSVC")) |
| 99 | + return root; |
| 100 | + } |
| 101 | + } |
| 102 | + return std::nullopt; |
| 103 | +} |
| 104 | + |
| 105 | +// Strategy 3: Scan well-known paths. |
| 106 | +std::optional<std::filesystem::path> find_vs_via_paths() { |
| 107 | + static constexpr std::string_view bases[] = { |
| 108 | + "C:\\Program Files\\Microsoft Visual Studio", |
| 109 | + "C:\\Program Files (x86)\\Microsoft Visual Studio", |
| 110 | + }; |
| 111 | + static constexpr std::string_view years[] = {"2025", "2022", "2019", "2017"}; |
| 112 | + static constexpr std::string_view editions[] = { |
| 113 | + "Enterprise", "Professional", "Community", "BuildTools", "Preview" |
| 114 | + }; |
| 115 | + |
| 116 | + std::error_code ec; |
| 117 | + for (auto base : bases) { |
| 118 | + for (auto year : years) { |
| 119 | + for (auto edition : editions) { |
| 120 | + auto p = std::filesystem::path(base) / std::string(year) / std::string(edition); |
| 121 | + if (std::filesystem::exists(p / "VC" / "Tools" / "MSVC", ec)) |
| 122 | + return p; |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + return std::nullopt; |
| 127 | +} |
| 128 | + |
| 129 | +// From a VS install path, find the latest MSVC tools version directory. |
| 130 | +std::optional<std::filesystem::path> find_latest_msvc_tools(const std::filesystem::path& vsRoot) { |
| 131 | + auto vcTools = vsRoot / "VC" / "Tools" / "MSVC"; |
| 132 | + std::error_code ec; |
| 133 | + if (!std::filesystem::exists(vcTools, ec)) return std::nullopt; |
| 134 | + |
| 135 | + std::filesystem::path latest; |
| 136 | + std::string latestVer; |
| 137 | + for (auto& entry : std::filesystem::directory_iterator(vcTools, ec)) { |
| 138 | + if (!entry.is_directory()) continue; |
| 139 | + auto ver = entry.path().filename().string(); |
| 140 | + if (ver > latestVer) { |
| 141 | + latestVer = ver; |
| 142 | + latest = entry.path(); |
| 143 | + } |
| 144 | + } |
| 145 | + return latest.empty() ? std::nullopt : std::optional{latest}; |
| 146 | +} |
| 147 | + |
| 148 | +#endif // _WIN32 |
| 149 | + |
| 150 | +} // namespace |
| 151 | + |
| 152 | +std::optional<std::filesystem::path> find_vs_install_path() { |
| 153 | +#if defined(_WIN32) |
| 154 | + // Try strategies in order of reliability |
| 155 | + if (auto p = find_vs_via_vswhere()) return p; |
| 156 | + if (auto p = find_vs_via_env()) return p; |
| 157 | + if (auto p = find_vs_via_paths()) return p; |
| 158 | +#endif |
| 159 | + return std::nullopt; |
| 160 | +} |
| 161 | + |
| 162 | +std::optional<std::filesystem::path> find_msvc_tools_dir() { |
| 163 | +#if defined(_WIN32) |
| 164 | + auto vs = find_vs_install_path(); |
| 165 | + if (!vs) return std::nullopt; |
| 166 | + return find_latest_msvc_tools(*vs); |
| 167 | +#else |
| 168 | + return std::nullopt; |
| 169 | +#endif |
| 170 | +} |
| 171 | + |
| 172 | +std::optional<std::filesystem::path> find_std_module_source() { |
| 173 | +#if defined(_WIN32) |
| 174 | + auto tools = find_msvc_tools_dir(); |
| 175 | + if (!tools) return std::nullopt; |
| 176 | + |
| 177 | + auto stdIxx = *tools / "modules" / "std.ixx"; |
| 178 | + if (std::filesystem::exists(stdIxx)) |
| 179 | + return stdIxx; |
| 180 | +#endif |
| 181 | + return std::nullopt; |
| 182 | +} |
| 183 | + |
| 184 | +std::optional<std::filesystem::path> find_cl() { |
| 185 | +#if defined(_WIN32) |
| 186 | + auto tools = find_msvc_tools_dir(); |
| 187 | + if (!tools) return std::nullopt; |
| 188 | + |
| 189 | + // cl.exe is at <tools>/bin/Hostx64/x64/cl.exe |
| 190 | + auto cl = *tools / "bin" / "Hostx64" / "x64" / "cl.exe"; |
| 191 | + if (std::filesystem::exists(cl)) |
| 192 | + return cl; |
| 193 | +#endif |
| 194 | + return std::nullopt; |
| 195 | +} |
| 196 | + |
| 197 | +} // namespace mcpp::toolchain::msvc |
0 commit comments