CLion 使用 clang-cl 工具链时未正确链接 vcpkg 中的 Boost 外部库
临时解决办法: 手动修改 CMake 缓存.

TL;DR: 简单来说是 CLion 的问题; 解决方法就是避免使用 clang-cl 工具链.

2023/02/24 更新: 最新的 CLion EAP 似乎已经修正了这个问题.


昨天比较闲, 就给 vcpkg 里的包都升级了一下, 自然是要重新构建 (大概花了 4 个小时). 说来最近 vcpkg 好像会自动清理掉构建过程的缓存了, 可能是觉得, 对于大多数用户, 或者说作为一款包管理器, 留着 buildtrees 的实际价值不大吧, 毕竟需要自行修改代码进行构建的用户可能也不会从包管理器里面去安装依赖.

升级完后, 自然是想体验一下, 于是打开 CLion 新建一个项目, 引入 Boost 依赖, 找一个简单的 demo 试一下能不能用, 然后就遇到了问题.

"C:\Program Files\CMake\bin\cmake.exe" --build D:\repos\boost-demos\cmake-build-debug --target bt -j 6
[1/2] Building CXX object CMakeFiles\bt.dir\bt.cpp.obj
[2/2] Linking CXX executable bt.exe
FAILED: bt.exe 
cmd.exe /C "cd . && "C:\Program Files\CMake\bin\cmake.exe" -E vs_link_exe --intdir=CMakeFiles\bt.dir --rc="D:\Windows Kits\10\bin\10.0.22621.0\x64\rc.exe" --mt="D:\Windows Kits\10\bin\10.0.22621.0\x64\mt.exe" --manifests  -- "D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin\lld-link.exe" /nologo CMakeFiles\bt.dir\bt.cpp.obj  /out:bt.exe /implib:bt.lib /pdb:bt.pdb /version:0.0 /machine:x64 /debug /INCREMENTAL /subsystem:console  D:\repos\vcpkg\installed\x64-windows\debug\lib\boost_program_options-vc140-mt-gd.lib  kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib && cmd.exe /C "cd /D D:\repos\boost-demos\cmake-build-debug && C:\Users\Henry\AppData\Local\Microsoft\WindowsApps\pwsh.exe -noprofile -executionpolicy Bypass -file D:/repos/vcpkg/scripts/buildsystems/msbuild/applocal.ps1 -targetBinary D:/repos/boost-demos/cmake-build-debug/bt.exe -installedDir D:/repos/vcpkg/installed/x64-windows/debug/bin -OutVariable out""
LINK Pass 1: command "D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin\lld-link.exe /nologo CMakeFiles\bt.dir\bt.cpp.obj /out:bt.exe /implib:bt.lib /pdb:bt.pdb /version:0.0 /machine:x64 /debug /INCREMENTAL /subsystem:console D:\repos\vcpkg\installed\x64-windows\debug\lib\boost_program_options-vc140-mt-gd.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTFILE:CMakeFiles\bt.dir/intermediate.manifest CMakeFiles\bt.dir/manifest.res" failed (exit code 1) with the following output:
lld-link: error: undefined symbol: void __cdecl boost::throw_exception(class std::exception const &)
>>> referenced by D:\repos\vcpkg\installed\x64-windows\include\boost\function\function_template.hpp:761
>>>               CMakeFiles\bt.dir\bt.cpp.obj:(public: void __cdecl boost::function1<void, int const &>::operator()(int const &) const)
>>> referenced by D:\repos\vcpkg\installed\x64-windows\include\boost\program_options\detail\value_semantic.hpp:62
>>>               CMakeFiles\bt.dir\bt.cpp.obj:(class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>> const & __cdecl boost::program_options::validators::get_single_string<char>(class std::vector<class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>, class std::allocator<class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>>> const &, bool))
>>> referenced by D:\repos\vcpkg\installed\x64-windows\include\boost\program_options\detail\value_semantic.hpp:66
>>>               CMakeFiles\bt.dir\bt.cpp.obj:(class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>> const & __cdecl boost::program_options::validators::get_single_string<char>(class std::vector<class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>, class std::allocator<class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char>>>> const &, bool))
>>> referenced 2 more times
ninja: build stopped: subcommand failed.

CMakeLists 如下:

cmake_minimum_required(VERSION 3.22)
project(Boost-Demos)

set(CMAKE_CXX_STANDARD 14)

find_package(Boost REQUIRED COMPONENTS program_options)

message("Boost libraries: ${Boost_LIBRARIES}")

add_executable (bt "bt.cpp")
target_link_libraries(bt Boost::boost ${Boost_LIBRARIES})

是老生常谈的 undefined symbol (未定义符号), 然后一看似乎是 Boost 库里的符号, 那么也许是少链接了某个 Boost 的库吧? 毕竟 Boost 目前好像还没有官方正式的 CMake 支持, vcpkg 也有出问题的可能. 因此, 猜测可能是 CMake Target 有些 bug 的地方.

但是直接查相应的错误, 然而发现没有结果. 由于看到 vcpkg 前几天刚推出的 Release, 所以也怀疑过是 vcpkg 的问题, 但是看到 Boost 库的 port 好像是几个月前更新的了, 这么长时间也没有用户提出相关的 issue, 应该 vcpkg 本身没问题.

差点就冲动去给 vcpkg 提 issue 了, 虽然解决问题的过程中间实在找不到办法的时候还是发了个 discussion.

于是只能从自己这边找问题.

先是尝试在 CMakeLists.txt 里面给目标多链接一个 Boost 的 throw_exception 库, 结果也没有任何变化.

首先在其他地方重新安装了一份 vcpkg, 发现没有问题, 同时还在 WSL 2 Ubuntu 上进行了测试, 构建十分顺滑没有问题, 于是排除了项目的 CMakeLists.txt 以及 Boost 和 vcpkg 自身的问题.

并且, 通过检测生成的构建脚本, 发现确实只链接了 program_options 这一个库. 根据报错信息里边的构建命令发现, 实际上也是指定了要链接这个库.

这样一来就说明, CMake 所解出的要链接的库, 以及生成的链接命令都没有差错, 那么可能存在问题的地方就只剩下编译器工具上了.

可笑的是, 笔者直到这时才想起来换一下构建工具试试. 先是换了 IDE, 打开 Visual Studio, 其默认使用的是自己的 MSVC 构建工具链, 结果成功构建没有问题, 更换 Clang for MSVC 也没有问题; 打开 Visual Studio Code, 然而 CMake Tools 插件从生成到构建也没有问题.

这样一来就是施展控制变量法的好时机了. 在 CLion 和其他工具中使用相同的工具集, 也就是 clang-cl, 即 Clang (MSVC CLI) for MSVC, 然后比对不同环境下 CMake 生成的 CMakeCache.txt, 发现 CMAKE_C_FLAGSCMAKE_CXX_FLAGS 变量的值有蹊跷: CLion 生成的CMake 缓存里面, CMAKE_C_FLAGS 等变量处只有 -m64, 而缺失了 /DWIN32 /D_WINDOWS /W3 /GR /EHsc 等参数.

既然发现 CMakeCache.txt 这里有问题, 那问题应该就出在这之前的步骤, 也就是 CMake 的生成步骤. 通过比较不同 IDE 所执行的 CMake 命令发现, CLion 这边多传递了 -DCMAKE_C_FLAGS=-m64-DCMAKE_CXX_FLAGS=m64 这两个选项给 CMake.

而观察 Visual Studio 生成的 CMake 缓存, 发现虽然附加了 -m64 这个参数 (进行 CMake 生成时候也提示拾取了这两个环境变量), 但是也有上面提到的 “缺失” 的参数.

笔者更换了一个 CLion 的版本, 发现情况一致: 即, 在使用 clang-cl 时, CLion 就会给出这两个选项, 而使用 GNU 风格命令行选项版本的 Clang (GNU CLI) for MSVC (clang) 时, CLion 就不会传递这两个选项.

目前笔者并不清楚是 CLion 自身的问题还是笔者本人的配置问题, 抑或是 MSVC 的问题, 但是目前感觉可能主要是 Visual Studio MSVC 的问题, 而 CLion 在识别并应用 Visual Studio 工具链的方式导致了问题的发生.

感觉是 Visual Studio 和 CLion 的处理方式不同, Visual Studio 是把环境中的 C/C++ 编译器选项参数附加到了 CMake 生成的缓存里, 而 CLion 是直接认为这个参数应该覆盖掉 CMake 的选项. 而之所以认为是 BUG, 是因为 -m64 并不是 CMD 风格的命令行选项.

毕竟, 对于不需要链接外部库的项目来说, 这些选项存在与否好像没有什么影响. 而在笔者把这些缺失的命令行选项手动加回到 CMake 缓存后, CMake 生成的构建脚本就能正常构建并链接到 Boost 库了.

遗憾的是, 笔者暂时没有更多时间精力去调查清楚, 比如实验一下对于其他的外部库是否存在该问题, 还有研究一下 CLion 使用 Visual Studio 开发环境的方式, 找出来到底是那个环节出了问题.

总而言之, 目前的解决办法是, 使用 clang 而不是 clang-cl.


Last modified on 2023-02-24