CMake

CMake是一个项目构建工具,是跨平台的。CMake允许开发者指定整个工程的编译流程,并根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,可以把CMake当作自动生成Makefile的工具,编译流程如下:

image-20230309130644912

CMake优点

  • 跨平台
  • 能够管理大型项目
  • 简化编译构建过程和编译过程
  • 可扩展,可以为cmake编写特定功能的模块,扩充cmake功能

CMake的使用

CMake支持大写、小写、混合大小写的命令

  • 注释:##[[]]

    # 行注释
    
    #[[块
    注
    释]]
    
    
  • cmake CMakeLists.txt所在路径:在当前路径./build执行cmake ..命令后生成了一个Makefile文件,在执行make命令,就可以对项目进行构建得到所需的可执行程序(默认可执行程序生成在./build目录下)

    .
    ├── CMakeLists.txt
    ├── add.c
    ├── build
    │   ├── CMakeCache.txt
    │   ├── CMakeFiles
    │   ├── Makefile
    │   └── cmake_install.cmake
    ├── head.h
    ├── main.c
    └── sub.c
    

    在执行cmake命令时指定宏的值:cmake CMakeLists.txt路径 -DCMAKE_CXX_STANDARD=11

  • cmake_minimum_required(VERSION 3.0):指定使用的cmake最低版本

  • project:定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言

    project(<PROJECT-NAME> [<language-name>...])
    project(<PROJECT-NAME>
           [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
           [DESCRIPTION <project-description-string>]
           [HOMEPAGE_URL <url-string>]
           [LANGUAGES <language-name>...])
           
    project(csapp)
    
  • add_executable(可执行程序名 源文件名称):生成一个可执行程序,源文件可以有多个,并用空格或;间隔

  • set:定义变量or设置宏or拼接字符串

    set(变量名 [变量值] [CACHE TYPE DOCSTRING [FORCE]])
    
    # 定义变量
    set(SRC_LIST main.c;add.c;sub.c)
    add_executable(app ${SRC_LIST})
    
    # 设置宏CMAKE_CXX_STANDARD的值指定cxx版本
    set(CMAKE_CXX_STANDARD 11)
    
    # 设置宏EXECUTABLE_OUTPUT_PATH的值指定可执行程序输出路径,如果此路径不存在,会自动生成
    # 由于可执行程序是基于cmake命令生成的makefile文件然后再执行make命令得到的,此处的相对路径./是makefile所在目录
    set(EXECUTABLE_OUTPUT_PATH ../bin)
    
    # 拼接字符串,将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖
    set(变量名1 ${变量名1} ${变量名2})
    
  • aux_source_directory:查找指定路径下的所有源文件

    aux_source_directory(<要搜索的目录> <将要搜索的目录下搜索到的源文件列表存储到该变量中>)
    
    CMAKE_CURRENT_SOURCE_DIR:宏表示当前访问的CMakeLists.txt文件所在路径
    PROJECT_SOURCE_DIR:宏就是在使用cmake命令时,后面紧跟对的目录,一般是工程的根目录
    
  • file:搜索文件,得到的是文件的绝对路径

    file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
    	GLOB:将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中
    	GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
    	
    # 搜索当前目录的src目录下的所有源文件,并存储到变量中,要搜索的文件路径和类型可加也可不加双引号
    file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
    file(GLOB MAIN_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c")
    
  • include_directories:设置要包含的头文件路径include_directories(head_path)

  • add_definitions:宏定义add_definitions(-D宏名称)

  • message:日志(显式一条消息),CMake的命令行工具会在stdout上显式STATUS消息,在stderr上显式其它所有消息。CMake的GUI会在它的log区域显式所有消息。CMake警告和错误消息的文本显示使用的是一种简单的标记语言,文本没有缩进,超过长度的行会回卷,段落之间以新行作为分隔符。

    message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
    	(无) :重要消息
        STATUS :非重要消息
        WARNING:CMake 警告, 会继续执行
        AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
        SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
        FATAL_ERROR:CMake 错误, 终止所有处理过程
        
    # 输出一般日志信息
    message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
    # 输出警告信息
    message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
    # 输出错误信息
    message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")
    
  • list:列表操作

    # 通过索引获取元素,[0……][……-1],边界越线报错
    list(GET <list> <element index> [<element index> ...] <output variable>)
    
    # 根据值查找元素,未找到-1
    list(FIND <list> <value> <output variable>)
    
    # 将元素追加到列表中
    list(APPEND <list> [<element> ...])
    
    # 在指定位置index插入元素
    list(INSERT <list> <element_index> <element> [<element> ...])
    
    # 将元素插入到0位置
    list (PREPEND <list> [<element> ...])
    
    # 根据索引从列表中移除元素
    list (REMOVE_AT <list> <index> [<index> ...])
    
    # 根据值从列表中移除元素
    list(REMOVE_ITEM <list> <value> [<value> ...])
    
    # 将列表中最后一个元素移除
    list (POP_BACK <list> [<out-var>...])
    
    # 将列表中第一个元素移除
    list (POP_FRONT <list> [<out-var>...])
    
    # 列表元素去重
    list (REMOVE_DUPLICATES <list>)
    
    # 列表翻转
    list(REVERSE <list>)
    
    # 获取list长度
    list(LENGTH <list> <output variable>)
    
    # 将列表中的元素用连接符glue连接起来组成一个字符串
    list (JOIN <list> <glue> <output variable>)
    
    # 列表排序
    list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
        COMPARE:指定排序方法,有如下几种值可选:
    	    STRING:按照字母顺序进行排序,为默认的排序方法
        	FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
        	NATURAL:使用自然数顺序排序
        CASE:指明是否大小写敏感。有如下几种值可选:
            SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
            INSENSITIVE:按照大小写不敏感方式进行排序
        ORDER:指明排序的顺序。有如下几种值可选:
            ASCENDING:按照升序排列,为默认值
            DESCENDING:按照降序排列
    

静&动态库

静态库:在Linux中,静态库名称分为三部分:lib + 库名字 + .a,下面只需要指定库名字即可,另外两部分生成该文件时自动添加。静态库会被打包到可执行程序中,可执行程序启动后,静态库也就随之加载到内存中了。

add_library(库名字 STATIC 源文件1 [源文件2] ...) 

动态库:在Linux中,动态库名称分为三部分:lib + 库名字 + .so,此处只需要指定出库名字即可,另外两部分生成该文件时自动添加。如果生成的库比较大,建议动态库。只有可执行程序调用了动态库中的函数的时候,动态库才会被加载到内存中,多个进程可以共享内存中的同一个动态库(共享库)。

add_library(库名字 SHARED 源文件1 [源文件2] ...) 

设置库生成路径

由于在Linux下生成的动态库默认是有可执行权限的,可按照生成可执行程序的方式指定生成目录。

由于在Linux下生成的静态库默认是不具有可执行权限的,所有在指定静态库生成路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,应该使用LIBRARY_OUTPUT_PATH,这个宏对静/动态库文件都适用

  • 适用于动态库:

    # 设置宏EXECUTABLE_OUTPUT_PATH的值指定动态库的输出路径
    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
    
  • 都适用:

    # 设置静/动态库生成路径
    set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
    

库链接

  • link_libraries:链接静态库

    link_libraries(<static lib> [<static lib>...])
    	static lib可以是:①全名libxxx.a;②xxx
    
    # 如果静态库不是系统提供的(自制or使用第三方提供的静态库)可能会出现静态库找不到的情况,此时可以指定静态库路径,此命令也可指定动态库路径
    link_directories(<lib path>)
    
  • target_link_libraries:链接动态库,也可链接静态库

    target_link_libraries(
        <target> 
        <PRIVATE|PUBLIC|INTERFACE> <item>... 
        [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
        
        target:指定要加载动态库的文件名字,该文件可能是一个源文件、动态库文件、可执行文件
        PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC。
    

    动态库具有传递性:如果A连接了B,C链接了A,那么相当于C连接了A和B,C可以使用A和B中定义的方法。

    如果各个动态库之间没有依赖关系,无需做任何设置,三者没有区别,一般无需指定,使用默认的PUBLIC即可:

    • public:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
    • PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
    • INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。

    链接系统/第三方动态库:在cmake中要指定链接的动态库的时候,应该将命令写到生成了可执行文件之后:target_link_libraries(可执行程序名 库名)

嵌套的CMake

在构建大型项目的时候可以给每个源代码目录都添加一个CMakeLists.txt文件(头文件目录不需要)。

嵌套的CMake是一个树状结构,最顶层的CMakeLists.txt是根节点,其次都是子节点。

  • 根节点CMakeLists.txt中的变量全局有效
  • 父节点CMakeLists.txt中的变量可以在子节点中使用
  • 子节点CMakeLists.txt中的变量只能在当前节点中使用

add_subdirectory:建立CMake中父子关系

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
	source_dir:指定CMakeLists.txt源文件和源代码文件的位置,即指定子目录
	binary_dir:指定输出文件路径,一般不需要指定
	EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外
					  用户必须显式构建在子路径下的目标

示例

目录结构:对于calc和sort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。

$ tree
.
├── build
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── CMakeLists.txt
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files

根目录

cmake_minimum_required(VERSION 3.0)
project(test)
# 定义变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 头文件目录
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME_1 test1)
set(APP_NAME_2 test2)
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)

calc目录

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} STATIC ${SRC})

sort目录

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC})

test1目录

cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
link_directories(${LIB_PATH})
link_libraries(${CALC_LIB})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_NAME_1} ${SRC})

test2目录

cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
link_directories(${LIB_PATH})
add_executable(${APP_NAME_2} ${SRC})
target_link_libraries(${APP_NAME_2} ${SORT_LIB})

在项目中,如果将程序中的某个模块制作成了动态库或者静态库并且在CMakeLists.txt 中指定了库的输出目录,而后其它模块又需要加载这个生成的库文件,此时直接使用就可以了,如果没有指定库的输出路径或者需要直接加载外部提供的库文件,此时就需要使用 link_directories 将库文件路径指定出来。

控制流程

条件

在进行条件判断的时候,如果有多个条件,那么可以写多个elseif,最后一个条件可以使用else,但是开始和结束是必须要成对出现的,分别为:ifendif

if(<condition>)
  <commands>
elseif(<condition>) # 可选快, 可以重复
  <commands>
else()              # 可选快
  <commands>
endif()

循环

foreach(<loop_var> <items>)
    <commands>
endforeach()
while(<condition>)
    <commands>
endwhile()

预定义宏

功能
PROJECT_SOURCE_DIR使用cmake命令后紧跟的目录,一般是工程的根目录
PROJECT_BINARY_DIR执行cmake命令的目录
CMAKE_CURRENT_SOURCE_DIR当前处理的CMakeLists.txt所在的路径
CMAKE_CURRENT_BINARY_DIRtarget编译目录
EXECUTABLE_OUTPUT_PATH重新定义目标二进制可执行文件的从存放路径
LIBRARY_OUTPUT_PATH重新定义目标链接库文件的存放位置
PROJECT_NAME返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径

参考

  • https://www.subingwen.cn/cmake/CMake-primer/

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部