diff --git a/.agents/docs/2026-05-20-msvc-stl-discovery-plan.md b/.agents/docs/2026-05-20-msvc-stl-discovery-plan.md new file mode 100644 index 0000000..257f13c --- /dev/null +++ b/.agents/docs/2026-05-20-msvc-stl-discovery-plan.md @@ -0,0 +1,61 @@ +# MSVC STL Discovery — msvc.cppm 模块设计 + +## 问题 + +mcpp 在 Windows 上查找 `import std` 的 MSVC STL `std.ixx` 时硬编码了 +`C:\Program Files\Microsoft Visual Studio\2022`,导致非标准 VS 安装找不到。 + +## 方案 + +新建 `src/toolchain/msvc.cppm` 模块,提供 MSVC/Visual Studio 发现能力。 + +### 查找策略(按优先级) + +1. **vswhere.exe** — 微软官方 VS 安装发现工具,最可靠 + - 路径: `C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe` + - 命令: `vswhere -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath` + - 返回: VS 安装路径如 `C:\Program Files\Microsoft Visual Studio\2022\Community` + +2. **环境变量** — `VSINSTALLDIR`、`VS170COMNTOOLS`(2022)、`VS160COMNTOOLS`(2019) + - 从 `VSINSTALLDIR` 直接得到 VS 根目录 + - 从 `VS*COMNTOOLS` 向上推算 + +3. **固定路径扫描** — 兜底方案 + - `C:\Program Files\Microsoft Visual Studio\{2025,2022,2019}\{Enterprise,Professional,Community,BuildTools}` + - `C:\Program Files (x86)\Microsoft Visual Studio\{2019,2017}\...` + +### std.ixx 路径推算 + +从 VS 安装路径推算: +``` +/VC/Tools/MSVC//modules/std.ixx +``` +`` 通过遍历 `VC/Tools/MSVC/` 目录取最新版本。 + +### 模块接口 + +```cpp +export module mcpp.toolchain.msvc; +import std; + +export namespace mcpp::toolchain::msvc { + +// 查找 VS 安装路径(返回最新版本) +std::optional find_vs_install_path(); + +// 查找 MSVC 工具链根目录 (VC/Tools/MSVC//) +std::optional find_msvc_tools_dir(); + +// 查找 std.ixx 模块源文件 +std::optional find_std_module_source(); + +// 查找 cl.exe(未来 MSVC 工具链支持用) +std::optional find_cl(); + +} +``` + +### 改动点 + +1. 新建 `src/toolchain/msvc.cppm` +2. `src/toolchain/clang.cppm` — `enrich_toolchain` 中的硬编码 VS 路径改为调用 `msvc::find_std_module_source()` diff --git a/src/toolchain/clang.cppm b/src/toolchain/clang.cppm index 9cea70b..837475f 100644 --- a/src/toolchain/clang.cppm +++ b/src/toolchain/clang.cppm @@ -4,6 +4,7 @@ export module mcpp.toolchain.clang; import std; import mcpp.toolchain.model; +import mcpp.toolchain.msvc; import mcpp.toolchain.probe; import mcpp.xlings; import mcpp.platform; @@ -146,26 +147,11 @@ void enrich_toolchain(Toolchain& tc, const std::string& envPrefix) { #if defined(_WIN32) // Fallback: if libc++ std.cppm not found, look for MSVC STL's std.ixx. - // This happens when Clang targets x86_64-pc-windows-msvc. + // Uses msvc.cppm which searches via vswhere, env vars, and known paths. if (!tc.hasImportStd && msvTarget) { - // Search Visual Studio installations for std.ixx - // Typical path: C:\Program Files\Microsoft Visual Studio\2022\*\VC\Tools\MSVC\*\modules\std.ixx - std::error_code ec; - std::filesystem::path vsBase = "C:\\Program Files\\Microsoft Visual Studio\\2022"; - if (std::filesystem::exists(vsBase, ec)) { - for (auto& edition : std::filesystem::directory_iterator(vsBase, ec)) { - auto vcTools = edition.path() / "VC" / "Tools" / "MSVC"; - if (!std::filesystem::exists(vcTools, ec)) continue; - for (auto& ver : std::filesystem::directory_iterator(vcTools, ec)) { - auto stdIxx = ver.path() / "modules" / "std.ixx"; - if (std::filesystem::exists(stdIxx, ec)) { - tc.stdModuleSource = stdIxx; - tc.hasImportStd = true; - break; - } - } - if (tc.hasImportStd) break; - } + if (auto p = mcpp::toolchain::msvc::find_std_module_source()) { + tc.stdModuleSource = *p; + tc.hasImportStd = true; } } #endif diff --git a/src/toolchain/msvc.cppm b/src/toolchain/msvc.cppm new file mode 100644 index 0000000..967acb3 --- /dev/null +++ b/src/toolchain/msvc.cppm @@ -0,0 +1,197 @@ +// mcpp.toolchain.msvc — MSVC / Visual Studio discovery on Windows. +// +// Provides reliable discovery of Visual Studio installations and MSVC +// toolchain components (std.ixx, cl.exe, lib.exe, etc.) using multiple +// strategies: +// 1. vswhere.exe (Microsoft's official VS locator) +// 2. Environment variables (VSINSTALLDIR, VS*COMNTOOLS) +// 3. Well-known installation paths (fallback) +// +// This module is used by clang.cppm to find MSVC STL's std.ixx when +// Clang targets x86_64-pc-windows-msvc. It will also serve as the +// foundation for future native MSVC (cl.exe) toolchain support. + +module; +#include +#include +#if defined(_WIN32) +#define popen _popen +#define pclose _pclose +#endif + +export module mcpp.toolchain.msvc; + +import std; + +export namespace mcpp::toolchain::msvc { + +// Find a Visual Studio installation path (returns the newest found). +std::optional find_vs_install_path(); + +// Find the MSVC tools directory: /VC/Tools/MSVC// +std::optional find_msvc_tools_dir(); + +// Find MSVC STL's std.ixx module source file. +std::optional find_std_module_source(); + +// Find cl.exe (for future MSVC toolchain support). +std::optional find_cl(); + +} // namespace mcpp::toolchain::msvc + +namespace mcpp::toolchain::msvc { + +namespace { + +#if defined(_WIN32) + +// Run a command and capture stdout (first line, trimmed). +std::string run_capture_line(const std::string& cmd) { + std::array buf{}; + std::string out; + std::FILE* fp = ::popen(cmd.c_str(), "r"); + if (!fp) return {}; + while (std::fgets(buf.data(), buf.size(), fp) != nullptr) + out += buf.data(); + ::pclose(fp); + // Trim trailing whitespace/newlines + while (!out.empty() && (out.back() == '\n' || out.back() == '\r' || out.back() == ' ')) + out.pop_back(); + // Take first line only + auto nl = out.find('\n'); + if (nl != std::string::npos) out.resize(nl); + return out; +} + +// Strategy 1: Use vswhere.exe to find VS installation. +std::optional find_vs_via_vswhere() { + // vswhere.exe ships with the VS Installer at a well-known path + std::filesystem::path vswhere = + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + if (!std::filesystem::exists(vswhere)) return std::nullopt; + + auto result = run_capture_line( + "\"" + vswhere.string() + "\" -latest -products * " + "-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 " + "-property installationPath 2>nul"); + + if (!result.empty() && std::filesystem::exists(result)) + return std::filesystem::path(result); + return std::nullopt; +} + +// Strategy 2: Use environment variables. +std::optional find_vs_via_env() { + // VSINSTALLDIR is set inside VS Developer Command Prompt + if (auto* dir = std::getenv("VSINSTALLDIR"); dir && *dir) { + std::filesystem::path p{dir}; + if (std::filesystem::exists(p / "VC" / "Tools" / "MSVC")) + return p; + } + + // VS*COMNTOOLS: VS170COMNTOOLS (2022), VS160COMNTOOLS (2019), VS150COMNTOOLS (2017) + for (auto* var : {"VS170COMNTOOLS", "VS160COMNTOOLS", "VS150COMNTOOLS"}) { + if (auto* val = std::getenv(var); val && *val) { + // Common7/Tools/ → go up two levels to VS root + std::filesystem::path p{val}; + auto root = p.parent_path().parent_path(); + if (std::filesystem::exists(root / "VC" / "Tools" / "MSVC")) + return root; + } + } + return std::nullopt; +} + +// Strategy 3: Scan well-known paths. +std::optional find_vs_via_paths() { + static constexpr std::string_view bases[] = { + "C:\\Program Files\\Microsoft Visual Studio", + "C:\\Program Files (x86)\\Microsoft Visual Studio", + }; + static constexpr std::string_view years[] = {"2025", "2022", "2019", "2017"}; + static constexpr std::string_view editions[] = { + "Enterprise", "Professional", "Community", "BuildTools", "Preview" + }; + + std::error_code ec; + for (auto base : bases) { + for (auto year : years) { + for (auto edition : editions) { + auto p = std::filesystem::path(base) / std::string(year) / std::string(edition); + if (std::filesystem::exists(p / "VC" / "Tools" / "MSVC", ec)) + return p; + } + } + } + return std::nullopt; +} + +// From a VS install path, find the latest MSVC tools version directory. +std::optional find_latest_msvc_tools(const std::filesystem::path& vsRoot) { + auto vcTools = vsRoot / "VC" / "Tools" / "MSVC"; + std::error_code ec; + if (!std::filesystem::exists(vcTools, ec)) return std::nullopt; + + std::filesystem::path latest; + std::string latestVer; + for (auto& entry : std::filesystem::directory_iterator(vcTools, ec)) { + if (!entry.is_directory()) continue; + auto ver = entry.path().filename().string(); + if (ver > latestVer) { + latestVer = ver; + latest = entry.path(); + } + } + return latest.empty() ? std::nullopt : std::optional{latest}; +} + +#endif // _WIN32 + +} // namespace + +std::optional find_vs_install_path() { +#if defined(_WIN32) + // Try strategies in order of reliability + if (auto p = find_vs_via_vswhere()) return p; + if (auto p = find_vs_via_env()) return p; + if (auto p = find_vs_via_paths()) return p; +#endif + return std::nullopt; +} + +std::optional find_msvc_tools_dir() { +#if defined(_WIN32) + auto vs = find_vs_install_path(); + if (!vs) return std::nullopt; + return find_latest_msvc_tools(*vs); +#else + return std::nullopt; +#endif +} + +std::optional find_std_module_source() { +#if defined(_WIN32) + auto tools = find_msvc_tools_dir(); + if (!tools) return std::nullopt; + + auto stdIxx = *tools / "modules" / "std.ixx"; + if (std::filesystem::exists(stdIxx)) + return stdIxx; +#endif + return std::nullopt; +} + +std::optional find_cl() { +#if defined(_WIN32) + auto tools = find_msvc_tools_dir(); + if (!tools) return std::nullopt; + + // cl.exe is at /bin/Hostx64/x64/cl.exe + auto cl = *tools / "bin" / "Hostx64" / "x64" / "cl.exe"; + if (std::filesystem::exists(cl)) + return cl; +#endif + return std::nullopt; +} + +} // namespace mcpp::toolchain::msvc