Skip to content

Commit e57be57

Browse files
committed
feat: add msvc.cppm — robust Visual Studio / MSVC STL discovery
New module mcpp.toolchain.msvc with three discovery strategies: 1. vswhere.exe (Microsoft's official VS locator) — most reliable 2. Environment variables (VSINSTALLDIR, VS*COMNTOOLS) 3. Well-known paths (VS 2017-2025, all editions + BuildTools) Replaces the hardcoded VS2022 path in clang.cppm with msvc::find_std_module_source(). Fixes "no std module source" error on machines with non-standard VS installations. Also provides find_cl() for future MSVC toolchain support.
1 parent 6d5eb10 commit e57be57

3 files changed

Lines changed: 263 additions & 19 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# MSVC STL Discovery — msvc.cppm 模块设计
2+
3+
## 问题
4+
5+
mcpp 在 Windows 上查找 `import std` 的 MSVC STL `std.ixx` 时硬编码了
6+
`C:\Program Files\Microsoft Visual Studio\2022`,导致非标准 VS 安装找不到。
7+
8+
## 方案
9+
10+
新建 `src/toolchain/msvc.cppm` 模块,提供 MSVC/Visual Studio 发现能力。
11+
12+
### 查找策略(按优先级)
13+
14+
1. **vswhere.exe** — 微软官方 VS 安装发现工具,最可靠
15+
- 路径: `C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe`
16+
- 命令: `vswhere -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`
17+
- 返回: VS 安装路径如 `C:\Program Files\Microsoft Visual Studio\2022\Community`
18+
19+
2. **环境变量**`VSINSTALLDIR``VS170COMNTOOLS`(2022)、`VS160COMNTOOLS`(2019)
20+
-`VSINSTALLDIR` 直接得到 VS 根目录
21+
-`VS*COMNTOOLS` 向上推算
22+
23+
3. **固定路径扫描** — 兜底方案
24+
- `C:\Program Files\Microsoft Visual Studio\{2025,2022,2019}\{Enterprise,Professional,Community,BuildTools}`
25+
- `C:\Program Files (x86)\Microsoft Visual Studio\{2019,2017}\...`
26+
27+
### std.ixx 路径推算
28+
29+
从 VS 安装路径推算:
30+
```
31+
<VS_ROOT>/VC/Tools/MSVC/<version>/modules/std.ixx
32+
```
33+
`<version>` 通过遍历 `VC/Tools/MSVC/` 目录取最新版本。
34+
35+
### 模块接口
36+
37+
```cpp
38+
export module mcpp.toolchain.msvc;
39+
import std;
40+
41+
export namespace mcpp::toolchain::msvc {
42+
43+
// 查找 VS 安装路径(返回最新版本)
44+
std::optional<std::filesystem::path> find_vs_install_path();
45+
46+
// 查找 MSVC 工具链根目录 (VC/Tools/MSVC/<ver>/)
47+
std::optional<std::filesystem::path> find_msvc_tools_dir();
48+
49+
// 查找 std.ixx 模块源文件
50+
std::optional<std::filesystem::path> find_std_module_source();
51+
52+
// 查找 cl.exe(未来 MSVC 工具链支持用)
53+
std::optional<std::filesystem::path> find_cl();
54+
55+
}
56+
```
57+
58+
### 改动点
59+
60+
1. 新建 `src/toolchain/msvc.cppm`
61+
2. `src/toolchain/clang.cppm` — `enrich_toolchain` 中的硬编码 VS 路径改为调用 `msvc::find_std_module_source()`

src/toolchain/clang.cppm

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export module mcpp.toolchain.clang;
44

55
import std;
66
import mcpp.toolchain.model;
7+
import mcpp.toolchain.msvc;
78
import mcpp.toolchain.probe;
89
import mcpp.xlings;
910
import mcpp.platform;
@@ -146,26 +147,11 @@ void enrich_toolchain(Toolchain& tc, const std::string& envPrefix) {
146147

147148
#if defined(_WIN32)
148149
// Fallback: if libc++ std.cppm not found, look for MSVC STL's std.ixx.
149-
// This happens when Clang targets x86_64-pc-windows-msvc.
150+
// Uses msvc.cppm which searches via vswhere, env vars, and known paths.
150151
if (!tc.hasImportStd && msvTarget) {
151-
// Search Visual Studio installations for std.ixx
152-
// Typical path: C:\Program Files\Microsoft Visual Studio\2022\*\VC\Tools\MSVC\*\modules\std.ixx
153-
std::error_code ec;
154-
std::filesystem::path vsBase = "C:\\Program Files\\Microsoft Visual Studio\\2022";
155-
if (std::filesystem::exists(vsBase, ec)) {
156-
for (auto& edition : std::filesystem::directory_iterator(vsBase, ec)) {
157-
auto vcTools = edition.path() / "VC" / "Tools" / "MSVC";
158-
if (!std::filesystem::exists(vcTools, ec)) continue;
159-
for (auto& ver : std::filesystem::directory_iterator(vcTools, ec)) {
160-
auto stdIxx = ver.path() / "modules" / "std.ixx";
161-
if (std::filesystem::exists(stdIxx, ec)) {
162-
tc.stdModuleSource = stdIxx;
163-
tc.hasImportStd = true;
164-
break;
165-
}
166-
}
167-
if (tc.hasImportStd) break;
168-
}
152+
if (auto p = mcpp::toolchain::msvc::find_std_module_source()) {
153+
tc.stdModuleSource = *p;
154+
tc.hasImportStd = true;
169155
}
170156
}
171157
#endif

src/toolchain/msvc.cppm

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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

Comments
 (0)