目录
[1.2、判断条件,可以添加逻辑链接,NOT AND OR](#1.2、判断条件,可以添加逻辑链接,NOT AND OR)
上一章节
一、前言
之前文章中主要介绍了一些单个CMakeLists.txt中的语法,针对复杂项目构建时,往往需要包含多个子项目,因为引入了嵌套的cmake语法,同时也有的场景下需要对构建项目流程,以及平台属性进行控制判断。下面梳理了进一步对cmake语法介绍。
二、嵌套cmake
产生这种需求的场景时,当我们构建项目比较大时,写在一个cmakelists.txt中时会比较复杂,这里需要将项目构建模块进行封装抽象出来,写成一个个独立的子cmakelists.txt,这样每一个文件都不会太复杂,且功能相对专一,便于管理。
子cmakelists.txt文件一般与各个模块中源文件放在一起,不跟头文件放在一块,下面列举一个例子深入解析一下,此种项目的构建方式:
1、项目层次
这样构建的项目层次更加明确,且在编译大型项目时应用更加灵活,可以将各个模块功能如同搭积木一样串联起来。
通用命令add_subdirectory****, 将子节点下的CMakeLists.txt串联起来,添加子节点只需要在父节点中添加子节点所对应的文件夹名;
(1)父节点中定义的变量可以在子节点中使用,根节点中定义的变量为全局变量;
(2)子节点中变量,只能在子节点中使用,无法给父节点使用;
bash
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
(1)、source_dir:指定包含CMakeLists.txt的子目录位置;
(2)、binary_dir:指定输出文件路径,一般不需要指定,使用默认路径,可以忽略;
(3)、EXCLUDE_FROM_ALL:在子目录下的目标文件一般不会包含到父项目的目标文件中,并且也会被排除在IDE工程文件之外,需要手动显性构建子目录下的目标;
2、CMakeLists.txt内容
例如:

上图所示中包含了5个CMakeLists.txt文件;
2.1、整个项目的根节点:
bash
cmake_minimum_required(VERSION 3.15)
project(test)
# 定义变量
# 定义静态库生成路径
set(LIBPATH ${PROJECT_SOURCE_DIR}/lib)
# 定义可执行文件存储路径
set(EXECPATH ${PROJECT_SOURCE_DIR}/bin)
# 定义头文件路径
set(HEADPATH ${PROJECT_SOURCE_DIR}/include)
# 定义库文件名字
set(CALCLIB calc)
set(SORTLIB sort)
# 定义可执行文件名
set(APPNAME1 app1)
set(APPNAME2 app2)
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)
2.2、test1下的CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(test1)
aux_source_directory(./ SRC)
include_directories(${HEADPATH})
# 指定静态库地址
link_directories(${LIBPATH})
# 链接静态库
link_libraries(${CALCLIB})
# 指定可执行文件地址
set(EXECUTABLE_OUTPUT_PATH ${EXECPATH})
add_executable(${APPNAME1} ${SRC})
2.3、test2下的CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(test2)
aux_source_directory(./ SRC)
include_directories(${HEADPATH})
# 指定动态库地址
link_directories(${LIBPATH})
# 指定可执行文件地址
set(EXECUTABLE_OUTPUT_PATH ${EXECPATH})
add_executable(${APPNAME2} ${SRC})
target_link_libraries(${APPNAME2} ${SORTLIB})
2.4、calc下的CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(calc)
# 添加源文件
aux_source_directory(./ SRC)
# 添加头文件路径
include_directories(${HEADPATH})
set(LIB_OUTPUT_PATH ${LIBPATH})
# 创建静态库
add_library(${CALCLIB} STATIC ${SRC})
2.5、sort下的CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(sort)
# 添加源文件
aux_source_directory(./ SRC)
# 添加头文件路径
include_directories(${HEADPATH})
set(LIBRARY_OUTPUT_PATH ${LIBPATH})
# 创建动态库
add_library(${SORTLIB} SHARED ${SRC})
三、流程控制
在编译的过程中想控制编译的过程,可以编写流程控制的代码,控制整个代码编译的流程,以下是两种常见的控制流程的方式:
1、if语句
bash
if(<condition1>) # 判断条件1
<commands>
elseif(<condition2>) # 判断条件2
<commands>
else() # 可选块
<commands>
endif()
这里 if 跟 endif必须成对出现
1.1、常见的判断条件,可以是逻辑常量,变量,字符串等
如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True
如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回False
1.2、判断条件,可以添加逻辑链接,NOT AND OR
1.3、基于值的比较
(1)、数值比较
bash
if(<variable|string> LESS <variable|string>) # 小于
if(<variable|string> GREATER <variable|string>) # 大于
if(<variable|string> EQUAL <variable|string>) # 等于
if(<variable|string> LESS_EQUAL <variable|string>) # 小于等于
if(<variable|string> GREATER_EQUAL <variable|string>) # 大于等于
(2)、字符串比较
bash
if(<variable|string> STRLESS <variable|string>) # 小于
if(<variable|string> STRGREATER <variable|string>) # 大于
if(<variable|string> STREQUAL <variable|string>) # 等于
if(<variable|string> STRLESS_EQUAL <variable|string>) # 小于等于
if(<variable|string> STRGREATER_EQUAL <variable|string>) # 大于等于
1.4、文件判断
(1)、判断文件/文件夹是否存在
bash
if(EXISTS path-to-file-or-directory) # 文件或者目录存在,返回true
(2)、判断是不是文件夹
bash
if(IS_DIRECTORY path) # 这里path必须是绝对路径
(3)、判断某个元素是否在列表中
bash
if(<variable|string> IN_LIST <variable>)
cmake版本必须在3.3以上
2、循环语句
cmake 中循环实现是通过foreach和while
(1)、foreach语法
bash
foreach(<循环变量> [IN] <遍历对象>)
# 循环体:可执行任意 CMake 指令(如 message、set、if 等)
endforeach([<循环变量>]) # 结尾可省略循环变量,仅作可读性提示
示例1、固定列表的遍历:
bash
# 定义列表(空格/分号分隔)
set(COLORS red green blue "yellow orange")
# 遍历列表
foreach(COLOR IN ITEMS ${COLORS})
message(STATUS "Current color: ${COLOR}")
endforeach()
示例2、数值范围循环(含步长)
bash
# 示例2.1:基础范围(0到5,步长1)
message(STATUS "Range 0-5:")
foreach(NUM RANGE 5)
message(STATUS "Num: ${NUM}")
endforeach()
# 示例2.2:指定起止(2到8,步长1)
message(STATUS "\nRange 2-8:")
foreach(NUM RANGE 2 8)
message(STATUS "Num: ${NUM}")
endforeach()
# 示例2.3:带步长(1到10,步长3,CMake 3.12+)
message(STATUS "\nRange 1-10 step 3:")
foreach(NUM RANGE 1 10 3)
message(STATUS "Num: ${NUM}")
endforeach()
# 输出:
# -- Range 0-5:
# -- Num: 0; Num:1; ... Num:5
# -- Range 2-8:
# -- Num:2; Num:3; ... Num:8
# -- Range 1-10 step 3:
# -- Num:1; Num:4; Num:7; Num:10
(2)、while语法
CMake 2.6+ 支持
语法:
bash
while(<条件表达式>)
# 循环体:执行任意 CMake 指令(message/set/if 等)
# 【必做】需在循环体内修改条件变量,避免死循环
endforeach() # 注意:while 循环的结束标记仍是 endforeach()(CMake 语法设计特殊点)
示例1、基础数值循环(计数终止)
bash
# 初始化循环变量
set(COUNT 0)
# while 循环:COUNT < 5 时执行
while(${COUNT} LESS 5)
message(STATUS "Current count: ${COUNT}")
# 计数自增(关键:修改条件变量)
math(EXPR COUNT "${COUNT} + 1") # CMake 中数值运算需用 math(EXPR)
endforeach()
# 输出:
# -- Current count: 0
# -- Current count: 1
# -- Current count: 2
# -- Current count: 3
# -- Current count: 4
示例2:字符串条件终止
bash
set(TEXT_LIST "apple;banana;stop;orange;grape") # 定义列表
set(INDEX 0) # 索引变量
while(TRUE) # 无限循环(通过 break 终止)
# 取出列表中第 INDEX 个元素
list(GET TEXT_LIST ${INDEX} CURRENT_ITEM)
# 条件1:遍历到 "stop" 终止
if(${CURRENT_ITEM} STREQUAL "stop")
message(STATUS "Found 'stop', break loop")
break() # CMake 3.20+ 支持 break
endif()
# 条件2:索引超出列表长度,终止(避免越界)
list(LENGTH TEXT_LIST LIST_LEN)
if(${INDEX} GREATER_EQUAL ${LIST_LEN})
message(STATUS "End of list, break loop")
break()
endif()
message(STATUS "Current item: ${CURRENT_ITEM}")
math(EXPR INDEX "${INDEX} + 1") # 索引自增
endforeach()
# 输出:
# -- Current item: apple
# -- Current item: banana
# -- Found 'stop', break loop
注意事项
数值运算:CMake 中变量默认是字符串,数值自增 / 运算必须用 math(EXPR)(如 math(EXPR VAR "${VAR} + 1"))。
结束标记:while 循环的结束必须写 endforeach()(而非 endwhile(),CMake 语法设计的特殊点,容易踩坑)。
break/continue:
break():CMake 3.20+ 支持,立即终止循环;
continue():CMake 3.20+ 支持,跳过当前循环剩余代码,进入下一次循环。
作用域:循环内定义的变量默认作用域为当前 while 块,若需外部访问,需用 set(VAR VALUE PARENT_SCOPE) 导出。
四、补充:多配置生成器编译
我们在CMake编译时,往往会遇到,你编译debug/release 版本需要链接的库不一样,或者生成的库文件,需要根据debug/release版本指定到不同的路径下
这里列举如下实例:
bash
cmake_minimum_required(VERSION 3.5)
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(Kurn_Basic VERSION 2.0.0 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Quick Widgets Network Core5Compat)
# 基础配置
set(libname Basic)
include_directories(${PROJECT_SOURCE_DIR}/includes)
include_directories(${PROJECT_SOURCE_DIR}/includes/logger)
include_directories(${PROJECT_SOURCE_DIR}/includes/socket_com)
# 收集源文件,头文件
file(GLOB INCLUDE_HEADERS ${PROJECT_SOURCE_DIR}/includes/*.h*)
file(GLOB INCLUDE_LOG__HEADERS ${PROJECT_SOURCE_DIR}/includes/logger/*.h*)
file(GLOB LOG_SOURCES ${PROJECT_SOURCE_DIR}/src/logger/*.c*)
file(GLOB INCLUDE_SOCKET_HEADERS ${PROJECT_SOURCE_DIR}/includes/socket_com/*.h*)
file(GLOB SOCKET_SOURCES ${PROJECT_SOURCE_DIR}/src/socket_com/*.c*)
# 生成动态库
add_library(${libname} SHARED ${INCLUDE_HEADERS} ${INCLUDE_LOG__HEADERS} ${INCLUDE_SOCKET_HEADERS} ${LOG_SOURCES} ${SOCKET_SOURCES})
target_link_libraries(${libname} PRIVATE Qt6::Core Qt6::Network Qt6::Widgets Qt6::Core5Compat)
# 生成导出头文件
include(GenerateExportHeader)
generate_export_header(${libname} BASE_NAME ${libname})
file(COPY ${PROJECT_BINARY_DIR}/${libname}_export.h DESTINATION ${PROJECT_SOURCE_DIR}/includes)
# 设置所有配置的默认输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../basicfuncs_build/)
# 针对多配置生成器,为 Debug/Release 配置单独设置输出目录
if(CMAKE_CONFIGURATION_TYPES)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../basicfuncs_build/install_${PROJECT_VERSION}_debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../basicfuncs_build/install_${PROJECT_VERSION}_release)
endif()
# 禁用自动生成的 Debug/Release 子目录
set_target_properties(${libname} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/lib"
LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/lib"
ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/lib"
ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/lib"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/bin"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/bin"
DEBUG_POSTFIX "d"
)
# 关键修改:确保 include 文件夹被创建并包含头文件
install(DIRECTORY ${PROJECT_SOURCE_DIR}/includes/
DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/include
FILES_MATCHING PATTERN "*.h*"
)
# 将配置文件拷贝到安装路径下
install(DIRECTORY ${PROJECT_SOURCE_DIR}/config/
DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/config
FILES_MATCHING PATTERN "*.ini"
)
分编译类型使用不同版本库:
bash
# 设置所需的最低CMake版本
cmake_minimum_required(VERSION 3.5)
# 设置项目名称为"lightSDK",使用的编程语言为C++。
project(lightSDK VERSION 1.3.1 LANGUAGES CXX)
# 启用自动UI编译(AUTOUIC)
set(CMAKE_AUTOUIC ON)
# 自动元对象编译(AUTOMOC)
set(CMAKE_AUTOMOC ON)
# 自动资源编译(AUTORCC)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick Widgets Core Gui Network Core5Compat)
# 基础配置
set(libname RseeLib)
set(appname Light_Demo)
set(KBasic_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/basicfuncs_2.0.0)
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${KBasic_DIR}/include)
include_directories(${KBasic_DIR}/include/logger)
include_directories(${KBasic_DIR}/include/socket_com)
file(GLOB INCLUDE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h*)
file(GLOB LIGHT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c*)
# 添加动态库
add_library(${libname} SHARED ${INCLUDE_HEADERS} ${LIGHT_SOURCES})
target_link_libraries(${libname} PRIVATE Qt6::Core Qt6::Network Qt6::Core5Compat Qt6::Widgets )
target_link_libraries(${libname} PRIVATE
$<$<CONFIG:Debug>:${KBasic_DIR}/debug/lib/Kurn_Basicd.lib> # 导入debug 版本库
$<$<CONFIG:Release>:${KBasic_DIR}/release/lib/Kurn_Basic.lib> # 导入release 版本库
)
# 生成导出头文件
include(GenerateExportHeader)
generate_export_header(${libname} BASE_NAME ${libname})
file(COPY ${PROJECT_BINARY_DIR}/${libname}_export.h DESTINATION ${PROJECT_SOURCE_DIR}/include) # 将export header复制到项目include目录下
# 设置所有配置的默认输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../lightsdk_build/)
# 针对多配置生成器,为 Debug/Release 配置单独设置输出目录
if(CMAKE_CONFIGURATION_TYPES)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../lightsdk_build/install_${PROJECT_VERSION}_debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../lightsdk_build/install_${PROJECT_VERSION}_release)
endif()
set_target_properties(${libname} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/lib"
LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/lib"
ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/lib"
ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/lib"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/bin"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/bin"
DEBUG_POSTFIX "d" # debug版本文件名后加上d
)
# 关键修改:确保 include 文件夹被创建并包含头文件
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/include
FILES_MATCHING PATTERN "*.h*"
)
# 将日志文件导入到发布路径下
install(FILES CHANGELOG.md DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
五、结语
至此,CMake 核心语法体系已梳理完毕。从基础变量定义、条件判断,到实用的 foreach 列表遍历、while 条件循环,这些语法覆盖了工程构建中的配置管理、文件处理、流程控制等核心场景。CMake 语法注重实用性与跨平台兼容性,其简洁的指令风格与灵活的扩展能力,能有效适配从简单项目到复杂大型工程的构建需求。
学习 CMake 的关键在于理解其 "配置优先、跨平台适配" 的设计理念,在实际应用中需注重语法版本兼容性(如高版本特性的降级处理),并结合工程实际场景灵活组合指令。后续可通过实践积累(如批量配置编译选项、自定义构建流程)深化对语法的理解,让 CMake 成为高效管理项目构建的有力工具。