四、cmake语法-结语篇

目录

上一章节

一、前言

二、嵌套cmake

1、项目层次

2、CMakeLists.txt内容

2.1、整个项目的根节点:

2.2、test1下的CMakeLists.txt

2.3、test2下的CMakeLists.txt

2.4、calc下的CMakeLists.txt

2.5、sort下的CMakeLists.txt

三、流程控制

1、if语句

1.1、常见的判断条件,可以是逻辑常量,变量,字符串等

[1.2、判断条件,可以添加逻辑链接,NOT AND OR](#1.2、判断条件,可以添加逻辑链接,NOT AND OR)

1.3、基于值的比较

(1)、数值比较

(2)、字符串比较

1.4、文件判断

(1)、判断文件/文件夹是否存在

(2)、判断是不是文件夹

(3)、判断某个元素是否在列表中

2、循环语句

(1)、foreach语法

(2)、while语法

四、补充:多配置生成器编译

五、结语


上一章节

三、cmake语法-提高篇-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/154040217?spm=1001.2014.3001.5502

一、前言

之前文章中主要介绍了一些单个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 成为高效管理项目构建的有力工具。

相关推荐
唐·柯里昂7987 天前
野火鲁班猫5使用正点原子 RTL8188EUS Wifi模块驱动移植(Linux5.10 Debian系统) 解决zsh报错
linux·c语言·mcu·物联网·ubuntu·硬件工程·软件构建
询问QQ:180809518 天前
基于TCN-LSTM的时序预测模型:多特征到单变量预测的深度学习框架
软件构建
爱思德学术9 天前
中国计算机学会(CCF)推荐学术会议-C(软件工程/系统软件/程序设计语言):EASE 2026
软件工程·软件构建·软件需求
帅次9 天前
系统分析师:系统规划与分析的业务流程分析、业务流程图、数据与数据流程分析和系统方案建议
流程图·软件工程·软件构建·需求分析·敏捷流程·设计规范·规格说明书
Wild_Pointer.10 天前
高效工具实战指南:CMake构建工具
c++·软件构建
帅次14 天前
系统分析师:系统规划与分析的系统规划概述、项目的提出和选择、系统分析概述以及问题分析
软件工程·团队开发·软件构建·需求分析·敏捷流程·设计规范·规格说明书
Highcharts.js17 天前
Highcharts Nightly Builds 每日构建:确保图表库持续集成
软件构建·源代码管理·highcharts·nightly builds·每日构建·代码测试·daily build
简艺会员管理系统18 天前
美甲美睫会员管理软件排名
软件构建
wohuidaquan1 个月前
GEO破局:教培机构如何被AI“看见”?
软件构建