如何在CMake中实现Makefile的变量校验失败逻辑?
解答
关于CMake条件语句的执行时机
CMake中if(NOT DEFINED LIBUV)这类条件语句是在**CMake配置阶段(即运行cmake命令时)**执行的,而非后续运行make阶段。这和原Makefile的逻辑本质不同:Makefile的检查是在make执行阶段触发,而默认的CMake逻辑是在配置阶段完成变量检查。
如果要实现**make阶段才触发检查**的效果,需要借助自定义脚本或构建规则;但更符合CMake设计模式的做法是在配置阶段完成检查,因为CMake的核心是预配置生成构建文件。以下提供两种方案:
方案1:配置阶段检查(推荐,符合CMake设计)
若接受在cmake配置阶段触发检查,补全逻辑即可实现等效规则。原Makefile的核心规则为:
- 除
clean目标外,所有构建需检查变量 - 检查规则:
- 未定义
LIBUV时:Windows系统必须定义VCPKG_DIR,非Windows系统必须定义PRJ_ROOT - 未定义
SPA时,必须定义PRJ_ROOT
- 未定义
对应的CMake代码:
cmake_minimum_required(VERSION 3.12) project(learn-cmake) # 模拟原Makefile中跳过clean目标检查的逻辑 # CMake无直接对应MAKECMDGOALS的变量,通过BUILD_CLEAN参数控制是否跳过检查 if(NOT DEFINED BUILD_CLEAN) # 检查LIBUV相关规则 if(NOT DEFINED LIBUV) if(CMAKE_SYSTEM_NAME STREQUAL "Windows_NT") if(NOT DEFINED VCPKG_DIR) message(FATAL_ERROR "must define one of LIBUV or VCPKG_DIR") endif() else() if(NOT DEFINED PRJ_ROOT) message(FATAL_ERROR "must define one of LIBUV or PRJ_ROOT") endif() endif() endif() # 检查SPA相关规则 if(NOT DEFINED SPA) if(NOT DEFINED PRJ_ROOT) message(FATAL_ERROR "must define one of SPA or PRJ_ROOT") endif() endif() endif() # 模拟原Makefile的all目标变量输出 add_custom_target(all COMMAND ${CMAKE_COMMAND} -E echo "LIBUV: $ENV{LIBUV}" COMMAND ${CMAKE_COMMAND} -E echo "VCPKG_DIR: $ENV{VCPKG_DIR}" COMMAND ${CMAKE_COMMAND} -E echo "SPA: $ENV{SPA}" COMMAND ${CMAKE_COMMAND} -E echo "PRJ_ROOT: $ENV{PRJ_ROOT}" )
使用方式:
- 直接运行
cmake .会触发检查,不满足条件则配置失败 - 若要跳过检查执行clean,运行
cmake -DBUILD_CLEAN=ON . && make clean
方案2:构建阶段(make时)检查(完全匹配原Makefile行为)
若需严格匹配原Makefile在make阶段触发检查的行为,可通过生成自定义检查脚本实现:
cmake_minimum_required(VERSION 3.12) project(learn-cmake) # 生成Shell检查脚本(Windows系统需替换为.bat脚本) file(WRITE ${CMAKE_BINARY_DIR}/check_vars.sh " #!/bin/bash if [ \"\$MAKECMDGOALS\" != \"clean\" ]; then if [ -z \"\$LIBUV\" ]; then OS=\$(uname) if [[ \"\$OS\" == *"Windows_NT"* ]]; then if [ -z \"\$VCPKG_DIR\" ]; then echo \"Error: must define one of LIBUV or VCPKG_DIR\" >&2 exit 1 fi else if [ -z \"\$PRJ_ROOT\" ]; then echo \"Error: must define one of LIBUV or PRJ_ROOT\" >&2 exit 1 fi fi fi if [ -z \"\$SPA\" ]; then if [ -z \"\$PRJ_ROOT\" ]; then echo \"Error: must define one of SPA or PRJ_ROOT\" >&2 exit 1 fi fi fi ") # 赋予脚本执行权限 execute_process(COMMAND chmod +x ${CMAKE_BINARY_DIR}/check_vars.sh) # 定义all目标:先执行检查脚本,再输出变量 add_custom_target(all COMMAND ${CMAKE_BINARY_DIR}/check_vars.sh COMMAND ${CMAKE_COMMAND} -E echo "LIBUV: $ENV{LIBUV}" COMMAND ${CMAKE_COMMAND} -E echo "VCPKG_DIR: $ENV{VCPKG_DIR}" COMMAND ${CMAKE_COMMAND} -E echo "SPA: $ENV{SPA}" COMMAND ${CMAKE_COMMAND} -E echo "PRJ_ROOT: $ENV{PRJ_ROOT}" )
使用方式与原Makefile完全一致:
$ make Error: must define one of LIBUV or PRJ_ROOT make: *** [all] Error 1 $ make LIBUV=foo Error: must define one of SPA or PRJ_ROOT make: *** [all] Error 1 $ make LIBUV=foo SPA=bar LIBUV: foo VCPKG_DIR: SPA: bar PRJ_ROOT: make: Nothing to be done for 'all'.
注:Windows系统需将Shell脚本替换为批处理脚本,可通过CMake的条件判断生成对应脚本。
内容的提问来源于stack exchange,提问作者StoneThrow
相关产品推荐
相关产品推荐

