如何在CMake中按目标系统条件设置-isystem与-L编译链接标志?
我有一个需同时为Linux和Windows编译的项目,Linux环境下编译Linux版本,Windows版本使用w64devkit编译,无需考虑单主机交叉编译。
针对Windows构建,所有编译文件需添加编译标志-isystem $(VCPKG_DIR)/installed/x64-windows/include和链接标志-L $(VCPKG_DIR)/installed/x64-windows/lib,其中$(VCPKG_DIR)为vcpkg的安装根目录。(为简化说明,下文聚焦包含路径)
我原本考虑用CMake Presets实现,示例配置如下:
{ "version": 1, "cmakeMinimumRequired": { "major": 3, "minor": 19, "patch": 0 }, "configurePresets": [ { "name": "linux", "displayName": "Build for Linux", "description": "Linux build using make generator", "generator": "Unix Makefiles", "binaryDir": "${sourceDir}/build" }, { "name": "win64", "displayName": "Build for Windows", "description": "Windows build using make generator", "generator": "Unix Makefiles", "binaryDir": "${sourceDir}/build", "CMAKE_C_FLAGS": "${VCPKG_DIR}/installed/x64-windows/include" } ] }
但从相关回答了解到,设置包含路径的规范方式是使用target_include_directories()(或target_sources())。这合理,但我的项目有多个同级目录,每个目录都有CMakeLists.txt,若按此方式需在每个文件中添加类似:
target_include_directories(submodule SYSTEM ${VCPKG_DIR}/installed/x64-windows/include)
的代码,存在重复问题。我希望能在一处统一配置,即“若为Windows构建,始终添加-I ${VCPKG_DIR}/installed/x64-windows/include”,这似乎符合CMake Presets的设计意图。
若此方式不可行或非最佳实践,那如何在各CMakeLists.txt中按构建目标条件添加该包含路径?是否需要写成:
if(WIN64) target_include_directories(my_executable SYSTEM "${VCPKG_DIR}/installed/x64-windows/include") endif()
我的核心问题是:在多个CMakeLists.txt中,按构建目标条件设置包含路径的最精简(最少重复)或符合CMake规范的方式是什么? 换句话说:若逻辑为“构建win64目标时,始终将${VCPKG_DIR}/installed/x64-windows/include设为系统包含路径”,在CMake中实现此逻辑的最佳(或规范)方式是什么?
方案1:通过CMake Presets统一设置全局编译/链接标志(推荐,匹配你的需求)
你最初的思路方向是对的,CMake Presets完全可以实现全局统一配置,只需修正CMAKE_C_FLAGS的写法——直接给CMAKE_C_FLAGS赋值时要加上-isystem前缀,同时用预设的环境变量或缓存变量传递VCPKG_DIR:
修改后的CMakePresets.json:
{ "version": 1, "cmakeMinimumRequired": { "major": 3, "minor": 19, "patch": 0 }, "configurePresets": [ { "name": "linux", "displayName": "Build for Linux", "description": "Linux build using make generator", "generator": "Unix Makefiles", "binaryDir": "${sourceDir}/build/linux" }, { "name": "win64", "displayName": "Build for Windows", "description": "Windows build using make generator", "generator": "Unix Makefiles", "binaryDir": "${sourceDir}/build/win64", "cacheVariables": { "VCPKG_DIR": "$env{VCPKG_DIR}", "CMAKE_C_FLAGS": "-isystem ${VCPKG_DIR}/installed/x64-windows/include", "CMAKE_EXE_LINKER_FLAGS": "-L ${VCPKG_DIR}/installed/x64-windows/lib" } } ] }
- 注意把
binaryDir分开,避免Linux和Windows构建文件混在一起 - 用
cacheVariables传递变量更规范,$env{VCPKG_DIR}会读取系统环境变量中的VCPKG路径 - 直接给
CMAKE_C_FLAGS加上-isystem,链接标志同理添加-L
这种方式完全不需要修改任何CMakeLists.txt,所有Windows构建的目标都会自动继承这些全局标志,实现一处配置全局生效。
方案2:在根CMakeLists.txt中设置全局包含路径(规范的CMake目标导向方式)
如果更倾向于CMake的目标导向规范,可以在根目录的CMakeLists.txt中统一设置,让所有子目录的目标自动继承:
# 根CMakeLists.txt if(WIN64) # 全局设置系统包含路径,所有后续创建的目标都会继承 include_directories(SYSTEM "${VCPKG_DIR}/installed/x64-windows/include") # 全局设置链接库路径 link_directories("${VCPKG_DIR}/installed/x64-windows/lib") endif()
include_directories(SYSTEM ...)会把路径设为系统包含目录,等价于-isystem- 根目录设置后,所有子目录的
CMakeLists.txt无需重复添加,目标会自动继承这些路径 - 若需要更精细的控制(比如只给部分目标添加),可以在根目录定义一个函数,子目录调用:
# 根CMakeLists.txt function(add_win64_system_includes target) if(WIN64) target_include_directories(${target} SYSTEM "${VCPKG_DIR}/installed/x64-windows/include") target_link_directories(${target} PRIVATE "${VCPKG_DIR}/installed/x64-windows/lib") endif() endfunction() # 子目录CMakeLists.txt add_executable(my_executable ...) add_win64_system_includes(my_executable)
这种方式既符合CMake的目标导向规范,又避免了重复代码,只需在子目录调用函数即可。
方案3:条件判断+全局变量(适合需要动态调整的场景)
如果需要根据不同预设动态切换,也可以在CMake Presets中定义一个缓存变量,然后在根CMakeLists.txt中判断:
在CMakePresets.json的win64预设中添加:
"cacheVariables": { "BUILD_WIN64": "ON", "VCPKG_DIR": "$env{VCPKG_DIR}" }
然后在根CMakeLists.txt中:
if(BUILD_WIN64) include_directories(SYSTEM "${VCPKG_DIR}/installed/x64-windows/include") link_directories("${VCPKG_DIR}/installed/x64-windows/lib") endif()
总结
- 如果你追求零修改子目录CMakeLists.txt,方案1(CMake Presets全局标志)是最精简的选择
- 如果你更遵循CMake目标导向的规范,方案2(根目录设置或定义函数)更合适
- 方案3适合需要更灵活开关的场景
内容的提问来源于stack exchange,提问作者StoneThrow

