Skip to content

Commit e1a2569

Browse files
committed
feat: Windows LLVM support — design doc + CI validation workflow
Design doc at .agents/docs/2026-05-17-windows-llvm-support-design.md covers architecture, two technical paths (clang++ vs clang-cl), and implementation plan. ci-windows.yml validates: - xlings LLVM installation on Windows - clang++.exe and clang-cl.exe compilation - libc++ / MSVC STL availability for import std - Package structure inspection
1 parent b5ce5b1 commit e1a2569

2 files changed

Lines changed: 394 additions & 0 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Windows LLVM/Clang 支持设计方案
2+
3+
Date: 2026-05-17
4+
5+
## 1. 目标
6+
7+
在 Windows x86_64 上用 xlings LLVM(clang++ / clang-cl)支持 mcpp 构建 C++23 模块项目,达到与 Linux/macOS 同等的核心可用水平:
8+
- `mcpp build` / `mcpp run` / `mcpp test`
9+
- `import std` 支持
10+
- 多模块项目 + 增量编译
11+
- 自举(mcpp 编译自己)
12+
13+
## 2. 现状分析
14+
15+
### 2.1 xlings LLVM Windows 包
16+
17+
**已有**(xlings-res/llvm 20.1.7):
18+
```
19+
bin/clang++.exe, clang.exe, clang-cl.exe ← 编译器
20+
bin/lld-link.exe ← MSVC 兼容链接器
21+
bin/llvm-ar.exe, llvm-lib.exe ← 归档工具
22+
bin/llvm-rc.exe, llvm-mt.exe ← 资源编译器
23+
lib/clang/20/lib/windows/ ← compiler-rt
24+
```
25+
26+
**没有**
27+
- ❌ libc++ 头文件和库(`include/c++/v1/` 不存在)
28+
-`std.cppm` / `std.compat.cppm` 模块源码
29+
-`clang-scan-deps.exe`(P1689 模块扫描器)
30+
31+
### 2.2 含义
32+
33+
Windows LLVM 包设计为 **MSVC 兼容模式**
34+
- 使用 MSVC 的 STL(`<iostream>` 等来自 Visual Studio)
35+
- 使用 MSVC 的 C runtime(ucrt)
36+
- 通过 `clang-cl.exe` 驱动(接受 `/std:c++latest``/EHsc` 等 MSVC 风格参数)
37+
- 链接用 `lld-link.exe`(MSVC `link.exe` 兼容)
38+
39+
这是 **Windows 上的行业标准做法**——即使用 Clang,也通常走 MSVC ABI。
40+
41+
### 2.3 C++23 Modules 在 Windows MSVC STL 上的状态
42+
43+
MSVC STL 从 VS 2022 17.5 起支持 `import std;`
44+
- 需要 `/std:c++latest``/std:c++23`
45+
- 模块文件格式:`.ifc`(不是 `.pcm``.gcm`
46+
-**clang-cl 目前不支持 MSVC 的 `.ifc` 格式**
47+
48+
**clang++ (GNU 驱动) on Windows**
49+
- 可以用 `-stdlib=libc++` 但需要自带 libc++
50+
- 当前 xlings Windows LLVM 包没有 libc++
51+
- 如果补充 libc++,可以用与 Linux/macOS 相同的 `.pcm` 模块模型
52+
53+
### 2.4 mcpp 代码现状
54+
55+
| 组件 | Windows 状态 | 需要改动 |
56+
|------|-------------|---------|
57+
| 平台检测(`_WIN32`| ✅ 已有 ||
58+
| CompilerId::MSVC | ✅ enum 定义 | 需要实现 |
59+
| `probe.cppm` | ❌ 用 Unix shell | 需要 Windows 移植 |
60+
| `flags.cppm` | ❌ 全是 Unix flags | 需要 MSVC flags |
61+
| `ninja_backend.cppm` | ❌ shell 命令 | 需要 cmd/PowerShell 适配 |
62+
| `config.cppm` |`/proc/self/exe` | 需要 `GetModuleFileName` |
63+
| `install.sh` | ⚠️ bash only | Windows 需要 PowerShell |
64+
65+
## 3. 技术方案
66+
67+
### 3.1 两条路径对比
68+
69+
| 方案 | 路径 | 优点 | 缺点 |
70+
|------|------|------|------|
71+
| **A: clang++ + libc++** | GNU 驱动 + 自带 libc++ | 与 Linux/macOS 统一,`.pcm` 格式 | 需要补充 libc++ 到 LLVM 包 |
72+
| **B: clang-cl + MSVC STL** | MSVC 兼容驱动 | Windows 原生,ABI 兼容 | 全新编译模型(`/std:c++latest`),`.ifc` 格式不兼容 |
73+
74+
**推荐方案 A**:用 `clang++.exe`(GNU 驱动)+ 补充 libc++。理由:
75+
1. 与 Linux/macOS 共享同一套模块编译逻辑(`.pcm``-fmodule-file=`
76+
2. 不依赖 Visual Studio 安装
77+
3. mcpp 核心代码改动最小(只需要处理路径分隔符和 shell 命令差异)
78+
79+
### 3.2 前提条件
80+
81+
1. **xlings LLVM Windows 包需要补充 libc++**
82+
- `include/c++/v1/` — libc++ 头文件
83+
- `share/libc++/v1/std.cppm` + `std.compat.cppm` — 模块源
84+
- `lib/libc++.lib` (或 `.a`) — 静态库
85+
- `bin/clang-scan-deps.exe` — P1689 扫描器
86+
87+
2. **或者**:在 LLVM Windows 包中生成 `clang++.cfg` 配置 libc++ 路径
88+
89+
### 3.3 mcpp 代码改动清单
90+
91+
#### Phase 1: 核心编译(让 `mcpp build` 在 Windows 上工作)
92+
93+
| 文件 | 改动 | 优先级 |
94+
|------|------|--------|
95+
| `probe.cppm` | `probe_compiler_binary`: Windows 用 `where.exe` 替代 `command -v` | P0 |
96+
| `probe.cppm` | `run_capture`: Windows 用 `_popen`/`_pclose` | P0 |
97+
| `probe.cppm` | `discover_*_dirs`: 添加 `#if defined(_WIN32)` 分支 | P0 |
98+
| `flags.cppm` | Windows 链接 flags:无 `-rpath`(Windows 不支持) | P0 |
99+
| `ninja_backend.cppm` | Shell 命令替换:`mkdir -p``cmd /c mkdir`, `cp``copy` | P0 |
100+
| `ninja_backend.cppm` | `mcpp_exe_path`: 用 `GetModuleFileNameW` | P0 |
101+
| `config.cppm` | 同上,exe 路径获取 | P0 |
102+
| `clang.cppm` | Windows libc++ 路径发现 | P1 |
103+
| `cli.cppm` | 默认工具链 `llvm@20.1.7` for Windows | P1 |
104+
105+
#### Phase 2: 可执行文件扩展名
106+
107+
| 位置 | 改动 |
108+
|------|------|
109+
| `ninja_backend.cppm` | 输出 `bin/mcpp.exe` 而非 `bin/mcpp` |
110+
| `manifest.cppm` | `kind = "bin"` 产出 `.exe` |
111+
| `cli.cppm` | `mcpp run` 查找 `.exe` |
112+
113+
#### Phase 3: CI + Release
114+
115+
| 改动 | 说明 |
116+
|------|------|
117+
| `.github/workflows/ci-windows.yml` | Windows CI(`windows-latest` runner) |
118+
| `.github/workflows/bootstrap-windows.yml` | xmake 首次编译 |
119+
| `release.yml` | 添加 Windows job |
120+
121+
### 3.4 Ninja shell 命令移植
122+
123+
这是最复杂的部分。当前 build.ninja 中的 shell 命令:
124+
125+
| 当前 (Unix) | Windows 等价 | 说明 |
126+
|-------------|-------------|------|
127+
| `mkdir -p $(dirname $out) && cp -f $in $out` | `cmd /c if not exist "$$(dir $out)" mkdir "$$(dir $out)" && copy /y $in $out` | 复制 BMI |
128+
| `if [ -n "$bmi_out" ] && ...` | `cmd /c ...` 或 PowerShell | BMI restat 逻辑 |
129+
| `cd ... && $cxx ...` | `cmd /c cd /d ... && $cxx ...` | 编译命令 |
130+
| `env LD_LIBRARY_PATH=...` | 不需要(Windows 用 PATH) | 运行时路径 |
131+
132+
**建议**:在 `ninja_backend.cppm` 中按平台生成不同的 rule 命令,用 `#if defined(_WIN32)` 条件编译。
133+
134+
### 3.5 Windows 链接策略
135+
136+
```cpp
137+
#if defined(_WIN32)
138+
// Windows: clang++ GNU driver links against libc++ automatically
139+
// No -rpath (not a thing on Windows)
140+
// No sysroot (not needed for MSVC ucrt)
141+
// Static libc++: -static-libc++ (or statically link libc++.a)
142+
f.ld = std::format("{}{}", full_static, b_flag);
143+
#endif
144+
```
145+
146+
Windows 产出的 `.exe` 运行时依赖:
147+
- `ucrt` (Universal C Runtime) — Windows 10+ 自带
148+
- `libc++.dll` 或静态链接 `libc++.a`
149+
- `vcruntime140.dll` — 如果用 MSVC 兼容模式
150+
151+
## 4. 实施计划
152+
153+
### Step 1: 验证 xlings LLVM Windows 能否编译 C++23 模块
154+
155+
创建 `ci-windows.yml` 在 GitHub Actions `windows-latest` runner 上:
156+
1. 安装 xlings
157+
2. 安装 LLVM
158+
3. 手动用 clang++ 编译 `import std`(如果 libc++ 可用)
159+
4. 如果 libc++ 不可用,验证 clang-cl + MSVC STL
160+
161+
### Step 2: xmake bootstrap
162+
163+
用 xmake 在 Windows 上编译 mcpp(参考 mcpp-dev 的 xmake.lua)。
164+
165+
### Step 3: mcpp 代码适配
166+
167+
基于 CI 验证结果,逐步适配 probe/flags/ninja_backend。
168+
169+
### Step 4: Self-host + Release
170+
171+
mcpp 自举 → 打包 → release。
172+
173+
## 5. 风险
174+
175+
| 风险 | 影响 | 缓解 |
176+
|------|------|------|
177+
| xlings Windows LLVM 包无 libc++ | 无法用 `import std` | 需要上游补充或用 MSVC STL |
178+
| ninja shell 命令移植复杂 | build.ninja 在 Windows 上不工作 | 可用 ninja 的 `msvc_deps_prefix` 特性 |
179+
| `clang-scan-deps.exe` 缺失 | P1689 扫描不可用 | GCC 模式的 `-fdeps-format` 也可用 |
180+
| Windows path separator (`\` vs `/`) | 路径拼接问题 | `std::filesystem` 已处理大部分 |
181+
182+
## 6. 依赖关系
183+
184+
```
185+
xlings LLVM Windows 包 (libc++ 补充)
186+
187+
CI 验证 (clang++ + import std)
188+
189+
xmake bootstrap (产出 mcpp.exe)
190+
191+
mcpp 代码适配 (probe/flags/ninja)
192+
193+
self-host (mcpp.exe 编译 mcpp.exe)
194+
195+
release
196+
```

.github/workflows/ci-windows.yml

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
name: ci-windows
2+
3+
# Windows validation CI for mcpp.
4+
# Step 1: Verify xlings LLVM toolchain capabilities on Windows.
5+
# Step 2: xmake bootstrap to produce first mcpp.exe.
6+
7+
on:
8+
push:
9+
branches: [ feat/windows-support ]
10+
pull_request:
11+
branches: [ main ]
12+
paths:
13+
- 'src/toolchain/**'
14+
- 'src/build/**'
15+
- 'src/cli.cppm'
16+
- '.github/workflows/ci-windows.yml'
17+
workflow_dispatch:
18+
19+
concurrency:
20+
group: ci-windows-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
windows-llvm-validation:
25+
name: Windows x64 — LLVM toolchain validation
26+
runs-on: windows-latest
27+
timeout-minutes: 30
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: System info
32+
shell: bash
33+
run: |
34+
echo "OS: $(uname -s)"
35+
echo "Arch: $(uname -m)"
36+
echo "Runner: $RUNNER_OS"
37+
38+
- name: Install xlings
39+
shell: bash
40+
env:
41+
XLINGS_NON_INTERACTIVE: '1'
42+
XLINGS_VERSION: '0.4.30'
43+
run: |
44+
# Download xlings for Windows
45+
tarball="xlings-${XLINGS_VERSION}-windows-x86_64.zip"
46+
curl -fsSL -o "/tmp/${tarball}" \
47+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" || {
48+
echo "::warning::xlings Windows zip not available, trying tar.gz"
49+
tarball="xlings-${XLINGS_VERSION}-windows-x86_64.tar.gz"
50+
curl -fsSL -o "/tmp/${tarball}" \
51+
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}"
52+
}
53+
# Extract
54+
cd /tmp
55+
if [[ "$tarball" == *.zip ]]; then
56+
unzip -q "$tarball" || 7z x "$tarball"
57+
else
58+
tar -xzf "$tarball"
59+
fi
60+
# Find and install xlings
61+
XLINGS_DIR=$(find /tmp -maxdepth 2 -name "xlings.exe" -o -name "xlings" | head -1 | xargs dirname)
62+
echo "xlings dir: $XLINGS_DIR"
63+
ls "$XLINGS_DIR/" || true
64+
# Try self install
65+
"$XLINGS_DIR/xlings" self install || "$XLINGS_DIR/xlings.exe" self install || true
66+
echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH"
67+
echo "$HOME/.xlings/bin" >> "$GITHUB_PATH"
68+
69+
- name: Verify xlings
70+
shell: bash
71+
run: |
72+
xlings --version || xlings.exe --version || echo "xlings not found in PATH"
73+
which xlings || which xlings.exe || echo "xlings location unknown"
74+
75+
- name: Install LLVM via xlings
76+
shell: bash
77+
run: |
78+
xlings install llvm -y || xlings install llvm@20.1.7 -y || {
79+
echo "::warning::xlings install llvm failed"
80+
exit 1
81+
}
82+
# Find LLVM
83+
LLVM_ROOT=$(find "$HOME/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang++.exe" 2>/dev/null | head -1 | xargs dirname | xargs dirname)
84+
if [ -z "$LLVM_ROOT" ]; then
85+
LLVM_ROOT=$(find "$HOME/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang.exe" 2>/dev/null | head -1 | xargs dirname | xargs dirname)
86+
fi
87+
echo "LLVM_ROOT=$LLVM_ROOT"
88+
echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV"
89+
90+
- name: Inspect LLVM package structure
91+
shell: bash
92+
run: |
93+
echo "=== bin/ ==="
94+
ls "$LLVM_ROOT/bin/" | grep -iE "clang|lld|llvm-ar|scan" | head -20
95+
echo "=== lib/ ==="
96+
ls "$LLVM_ROOT/lib/" 2>/dev/null | head -10
97+
echo "=== include/c++/ ==="
98+
ls "$LLVM_ROOT/include/c++/" 2>/dev/null || echo "(no libc++ headers)"
99+
echo "=== share/libc++/ ==="
100+
find "$LLVM_ROOT" -name "std.cppm" 2>/dev/null || echo "(no std.cppm)"
101+
echo "=== clang++.cfg ==="
102+
cat "$LLVM_ROOT/bin/clang++.cfg" 2>/dev/null || echo "(no cfg)"
103+
echo "=== clang version ==="
104+
"$LLVM_ROOT/bin/clang++.exe" --version 2>/dev/null || "$LLVM_ROOT/bin/clang++" --version 2>/dev/null
105+
echo "=== Target triple ==="
106+
"$LLVM_ROOT/bin/clang++.exe" -dumpmachine 2>/dev/null || "$LLVM_ROOT/bin/clang++" -dumpmachine 2>/dev/null
107+
108+
- name: Test — non-module C++ compilation (clang++)
109+
shell: bash
110+
run: |
111+
WORK=$(mktemp -d)
112+
cd "$WORK"
113+
CXX="$LLVM_ROOT/bin/clang++.exe"
114+
test -x "$CXX" || CXX="$LLVM_ROOT/bin/clang++"
115+
116+
cat > main.cpp << 'EOF'
117+
#include <iostream>
118+
int main() {
119+
std::cout << "Hello from clang++ on Windows!" << std::endl;
120+
return 0;
121+
}
122+
EOF
123+
124+
echo "=== Compile ==="
125+
"$CXX" -std=c++23 -o hello.exe main.cpp 2>&1 || {
126+
echo "::warning::clang++ compilation failed, trying with MSVC headers"
127+
# clang++ on Windows may need MSVC include paths
128+
"$CXX" -std=c++23 --target=x86_64-pc-windows-msvc -o hello.exe main.cpp 2>&1 || true
129+
}
130+
131+
if [ -f hello.exe ]; then
132+
echo "=== Run ==="
133+
./hello.exe
134+
else
135+
echo "::warning::Compilation did not produce hello.exe"
136+
fi
137+
138+
- name: Test — clang-cl compilation
139+
shell: bash
140+
run: |
141+
WORK=$(mktemp -d)
142+
cd "$WORK"
143+
CLANGCL="$LLVM_ROOT/bin/clang-cl.exe"
144+
test -x "$CLANGCL" || CLANGCL="$LLVM_ROOT/bin/clang-cl"
145+
146+
cat > main.cpp << 'EOF'
147+
#include <iostream>
148+
int main() {
149+
std::cout << "Hello from clang-cl on Windows!" << std::endl;
150+
return 0;
151+
}
152+
EOF
153+
154+
echo "=== Compile with clang-cl ==="
155+
"$CLANGCL" /std:c++latest /EHsc main.cpp /Fe:hello.exe 2>&1 || {
156+
echo "::warning::clang-cl compilation failed"
157+
echo "clang-cl may need Visual Studio installation for MSVC headers/libs"
158+
}
159+
160+
if [ -f hello.exe ]; then
161+
echo "=== Run ==="
162+
./hello.exe
163+
fi
164+
165+
- name: Check libc++ availability for import std
166+
shell: bash
167+
run: |
168+
echo "=== Checking for libc++ in LLVM package ==="
169+
find "$LLVM_ROOT" -name "*.cppm" 2>/dev/null | head -5 || echo "No .cppm files found"
170+
find "$LLVM_ROOT" -path "*/c++/v1" -type d 2>/dev/null | head -3 || echo "No libc++ include dir"
171+
find "$LLVM_ROOT" -name "libc++*" 2>/dev/null | head -5 || echo "No libc++ library files"
172+
173+
echo
174+
echo "=== Checking MSVC STL for import std ==="
175+
# Visual Studio on GitHub runners includes MSVC STL with module support
176+
VSDIR="/c/Program Files/Microsoft Visual Studio/2022/Enterprise"
177+
if [ -d "$VSDIR" ]; then
178+
echo "Visual Studio found at: $VSDIR"
179+
find "$VSDIR" -name "std.ixx" 2>/dev/null | head -3 || echo "No std.ixx found"
180+
find "$VSDIR" -path "*/modules" -name "*.ixx" 2>/dev/null | head -5 || echo "No .ixx module files"
181+
else
182+
echo "Visual Studio not found at expected path"
183+
ls "/c/Program Files/Microsoft Visual Studio/" 2>/dev/null || true
184+
fi
185+
186+
- name: Summary
187+
shell: bash
188+
run: |
189+
echo "=== Windows LLVM Validation Summary ==="
190+
echo "LLVM Root: $LLVM_ROOT"
191+
echo "Clang version: $("$LLVM_ROOT/bin/clang++.exe" --version 2>/dev/null | head -1 || echo 'N/A')"
192+
echo "Target: $("$LLVM_ROOT/bin/clang++.exe" -dumpmachine 2>/dev/null || echo 'N/A')"
193+
echo
194+
echo "Has libc++: $([ -d "$LLVM_ROOT/include/c++/v1" ] && echo YES || echo NO)"
195+
echo "Has std.cppm: $(find "$LLVM_ROOT" -name 'std.cppm' 2>/dev/null | head -1 | grep -q . && echo YES || echo NO)"
196+
echo "Has clang-scan-deps: $([ -f "$LLVM_ROOT/bin/clang-scan-deps.exe" ] && echo YES || echo NO)"
197+
echo "Has clang-cl: $([ -f "$LLVM_ROOT/bin/clang-cl.exe" ] && echo YES || echo NO)"
198+
echo "Has lld-link: $([ -f "$LLVM_ROOT/bin/lld-link.exe" ] && echo YES || echo NO)"

0 commit comments

Comments
 (0)