Skip to content

Windows/MSVC 迁移实践:6 个问题(build.mcpp 不可用 / cppwinrt / include_dirs glob / ldflags 相对路径 / linkage 文档 / path 索引注册) #331

Description

@Sunrisepeak

把一个 66k 行的 Windows/MSVC 桌面应用(SpinningMomo,原 xmake + vcpkg)迁到 mcpp,最终构建成功了。过程中遇到 6 个问题,其中 4 个是实现缺陷、2 个是文档与实现不一致。都已绕过,绕行方式一并附上。

环境:mcpp 2026.7.31.1 / xlings 2026.7.28.4 · GitHub Actions windows-latest · MSVC 19.51.36252(VS18 Enterprise,VC tools 14.51.36231)· Windows SDK 10.0.26100.0 · 目标 x86_64-windows-msvc

按影响面排序。


1. build.mcpp 在 Windows + MSVC 下完全不可用

现象:任何带 build.mcpp 的工程在 Windows 上都构建不了,且 build.mcpp 里的代码一行都没机会执行:

error: build.mcpp failed to compile (exit 1):
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.

原因src/build/build_program.cppm:699capture_exec(compileArgv, ...) 编译 build.mcpp,而 Windows 上 capture_execcmd.execommand_from_argvsrc/platform/process.cppm:234-240刻意argv[0] 原样保留不加引号:

// The first token (program) is kept RAW on Windows — quoting it would make
// cmd.exe's `/c "..."` strip the outer quotes and mangle the path
#if defined(_WIN32)
    std::string cmd = argv[0];
#endif

这个取舍在 argv[0] 不含空格时成立,但 MSVC 的 cl.exe 路径必然含空格(C:\Program Files\Microsoft Visual Studio\...)。

影响build.mcpp 是构建期逻辑的正规入口(代码生成、资源编译、探测宿主)。在 Windows + MSVC 上它整体不可用,这些逻辑只能移到 mcpp 之外的脚本里,也就失去了重跑缓存和 MCPP_* 环境契约。

绕行:把 build.mcpp 的内容改写成一个外部 prebuild.sh,在 mcpp build 之前跑。

顺带:mcpp:link-lib=<name>build_program.cppm:132 被硬拼成 "-l" + val,MSVC 方言下不可用。即使 #1 修好,这条指令在 msvc 上仍然没有意义。


2. C++/WinRT 投影头目录不在合成的 INCLUDE

现象:任何用 C++/WinRT 的工程在 MSVC 上编不过:

fatal error C1083: Cannot open include file: 'winrt/Windows.Foundation.h': No such file or directory

原因src/toolchain/msvc.cppm:455-461 合成 INCLUDE 时只加了四个目录:

env.push_back({"INCLUDE", join({
    tools / "include",
    sdk.root / "Include" / sdk.version / "ucrt",
    sdk.root / "Include" / sdk.version / "um",
    sdk.root / "Include" / sdk.version / "shared",
    sdk.root / "Include" / sdk.version / "winrt",
})});

这里的 winrtABI 头目录(小写的 windows.foundation.h,C 风格接口),不是 C++/WinRT 投影。投影头在 Include/<ver>/cppwinrt/winrt/*.h,而且在部分 SDK 布局下根本不预置,需要 cppwinrt.exe -in local -out <dir> 现场生成。

影响:Windows.Graphics.Capture、WinUI 等一切基于 C++/WinRT 的代码在 mcpp 下开箱不可用。

绕行:prebuild 里探测 Include/<ver>/cppwinrt,没有就用 cppwinrt.exe -in local 生成到项目内,再经 [build] include_dirs 引入。

建议cppwinrt 目录存在时加进 INCLUDE(与 winrt 并列即可)。


3. 绝对路径的 include_dirs 条目跳过 glob 展开

原因src/build/plan.cppm:261-270

expand_manifest_include_entry(const std::filesystem::path& root,
                              const std::filesystem::path& inc)
{
    if (inc.is_absolute()) return { inc };   // ← 绝对路径原样返回,不展开
    const auto glob = inc.generic_string();
    auto expanded = mcpp::modgraph::expand_dir_glob(root, glob);
    ...
}

相对路径条目会走 expand_dir_glob,绝对路径直接原样返回。

复现

[build]
include_dirs = ["C:/Program Files (x86)/Windows Kits/10/Include/*/cppwinrt"]

字面量 * 直达 cl.exe。实际观察到的表现是这一条被拆成了三个参数并落到源文件位置

c1xx: fatal error C1083: Cannot open source file: 'Files'
c1xx: fatal error C1083: Cannot open source file: '(x86)/Windows'
c1xx: fatal error C1083: Cannot open source file: 'Kits/10/Include/*/cppwinrt'

src/build/flags.cppm:238-242 明确对每个 include token 做了 shell_quote_arg,所以拆分这一半我没能定位到具体环节 —— 这部分作为观察到的现象报告,不作断言。跳过 glob 展开那一半是确定的。

绕行:由 prebuild 解析出真实版本目录,在项目内建一个无空格的 junction,manifest 里只写项目相对路径。


4. ldflags 的文件输入不能用工程相对路径,且与 build.mcpp 的行为不一致

现象

[build]
ldflags = ["gen/app.res"]     # rc.exe 产出的资源
LINK : fatal error LNK1181: cannot open input file 'gen\app.res'

原因ldflags 原样进链接行(src/build/ninja_backend.cppm:409),而 ninja/link.exe 的 cwd 是 target/<triple>/<hash>/,工程相对路径当然解析不到。

不一致点docs/07-build-mcpp.md:49 明确写着 mcpp:link-search=<dir>相对路径按工程根解析。同一件事在两个通道上语义相反,而 manifest 这一侧没有任何说明。

绕行ldflags = ["../../../gen/app.res"] —— 依赖 target/<triple>/<hash> 这个三层布局,很脆。

建议:要么让 [build] ldflags 里长得像路径的条目按工程根解析(与 link-search 对齐),要么给 [build] 补一个 link_search / link_inputs 字段。


5. 文档在 [build] 下举例了 linkage,但它不是 [build] 的键

现象

warning: [build] has unsupported key 'linkage' (ignored). Supported keys: sources,
cflags, cxxflags, ldflags, defines, flags, include_dirs, include_dirs_after,
dialect_cxxflags, c_standard, target, static_stdlib, allow_host_libs, cache,
profile, macos_deployment_target.

文档侧

  • docs/03-toolchains.md:129 —— `[build] linkage = "dynamic"` opts out
  • docs/03-toolchains.md:183 —— `[build] linkage = "static"` selects the `/MT` CRT ← 正是 MSVC 那一节
  • docs/05-mcpp-toml.md:484 则把它放在 [target.<triple>]

第二条尤其误导:它在 MSVC 小节里把 [build] linkage 说成选 /MT 的方式,而实际上这个键被静默忽略,所有 TU 仍然编 /MD

影响:见 #6 的 CRT 问题。绕行:显式 [target.'cfg(windows)'.build] cflags/cxxflags = ["/MT"]

建议:改掉 03-toolchains.md 的两处;或者在 [build] 下把它实现成 [target.*] 的简写。


6. path 索引的注册落在沙盒 xlings 的配置里,mcpp.toml[indices] 单独不够

这一条最隐蔽 —— 它的表现是本地能跑、CI 不能,而两边的 mcpp.toml 完全一样。

现象:同一个工程,本地首次构建就成功;CI 上(全新 runner)永远失败,且 mcpp 与 xlings 的诊断互相矛盾:

mcpp:   1 index repo configured [sm -> D:/a/SpinningMomo/SpinningMomo/mcpp]
xlings: E_NOT_FOUND: package 'sm:reflectcpp@0.25.0' not found
        (hint: searched repos: [xim, mcpplibs])

mcpp index list 在 CI 上也能正确列出:

Project indices (mcpp.toml):
  sm               ../../mcpp  (local path)

原因:mcpp 把 [indices] 物化进 <project>/.mcpp/.xlings.json,但真正解析包的那个沙盒 xlings 以 $MCPP_HOME/registry 为 home(~/.mcpp/config.toml[xlings] home = ""),它读的是 ~/.mcpp/registry/.xlings.jsonindex_repos,不看项目那份。

一个长期存在的本地 checkout 早就被某次 mcpp index add 或某次运行写进去了,所以一直是绿的;CI runner 永远不会有。

绕行:CI 里显式 mcpp index add <name> <path>(它写的正是沙盒那份)。

建议:至少让诊断能对上 —— mcpp 报告「configured」而 xlings 报告「searched repos」里没有它,这组信息把人往查找逻辑上带,实际是注册落点的问题。理想情况下 [indices] 的 path 索引应当在解析前自动注册到沙盒。


附:迁移结果

除上述之外一切顺利。整个迁移只改了两行业务代码(66,156 行 / 180 个 TU):一处 include 路径(vcpkg 的安装布局与上游 tarball 不同),一处 asio 1.30 移除的 cancel(error_code&) 重载。

export using ::HWND; 这类把 Win32 符号从模块重导出回全局命名空间的写法在 MSVC 上工作正常;C++/WinRT 在具名模块的 GMF 里也完全可用(解决 #2 之后)。这两点对 Windows 项目的模块化很关键,可以考虑写进文档。

需要的话我可以把任意一条拆成单独 issue,或者提 PR。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions