CMake学习

本文相关的源代码可从gitee上下载:https://gitee.com/mylayfolk/cmake-learning/tree/master/CMake_Study

参考

官方网站:CMake Reference Documentation — CMake 3.26.4 Documentation

官方教程:CMake Tutorial — CMake 3.27.0-rc1 Documentation

安装包:Download | CMake

其它参考:

现代的 CMake 的介绍 · Modern CMake

前言 - 《CMake菜谱(CMake Cookbook中文版)》 - 书栈网 · BookStack

CMake 从入门到精通 - 凌逆战 - 博客园 (cnblogs.com)

Ubuntu下安装最新版本的CMake (ngui.cc)

10-补充(完结)_哔哩哔哩_bilibili

cmkae命令set_target_properties - yooooooo - 博客园 (cnblogs.com)

简介

CMake is a tool to manage building of source code. Originally, CMake was designed as a generator for various dialects of Makefile, today CMake generates modern buildsystems such as Ninja as well as project files for IDEs such as Visual Studio and Xcode.

CMake是一个管理编译源码构建的系统。最初,CMake被设计为各种Makefile的生成器,如今CMake产生各种现代构建系统。

安装CMake

Ubuntu下可以直接使用apt install命令安装,但是安装的版本比较低:

apt install cmake

或者直接从cmake官网下载最新版本,官网:Download | CMake

我下载的是cmake-3.26.4-linux-x86_64版本,下载完成后,先解压到/usr目录:

$ ls /usr/ -al
drwxr-xr-x   6 root root  4096 Jun  7 08:00 cmake-3.26.4-linux-x86_64/

$ ls /usr/cmake-3.26.4-linux-x86_64/ -al
drwxr-xr-x  2 root root 4096 May 18 10:57 bin/
drwxr-xr-x  3 root root 4096 May 18 10:57 doc/
drwxr-xr-x  4 root root 4096 May 18 10:57 man/
drwxr-xr-x 10 root root 4096 May 18 10:57 share/

然后创建软链接:

$ ln -s /usr/cmake-3.26.4-linux-x86_64/bin/* /usr/bin/
$ ls /usr/cmake-3.26.4-linux-x86_64/bin/
ccmake*  cmake*  cmake-gui*  cpack*  ctest*
$ ll /usr/bin/ccmake 
lrwxrwxrwx 1 root root 41 Jun  7 08:03 /usr/bin/ccmake -> /usr/cmake-3.26.4-linux-x86_64/bin/ccmake*
$ ll /usr/bin/cmake
lrwxrwxrwx 1 root root 40 Jun  7 08:03 /usr/bin/cmake -> /usr/cmake-3.26.4-linux-x86_64/bin/cmake*
$ ll /usr/bin/cmake-gui 
lrwxrwxrwx 1 root root 44 Jun  7 08:03 /usr/bin/cmake-gui -> /usr/cmake-3.26.4-linux-x86_64/bin/cmake-gui*
$ ll /usr/bin/cpack 
lrwxrwxrwx 1 root root 40 Jun  7 08:03 /usr/bin/cpack -> /usr/cmake-3.26.4-linux-x86_64/bin/cpack*
$ ll /usr/bin/ctest 
lrwxrwxrwx 1 root root 40 Jun  7 08:03 /usr/bin/ctest -> /usr/cmake-3.26.4-linux-x86_64/bin/ctest*

CMake知识点

第一个CMake

$ tree
.
├── CMakeLists.txt
└── main.c

CMakeLists.txt文件内容:

cmake_minimum_required(VERSION 3.5)

PROJECT (LESSON00)

SET(SRC_LIST main.c)

MESSAGE(STATUS "Project name " ${PROJECT_NAME})

MESSAGE(STATUS "This is BINARY dir" ${PROJECT_BINARY_DIR})

MESSAGE(STATUS "This is SOURCE dir" ${PROJECT_SOURCE_DIR})

ADD_EXECUTABLE(lesson00 ${SRC_LIST})

main.c文件内容:

#include <stdio.h>

int main(void)
{
    printf("This is first lesson!\n");

    return 0;
}

执行CMake并编译:

$ cmake .
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project name LESSON00
-- This is BINARY dir /root/cmake/CMake_Study/Study00
-- This is SOURCE dir /root/cmake/CMake_Study/Study00
-- Configuring done (1.0s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/Study00

$ make
[ 50%] Building C object CMakeFiles/lesson00.dir/main.c.o
[100%] Linking C executable lesson00
[100%] Built target lesson00

$ ./lesson00 
This is first lesson!

基本语法介绍

基本规则

  • 变量使用${}方式取值,但是在IF控制语句中是直接使用变量名

  • 指令(参数1 参数2...) 参数使用括弧括起,参数之间使用空格或分号分开。

    ADD_EXECUTABLE指令为例,如果存在另外一个func.c源文件就要写成:ADD_EXECUTABLE(LESSON00 main.c func.c)或者ADD_EXECUTABLE(LESSON00 main.c;func.c)

  • 指令是大小写无关的,参数和变量是大小写相关的。但推荐全部使用大写指令

预定义变量

参考:cmake-variables(7) — CMake 3.31.4 文档 --- cmake-variables(7) — CMake 3.31.4 Documentation

这里仅描述一些:

PROJECT_BINARY_DIR:编译工程的构建目录

PROJECT_SOURCE_DIR:当前工程最上层目录

EXECUTABLE_OUTPUT_PATH:目标二进制可执行文件的存放位置

LIBRARY_OUTPUT_PATH:库文件的默认输出路径

CMAKE_C_STANDARD:C语言标准版本

CMAKE_PROJECT_NAME:存储了 project() 命令设置的项目名称

CMAKE_PROJECT_VERSION:存储项目的版本号,如果在 project() 命令中指定了版本,该变量会存储该版本信息

CMAKE_SYSTEM_NAME:存储当前系统的名称,如 LinuxWindowsDarwin(macOS)等

CMAKE_C_COMPILER:存储 C 编译器的路径

CMAKE_CURRENT_SOURCE_DIR:存储当前正在处理的 CMakeLists.txt 文件所在的目录路径

CMAKE_CURRENT_BINARY_DIR:存储当前正在处理的 CMakeLists.txt 文件对应的二进制输出目录

CMAKE_INSTALL_PREFIX:存储安装目录的根路径。默认情况下,在 Unix 系统上可能是 /usr/local

CMAKE_SOURCE_DIR:存储最顶层 CMakeLists.txt 文件所在的目录路径,通常是项目的根目录。

CMAKE_BINARY_DIR:存储最顶层 CMakeLists.txt 文件对应的二进制输出目录,通常是构建目录。

基本函数

cmake_minimum_required

这里将后面也会用到的一些命令都放在这里统一说明。

# 功能:设置项目所需最低的cmake版本,如果运行的cmake版本低于所需的最低`<min>`版本,则会报错并停止。
# 参数<min>:最低版本号
# 注意:这个函数应该在CMakeLists.txt文件的最开头的地方,在调用project()命令之前。
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])

project

# 功能:设置工程的名字和支持的语言,并将其存放在变量<PROJECT_NAME>中,如果是在CMakeLists.txt顶层调用,还会存放工程名在CMAKE_PROJECT_NAME变量。
# 参数<PROJECT-NAME>:工程的名字
# 可选参数<language-name>:不指定默认支持所有语言。
# 举例:
# project (LESSON0):指定了工程的名字,并且支持所有语言—建议
# project (LESSON0 CXX):指定了工程的名字,并且支持语言是C++
# project (LESSON0 C CXX):指定了工程的名字,并且支持语言是C和C++
  
project(<PROJECT-NAME> [<language-name>...])

set

# 功能:将普通变量、缓存变量、环境变量设置给定值。
# 参数<variable>:变量名
# 参数<value>:变量的值
# 举例:
# set(SRC_LIST main.c)		# 定义一个变量SRC_LIST,并且赋值为main.c, 注意不是追加
# set(CMAKE_C_STANDARD 11)  # 指定C语言标准版本为C11
set(<variable> <value>... [PARENT_SCOPE])

message

# 功能:向终端输出用户自定义的信息,如果给定多条字符串消息,它们被连接成一条消息,并且没有字符串之间的分隔符。
# <mode>可选参数:消息的类型,影响消息的处理方式
#   - FATAL_ERROR:CMake错误,停止处理和生成
#   - SEND_ERROR:CMake错误,继续处理,但提供生成
#   - WARNING:CMake告警,继续处理
#   - NOTICE(默认值):重要消息打印到标准输出
#   - STATUS:项目用户可能感兴趣的主要消息。 理想情况下应该简洁,不超过一行。
#   - VERBOSE:面向项目用户的详细信息。
message([<mode>] "message text" ...)

add_executable

# 功能:将源文件目录加到生成可执行文件的目录中
# 参数<name>:生成的可执行文件名
# 可选参数[source1]:构建目标可执行文件的源文件列表
# 可选参数[EXCLUDE_FROM_ALL]:如果指定此选项,该可执行文件不会被默认构建,除非显式地指定构建它。(还是会生成Makefile,但是执行make不会生成二进制)
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

add_subdirectory

# 功能:将子目录添加到生成系统中。可以指定源文件的子目录,也可以指定生成中间文件的存放目录。
# 参数source_dir:指定子文件中CMakeLists.txt目录和源代码目录,该目录下的CMakeLists.txt文件用于编译该文件夹下的源码。
# 可选参数binary_dir:指定存放输出文件的目录
# 可选参数EXCLUDE_FROM_ALL:排除子目录包含在构建系统。
# 举例:add_subdirectory(src bin):将src子目录加入工程,并指定输出为bin目录。
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])

add_library

# 功能:构建源文件构建动态库、静态库
# 参数<name>:构建的库名,最终生成的库名lib<name>.a / lib<name>.so
# 可选参数[STATIC | SHARED | MODULE]:生成的库类型,STATIC生成静态库,SHARED生成动态库
# 可选参数EXCLUDE_FROM_ALL:排除子目录包含在构建系统
# 可选参数[<source>...]:生成库的源文件列表
add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

set_target_properties

# 功能:在 CMake 中用于设置目标(如可执行文件、库等)的属性。这些属性可以控制目标的各种行为和特性,例如输出文件名、版本信息、链接选项等。
# 命令语法:列出想要更改的所有目标,然后提供想要设置的值,可以使用该命令设置任何需要的键值对,然后使用get_property()或get_target_property()命令提取。
# <target1> <target2>...:要设置属性的目标列表。目标可以是通过 add_executable、add_library 等函数创建的可执行文件或库。
# PROPERTIES:关键字,用于标识接下来是属性设置。
# <prop1> <prop2>...:要设置的属性名称。
# <value1> <value2>...:对应属性的值。
set_target_properties(target1 target2 ...
                      PROPERTIES
                      prop1 value1
                      prop2 value2 ...)

aux_source_directory

# 功能:搜集指定目录<dir>下所有源文件,然后存放到<variable>变量中。
# <dir>:需要查找源文件的目录路径,这个路径是相对于包含 aux_source_directory 命令的 CMakeLists.txt 文件所在的目录。
# <variable>:用于存储找到的源文件列表的变量名。
aux_source_directory(<dir> <variable>)

include_directories

# 功能:用于指定在编译过程中查找头文件的目录。当编译器需要解析 #include 指令时,会在这些指定的目录中搜索相应的头文件。
# [AFTER|BEFORE](可选):用于指定将这些目录添加到编译器搜索路径的顺序。AFTER 表示添加到搜索路径的末尾(默认行为),BEFORE 表示添加到搜索路径的开头。
# [SYSTEM](可选):如果指定了 SYSTEM,则这些目录会被视为系统包含目录。这对于处理标准库头文件或第三方库头文件非常有用,编译器会对系统包含目录中的头文件采用不同的处理方式,例如在某些编译器中,不会在系统包含目录中查找缺失的头文件,并且在编译输出中会对这些头文件的包含进行特殊标记。
# dir1 [dir2...]:一个或多个需要添加到搜索路径的目录。这些目录通常是相对于包含 include_directories 命令的 CMakeLists.txt 文件所在的目录。
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

find_library

# 功能:在指定目录下查找指定库,并把库的绝对路径存放到变量里,默认查找动态库。
# 参数<VAR>:变量名称
# 参数name1:库名称
# 参数[path1 path2 ...]:库的路径
find_library (<VAR> name1 [path1 path2 ...])

target_link_libraries

# 功能:用于指定目标(如可执行文件或库)需要链接的库。这个函数在管理项目的依赖关系和确保正确的链接过程中起着关键作用。
# <target>:要链接库的目标,可以是通过 add_executable 或 add_library 创建的可执行文件或库。
# [item1 [item2 [...]]]:要链接到目标的库或目标列表。这些库可以是系统库(如 m、pthread)、自定义库(如 my_library)或其他目标。
# <debug | optimized | general>:用于指定不同构建配置(调试、优化或通用)下的链接库。例如,在调试配置下可以链接一个调试版本的库,而在优化配置下链接一个发布版本的库。
# NAMED_COMPONENTS:用于指定命名组件的链接,这在处理复杂的库依赖关系时非常有用。
# [PUBLIC | PRIVATE | INTERFACE]:用于指定链接的可见性和传播性。
target_link_libraries(<target> [item1 [item2 [...]]]
                      [<debug | optimized | general> <item>...]
                      [NAMED_COMPONENTS <components>...]
                      [PUBLIC | PRIVATE | INTERFACE])

add_compile_options

# 功能:用于添加编译选项的命令,它允许你为项目中的所有目标(可执行文件、库等)设置编译选项。这些选项会传递给编译器,影响编译过程和生成的目标文件特性。
# <option1> [<option2>...] 是一个或多个编译选项,这些选项取决于使用的编译器,例如,对于 GCC 和 Clang 编译器,常见的选项有 -Wall(开启所有常见警告)、-Werror(将所有警告视为错误)、-O3(最高优化级别)等
add_compile_options(<option1> [<option2>...])

target_compile_options

# 功能:用于为特定的目标(如可执行文件或库)设置编译选项。与 add_compile_options 不同,target_compile_options 是面向目标的,这使得对不同目标的编译选项设置更加灵活和精确。
# <target>:要设置编译选项的目标,可以是通过 add_executable 或 add_library 创建的可执行文件或库。
# [BEFORE](可选):如果指定,这些编译选项将被添加到目标的编译选项列表的开头,而不是默认的结尾。
# [ONLY_IF_USED](可选):如果指定,只有当目标被其他目标使用时,这些编译选项才会生效。
# <CONFIG>(可选):可以指定特定的构建配置(如 Debug、Release、RelWithDebInfo 等),只有在这些配置下,编译选项才会应用。
# [PUBLIC | PRIVATE | INTERFACE]:用于指定编译选项的传播性和可见性:
# 	PUBLIC:表示该目标及其依赖的其他目标都将使用这些编译选项。
# 	PRIVATE:表示只有该目标自身会使用这些编译选项,依赖该目标的其他目标不会继承这些选项。
# 	INTERFACE:表示只有依赖该目标的其他目标会使用这些编译选项,而该目标自身不会使用。
# <option1> [<option2>...]:一个或多个编译选项,这些选项的格式取决于所使用的编译器。
target_compile_options(<target>
    [BEFORE] [ONLY_IF_USED]
    [<CONFIG>...]
    [PUBLIC | PRIVATE | INTERFACE]
    <option1> [<option2>...]
)

add_definitions

# 功能:用于向编译器添加预处理器定义。这些定义会在编译源文件时被预处理器识别,从而影响代码的编译行为。例如,可以使用它来定义一些条件编译的宏,或者设置一些全局的编译参数。
# <definition1> [<definition2>...] 是一个或多个预处理器定义。这些定义的格式通常与编译器的命令行参数格式相同。例如,在 GCC 和 Clang 编译器中,定义一个宏 DEBUG 可以使用 -DDEBUG
add_definitions(<definition1> [<definition2>...])

target_compile_definitions

# 功能:用于为特定目标设置编译定义(预处理器宏)的函数。它允许你针对不同的目标(如可执行文件或库)精确地控制编译时的预处理器行为,这在现代 CMake 项目中提供了更灵活和细粒度的配置方式。
# <target>:要设置编译定义的目标,可以是通过 add_executable 或 add_library 创建的可执行文件或库。
# [BEFORE](可选):如果指定,这些编译定义将被添加到目标的编译定义列表的开头,而不是默认的结尾。
# [ONLY_IF_USED](可选):如果指定,只有当目标被其他目标使用时,这些编译定义才会生效。
# <CONFIG>(可选):可以指定特定的构建配置(如 Debug、Release、RelWithDebInfo 等),只有在这些配置下,编译定义才会应用。
# [PUBLIC | PRIVATE | INTERFACE]:用于指定编译定义的传播性和可见性:
#   PUBLIC:表示该目标及其依赖的其他目标都将使用这些编译定义。
#   PRIVATE:表示只有该目标自身会使用这些编译定义,依赖该目标的其他目标不会继承这些定义。
#   INTERFACE:表示只有依赖该目标的其他目标会使用这些编译定义,而该目标自身不会使用。
# <definition1> [<definition2>...]:一个或多个编译定义。这些定义通常以宏的形式出现,例如 DEBUG、NDEBUG 等,并且在编译器命令行中通常以 -D 前缀表示(对于 GCC 和 Clang 等编译器),或者以 /D 前缀表示(对于 Visual Studio 编译器)。
target_compile_definitions(<target>
    [BEFORE] [ONLY_IF_USED]
    [<CONFIG>...]
    [PUBLIC | PRIVATE | INTERFACE]
    <definition1> [<definition2>...]
)

option

# 功能:用于定义一个可切换的选项,这个选项可以在 CMake 配置阶段由用户进行设置。它为项目的构建提供了灵活性,允许用户根据不同的需求开启或关闭某些功能。
# <variable>:定义的选项变量名,这个变量将在 CMake 脚本中用于判断选项的状态。
# "<help_text>":对该选项的描述信息,在用户使用 ccmake 或 cmake -GUI 等交互式工具配置项目时,会显示这个描述信息,帮助用户了解该选项的作用。
# [value](可选):选项的默认值,取值为 ON 或 OFF,如果不指定,默认值为 OFF。
option(<variable> "<help_text>" [value])

add_dependencies

# 功能:用于明确指定目标之间的依赖关系。这在构建复杂项目时非常重要,因为它可以确保在构建某个目标之前,其依赖的目标已经被正确构建。
# <target>:表示依赖其他目标的目标,通常是一个可执行文件或库,这个目标的构建依赖于后面列出的 <dependency1>、<dependency2> 等目标。
# <dependency1> [<dependency2>...]:一个或多个被依赖的目标,这些目标需要在 <target> 之前构建完成。
add_dependencies(<target> <dependency1> [<dependency2>...])

add_custom_target

# 功能:添加一个自定义的构建目标,该目标可以执行自定义的命令或脚本
# <name>:自定义目标的名称。你可以使用这个名称在 make 或其他构建工具中调用这个目标。
# ALL(可选):如果指定了 ALL,这个自定义目标将成为默认的构建目标之一。当你运行 make 时,它会被自动执行,就像 make all 一样。
# COMMAND:一个或多个要执行的命令。你可以添加多个 COMMAND,它们会按顺序执行。
# ARGS:可以跟随在 COMMAND 后面,用于指定命令的参数。
# DEPENDS:一个或多个依赖的目标。这些目标将在该自定义目标执行之前被构建。
# WORKING_DIRECTORY(可选):指定执行命令的工作目录。如果未指定,将使用当前 CMake 的构建目录。
# COMMENT(可选):在执行命令时显示的注释信息。
# VERBATIM(可选):如果指定,CMake 会将命令传递给构建工具时不会进行任何转义或变量扩展,保证命令的原始形式。
# USES_TERMINAL(可选):指示命令应该在终端中运行,而不是在构建工具的日志输出中。
# SOURCES(可选):可以指定一些源文件,这些源文件会被添加到 IDE 的项目文件中,但不会影响构建过程。
add_custom_target(
    <name> [ALL] [COMMAND command1 [ARGS] [args1...]]
    [COMMAND command2 [ARGS] [args2...]]
    [DEPENDS depend_target1 depend_target2...]
    [WORKING_DIRECTORY dir]
    [COMMENT comment]
    [VERBATIM]
    [USES_TERMINAL]
    [SOURCES src1 [src2...]])

add_custom_command

# 功能:用于为构建过程添加自定义的命令。这些命令可以在不同的构建阶段执行,例如在生成文件之前、生成文件之后或者作为自定义目标的一部分。
# TARGET <target>:该命令将与指定的目标相关联。这个目标可以是一个可执行文件、库或者自定义目标。
# PRE_BUILD | PRE_LINK | POST_BUILD:表示自定义命令的执行时间:
# 	PRE_BUILD:在构建目标之前执行自定义命令
# 	PRE_LINK:在链接目标之前执行自定义命令
# 	POST_BUILD:在构建目标之后执行自定义命令
# COMMAND:一个或多个要执行的命令,你可以添加多个 COMMAND,它们将按顺序执行。
# ARGS:可用于指定命令的参数。
# WORKING_DIRECTORY(可选):自定义命令的工作目录。如果未指定,将使用 CMake 的当前构建目录。
# COMMENT(可选):在执行命令时显示的注释信息。
# VERBATIM(可选):当使用此选项时,CMake 会将命令传递给构建工具而不进行转义或变量扩展,保持命令的原始形式。
# USES_TERMINAL(可选):指示命令应在终端中执行,而不是在构建工具的日志输出中。
add_custom_command(
    TARGET <target>
    PRE_BUILD | PRE_LINK | POST_BUILD
    COMMAND command1 [ARGS] [args1...]
    [COMMAND command2 [ARGS] [args2...]]
    [WORKING_DIRECTORY dir]
    [COMMENT comment]
    [VERBATIM]
    [USES_TERMINAL]
)

execute_process

# 功能:在 CMake 配置或生成阶段执行外部命令,并获取命令的结果、输出和错误信息。
# COMMAND <cmd1> [<args>...]:要执行的命令及其参数。
# WORKING_DIRECTORY <dir>:指定命令的工作目录。
# RESULT_VARIABLE <var>:将命令的返回结果存储在变量 <var> 中。
# OUTPUT_VARIABLE <var>:将命令的标准输出存储在变量 <var> 中。
# ERROR_VARIABLE <var>:将命令的错误输出存储在变量 <var> 中。
# OUTPUT_QUIET:抑制标准输出。
# ERROR_QUIET:抑制错误输出。
# OUTPUT_STRIP_TRAILING_WHITESPACE:去除标准输出的尾随空格。
# ERROR_STRIP_TRAILING_WHITESPACE:去除错误输出的尾随空格。
# TIMEOUT <seconds>:设置命令执行的超时时间,以秒为单位。
# COMMAND_ECHO <output>:控制命令执行时的输出显示,可选值有 NONE(不显示)、STDERR(只显示错误输出)、STDOUT(只显示标准输出)或 BOTH(显示全部输出)。
execute_process(
    COMMAND <cmd1> [<args>...]
    [WORKING_DIRECTORY <dir>]
    [RESULT_VARIABLE <var>]
    [OUTPUT_VARIABLE <var>]
    [ERROR_VARIABLE <var>]
    [OUTPUT_QUIET]
    [ERROR_QUIET]
    [OUTPUT_STRIP_TRAILING_WHITESPACE]
    [ERROR_STRIP_TRAILING_WHITESPACE]
    [TIMEOUT <seconds>]
    [COMMAND_ECHO <output>]
)

set_source_files_properties

# 功能:用于为源文件设置各种属性,这些属性会影响源文件在构建过程中的处理方式。
# <files>:表示一个或多个源文件的列表,可以使用相对路径或绝对路径。这些源文件是你想要设置属性的对象。
# PROPERTIES:关键字,用于引出要设置的属性及其对应的值。
# COMPILE_DEFINITIONS:不推荐使用
# COMPILE_FLAGS:为源文件设置编译标志,如优化标志、警告标志等。
# INCLUDE_DIRECTORIES:用于为源文件设置头文件的包含目录,帮助编译器找到所需的头文件。(注意:只能为源文件设置一个头文件路径,反复调用会覆盖之前的路径)
set_source_files_properties(<files>... PROPERTIES <prop1> <value1> [<prop2> <value2>]...)

if

if(<condition>)
  <commands>
elseif(<condition>) # optional block, can be repeated
  <commands>
else()              # optional block
  <commands>
endif()

IF(var)				# 如果变量不是: 空, 0, N, NO, OFF, FALSE, NOTFOUND 或 <var>_NOTFOUND 时, 表达式为真。
IF(NOT var )		# 与上述条件相反。
IF(var1 AND var2)	# 当两个变量都为真是为真
IF(var1 OR var2)	# 当两个变量其中一个为真时为真
IF(COMMAND cmd)		# 当给定的 cmd 确实是命令并可以调用是为真
IF(EXISTS dir)		# 当目录名存在时为真
IF(EXISTS file)		# 当文件名存在时为真
IF(file1 IS_NEWER_THAN file2)	# 当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径
IF(IS_DIRECTORY dirname)		# 当 dirname 是目录时为真
IF(variable MATCHES regex)
IF(string MATCHES regex)

foreach

# foreach 循环用于对一个列表中的每个元素执行相同的操作。
# <loop_variable>:循环变量,在每次迭代中依次取 <list> 中的每个元素。
# <list>:要遍历的元素列表,可以是变量、具体值的列表等。
# [COMMAND <command> [<arg1>...]](可选):使用 COMMAND 关键字可以指定一个命令,该命令会在每次迭代中执行,参数 <arg1>... 会传递给该命令。
# [RESULT <result_variable> ](可选):如果使用了 COMMAND,这个变量将存储最后一次执行命令的结果。
# <commands>:在每次迭代中要执行的 CMake 命令块。
foreach(<loop_variable> <list> [COMMAND <command> [<arg1>...]] [RESULT <result_variable>])
    <commands>
endforeach()

while

# while 循环用于在满足特定条件时重复执行一段命令块。
# <condition>:循环条件,只要这个条件为真,就会继续执行循环体中的命令。
# <commands>:循环体中的 CMake 命令块。
while(<condition>)
  <commands>
endwhile()

命令command

在很多函数中,都可以执行command,比如execute_process、add_custom_command、add_custom_target,这里的命令不仅可以支持shell命令,也可以支持cmake的命令。

cmake支持的命令在cmake的安装目录下可以查看,建议尽量使用cmake的命令:

$ /usr/local/bin/cmake -E --help
CMake Error: cmake version 3.27.7
Usage: /usr/local/bin/cmake -E <command> [arguments...]
Available commands: 
  ... ...
  copy <file>... destination  - copy files to destination (either file or directory)
  copy_directory <dir>... destination   - copy content of <dir>... directories to 'destination' directory
  copy_directory_if_different <dir>... destination   - copy changed content of <dir>... directories to 'destination' directory
  copy_if_different <file>... destination  - copy files if it has changed
  echo [<string>...]        - displays arguments as text
  echo_append [<string>...] - displays arguments as text but no new line
  ... ...
  make_directory <dir>...   - create parent and <dir> directories
  ... ...
  remove [-f] <file>...     - remove the file(s), use -f to force it (deprecated: use rm instead)
  remove_directory <dir>... - remove directories and their contents (deprecated: use rm instead)
  rename oldname newname    - rename a file or directory (on one volume)
  rm [-rRf] [--] <file/dir>... - remove files or directories, use -f to force it, r or R to remove directories and their contents recursively
  ... ...
  touch <file>...           - touch a <file>.
  touch_nocreate <file>...  - touch a <file> but do not create it.
  create_symlink old new    - create a symbolic link new -> old
  create_hardlink old new   - create a hard link new -> old

比如,自定义一个target,target的名字叫做ko,使用了cmake的命令copy:

add_custom_target(ko ALL 
    COMMAND make all
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${KMOD_NAME}.ko ${CMAKE_CURRENT_BINARY_DIR}
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}
    COMMENT "Make kernel module"
    VERBATIM
)

注意事项

  • SET(SRC_LIST main.c)可以写成SET(SRC_LIST "main.c"),如果源文件名中含有空格,就必须要加双引号。
  • ADD_EXECUTABLE(hello main)后缀可以不写,会自动去找.c和.cpp,最好不要这样写,可能会有这两个文件main.cpp和main。

冗余编译

冗余编译类似于MakefileV=1参数:

cmake .. -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON

外部构建

目的:将源文件和生成的中间文件分开

$ tree
.
├── build
├── CMakeLists.txt
└── main.c

$ cd build/

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Project name LESSON00
-- This is BINARY dir/root/cmake/CMake_Study/Study00/build
-- This is SOURCE dir/root/cmake/CMake_Study/Study00
-- Configuring done (0.7s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/Study00/build

$ make
[ 50%] Building C object CMakeFiles/lesson00.dir/main.c.o
[100%] Linking C executable lesson00
[100%] Built target lesson00
[root@ubuntu] ~/cmake/CMake_Study/Study00/build

$ ./lesson00 
This is first lesson!

生成的文件都存放在build目录

指定源文件和生成文件路径

$ tree
.
├── build
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── main.c

外层CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

PROJECT(LESSON00)

ADD_SUBDIRECTORY(src bin)

src下的CMakeLists.txt:

ADD_EXECUTABLE(lesson00 main.c)

执行、测试:

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.6s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/Study00/build

$ ll 
drwxr-xr-x 4 root root  4096 Jun 10 21:05 ./
drwxr-xr-x 5 root root  4096 Jun 10 20:42 ../
drwxr-xr-x 3 root root  4096 Jun 10 21:05 bin/
-rw-r--r-- 1 root root 14245 Jun 10 21:05 CMakeCache.txt
drwxr-xr-x 5 root root  4096 Jun 10 21:05 CMakeFiles/
-rw-r--r-- 1 root root  1810 Jun 10 21:05 cmake_install.cmake
-rw-r--r-- 1 root root  4597 Jun 10 21:05 Makefile

$ ls bin/
CMakeFiles/  cmake_install.cmake  Makefile

$ make 
[ 50%] Building C object bin/CMakeFiles/lesson00.dir/main.c.o
[100%] Linking C executable lesson00
[100%] Built target lesson00

$ ls bin/
CMakeFiles/  cmake_install.cmake  lesson00*  Makefile

$ ./bin/lesson00 
This is first lesson!

可以看出,编译生成的可执行文件在build/bin目录下。

src下的CMakeLists.txt:

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

ADD_EXECUTABLE(lesson00 main.c)

EXECUTABLE_OUTPUT_PATH:定义可执行文件输出路径变量

LIBRARY_OUTPUT_PATH:定义库输出路径变量

静态库和动态库构建

目录结构:

$ tree 
.
├── build            # 存放临时生成的文件
├── CMakeLists.txt   # 顶层CMakeLists.txt
├── lib              # 存放生成的库
└── libsrc              # 生成库的源文件
    ├── CMakeLists.txt
    ├── func.c
    └── func.h

顶层CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
PROJECT(FUNC)
ADD_SUBDIRECTORY(libsrc bin)

libsrc目录下的CMakeLists.txt,同时生成动态库和静态库:

SET(LIBFUNC_SRC func.c)

# 对 源文件变量 生成动态库 func_shared
add_library(func_shared SHARED ${LIBFUNC_SRC})
# 对 源文件变量 生成静态库 func_static
add_library(func_static STATIC ${LIBFUNC_SRC})

# 设置最终生成的库的名称
set_target_properties(func_shared PROPERTIES OUTPUT_NAME "func")
set_target_properties(func_static PROPERTIES OUTPUT_NAME "func")

# 指定动态库版本, VERSION:指代动态库版本  SOVERSION:指代API版本
SET_TARGET_PROPERTIES(func_shared PROPERTIES VERSION 1.2 SOVERSION 1)

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

执行,生成动态库:

$ cd build/

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.9s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/GenLib/build

输出文件:

$ make
[ 25%] Building C object bin/CMakeFiles/func_shared.dir/func.c.o
[ 50%] Linking C shared library /root/cmake/CMake_Study/GenLib/lib/libfunc.so
[ 50%] Built target func_shared
[ 75%] Building C object bin/CMakeFiles/func_static.dir/func.c.o
[100%] Linking C static library /root/cmake/CMake_Study/GenLib/lib/libfunc.a
[100%] Built target func_static

$ ll ../lib/
-rw-r--r-- 1 root root 1654 Jun 11 00:12 libfunc.a
lrwxrwxrwx 1 root root   12 Jun 11 00:12 libfunc.so -> libfunc.so.1*
lrwxrwxrwx 1 root root   14 Jun 11 00:12 libfunc.so.1 -> libfunc.so.1.2*
-rwxr-xr-x 1 root root 8112 Jun 11 00:12 libfunc.so.1.2*

对库进行链接

调用上面的生成的库进行测试,目录结构如下:

$ tree
.
├── bin     # 存放生成的可执行文件
├── build   # 存放临时生成的文件
├── CMakeLists.txt  # 顶层CMakeLists.txt
├── module  # 库和头文件
│   ├── include
│   │   └── func.h
│   └── lib
│       ├── libfunc.a
│       ├── libfunc.so
│       ├── libfunc.so.1
│       └── libfunc.so.1.2
└── src     # main函数源文件
    └── main.c

顶层CMakeLists.txt中的内容:

cmake_minimum_required(VERSION 3.10)

project(cmake_study)

# 输出bin文件路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 将源代码添加到变量
set(src_list ${PROJECT_SOURCE_DIR}/src/main.c)

# 添加头文件搜索路径
include_directories(${PROJECT_SOURCE_DIR}/module/include)

# 在指定路径下查找库,并把库的绝对路径存放到变量里
find_library(FUNC_LIB func HINTS ${PROJECT_SOURCE_DIR}/module/lib)

# 执行源文件
add_executable(main ${src_list})

# 把目标文件与库文件进行链接
target_link_libraries(main ${FUNC_LIB})

执行,编译,生成可执行文件:

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.6s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/CMake_Study/TestLib/build

$ make
[ 50%] Building C object CMakeFiles/main.dir/src/main.c.o
[100%] Linking C executable /root/cmake/CMake_Study/TestLib/bin/main
[100%] Built target main

$ readelf -d ../bin/main 
Dynamic section at offset 0xe08 contains 26 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libfunc.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [/root/cmake/CMake_Study/TestLib/module/lib]
 
$ ./../bin/main 
./../bin/main: error while loading shared libraries: libfunc.so.1: cannot open shared object file: No such file or directory

$ export LD_LIBRARY_PATH=../module/lib/
$ ./../bin/main 
Generate library!

同一个目录下多个源文件

目录结构:

$ tree
.
├── build	# 存放临时生成文件
├── CMakeLists.txt
├── main.c
├── test.c
└── test.h

CMakeLists.txt文件的内容:

cmake_minimum_required(VERSION 3.10)

project(cmake_study)

add_executable(cmake_study
        main.c
        test.c)

各个源文件内容:

$ more main.c 
#include "test.h"

int main(void)
{
    func(100);

    return 0;
}

$ more test.h
#ifndef TEST_H
#define TEST_H

void func(int data);

#endif

$ more test.c 
#include <stdio.h>

void func(int data)
{
    printf("data is %d\n", data);
}

如果源文件太多,可以使用aux_source_directory命令将当前目录下的源文件存放到列表变量里,然后在add_executable里调用列表变量,修改CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 3.10)

project (cmake_study)

# 将当前目录下的源代码,收集到变量src_path
aux_source_directory(. src_path)

add_executable(cmake_study ${src_path})

aux_source_directory缺点:会把指定目录下的所有源文件都加入。

不同目录下多个源文件

目录结构:

 tree
.
├── build			# 存放临时文件
├── CMakeLists.txt  # 顶层CMakeLists.txt
├── module0    		# module0源文件
│   ├── module0.c
│   └── module0.h
├── module1    		# module1源文件
│   ├── module1.c
│   └── module1.h
└── src       	 	# main函数
    └── main.c

顶层CMakeLists.txt文件的内容:

cmake_minimum_required(VERSION 3.10)

project(cmake_study C)

# 添加 头文件 的搜索路径
include_directories(module0 module1)

# 将路径的源文件收集到变量列表
aux_source_directory(module0 src_path)
aux_source_directory(module1 src_path)

add_executable(cmake_study
        src/main.c ${include_path} ${src_path})

各个源文件内容:

$ more module0/module0.h 
#ifndef MODULE0_H
#define MODULE0_H

void module0_func(int data);

#endif

$ more module0/module0.c
#include <stdio.h>

void module0_func(int data)
{
    printf("Module0 data is %d\n", data);
}

$ more module1/module1.h 
#ifndef MODULE1_H
#define MODULE1_H

void module1_func(int data);

#endif

$ more module1/module1.c
#include <stdio.h>

void module1_func(int data)
{
    printf("Module1 data is %d\n", data);
}

$ more src/main.c 
#include "module0.h"
#include "module1.h"

int main(void)
{
    module0_func(100);
    module1_func(200);

    return 0;
}

源文件和头文件分开

目录结构:

$ tree
.
├── bin     # 存放输出的文件
├── build   # 存放中间生成文件
├── CMakeLists.txt  # 顶层CMakeLists.txt
├── include   # 头文件目录
│   ├── module0.h
│   └── module1.h
└── src       # 源文件目录
    ├── CMakeLists.txt  # 子目录CMakeLists.txt
	├── main.c
    ├── module0.c
    └── module1.c

顶层CMakeLists.txt内容:

cmake_minimum_required (VERSION 3.10)

project (cmake_study)

# 定义变量, 存放可执行文件输出路径
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 向当前工程添加存放源文件的子目录
add_subdirectory(src)

src子目录CMakeLists.txt:

include_directories(../include)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})

执行,编译,生成文件在bin目录:

$ cd bin/
$ ls
main*
$ ./main
Module0 data is 100
Module1 data is 200

添加编译和控制选项

添加编译选项

CMAKE_C_COMPILER:指定C编译器

CMAKE_C_FLAGS:添加C文件编译选项,也可以通过add_definitions命令添加

在cmake脚本中,设置编译选项(配置编译器)有如下三种方法:

  • add_compile_options命令

    add_compile_options(-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes)
    
  • add_definitions命令

    add_definitions("-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes")
    
  • set命令修改CMAKE_CXX_FLAGSCMAKE_C_FLAGS

    set(CMAKE_C_FLAGS "-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes")
    

使用这三种方式在有的情况下效果是一样的,但请注意它们还是有区别的:

add_compile_options命令和add_definitions添加的编译选项是针对所有编译器的(包括c和c++编译器),

set命令设置CMAKE_C_FLAGS CMAKE_CXX_FLAGS 变量则是分别只针对c和c++编译器的。

添加控制选项

适用情况:在编译代码时仅编译一些指定的代码,可以使用cmake的option选项,主要有两种情况:

  • 本来要生成多个bin或库文件,现在只想生成部分指定的bin或库文件
  • 对于同一个bin文件,只编译其中部分代码(使用宏控制)

(1)第一种情况举例:假设工程会生成两个bin文件

$ tree
.
├── bin     # 存放生成的二进制文件
├── build   # 存放中间文件
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    ├── main1.c  # main1可执行文件
    └── main2.c  # main2可执行文件

顶层CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.10)

project(cmake_study)

# 描述选项
option(MYDEBUG "enable debug compilation" OFF)

# 设置输出bin的地址
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 添加源文件的子目录
add_subdirectory(src)

src目录下的CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.10)

project(cmake_study)

# 执行源文件
add_executable(main1 main1.c)

if (MYDEBUG)
    add_executable(main2 main2.c)   # 执行源文件
else()
    message(STATUS "Currently is not in debug mode")
endif()

main1.c和main2.c内容如下:

$ more src/main1.c 
#include <stdio.h>

int main(void)
{
    printf("main1 func\n");
    
    return 0;
}

$ more src/main2.c 
#include <stdio.h>

int main(void)
{
    printf("main2 func\n");
    
    return 0;
}

生成、编译、执行:

# 默认执行编译main1.c
$ cmake .. && make && ./../bin/main1
main1 func

# 指定MYDEBUG为ON,同时编译main2.c
$ cmake .. -DMYDEBUG=ON && make && ./../bin/main2
main2 func

(2)第二种情况,对于同一个bin,仅编译部分代码。

$ tree
.
├── bin
├── build
├── CMakeLists.txt
└── main.c

假设main.c内容如下:

#include <stdio.h>

int main(void)
{
#ifdef FUNC1
    printf("main1 func\n");
#endif

#ifdef FUNC2
    printf("main2 func\n");
#endif

    return 0;
}

顶层CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.10)
project(cmake_study)

# 设置输出bin文件的地址
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 设置选项WWW1和WWW2,默认关闭 
option(FUNC1 "print one message" OFF)
option(FUNC2 "print another message" OFF)

if (FUNC1)
    add_compile_options(-DFUNC1)
endif ()

if (FUNC2)
    add_compile_options(-DFUNC2)
endif ()

# 执行源文件
add_executable(main main.c)

各种情况下的生成、编译、执行:

$ cmake .. -DFUNC1=ON -DFUNC2=OFF && make && ./../bin/main 
main1 func

$ cmake .. -DFUNC1=OFF -DFUNC2=ON && make && ./../bin/main 
main2 func

$ cmake .. -DFUNC1=ON -DFUNC2=ON && make && ./../bin/main 
main1 func
main2 func

调试CMake

参考:调试 · Modern CMake (modern-cmake-cn.github.io)

打印变量

通常我们使用的打印语句如下:

message(STATUS "MY_VARIABLE=${MY_VARIABLE}")

然而,通过一个内置的模组 CMakePrintHelpoers 可以更方便的打印变量:

include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)

如何你只是想要打印一个变量,那么上述方法已经很好用了!如何你想要打印一些关于某些目标 (或者是其他拥有变量的项目,比如 SOURCESDIRECTORIESTESTS , 或 CACHE_ENTRIES - 全局变量好像因为某些原因缺失了) 的变量,与其一个一个打印它们,你可以简单的列举并打印它们:

cmake_print_properties(
    TARGETS my_target
    PROPERTIES POSITION_INDEPENDENT_CODE
)

跟踪运行

你可能想知道构建项目的时候你的 CMake 文件究竟发生了什么,以及这些都是如何发生的?用 --trace-source="filename" 就很不错,它会打印出你指定的文件现在运行到哪一行,让你可以知道当前具体在发生什么。

例子:

cmake -S . -B build --trace-source=CMakeLists.txt
$ cmake -S . -B build --trace-source=CMakeLists.txt
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(9):  cmake_minimum_required(VERSION 3.1...3.21 )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(13):  project(ModernCMakeExample VERSION 1.0 LANGUAGES CXX )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(29):  add_library(MyLibExample simple_lib.cpp simple_lib.hpp )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(34):  add_executable(MyExample simple_example.cpp )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(38):  target_link_libraries(MyExample PRIVATE MyLibExample )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(40):  message(STATUS PROJECT_VERSION=${PROJECT_VERSION} )
-- PROJECT_VERSION=1.0
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(41):  include(CMakePrintHelpers )
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(42):  cmake_print_variables(PROJECT_VERSION )
-- PROJECT_VERSION="1.0"
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(48):  enable_testing()
/home/share/Modern-CMake-zh_CN/examples/simple-project/CMakeLists.txt(49):  add_test(NAME MyExample COMMAND MyExample )
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/share/Modern-CMake-zh_CN/examples/simple-project/build

以debug模式构建

对于单一构建模式的生成器 (single-configuration generators),你可以使用参数 -DCMAKE_BUILD_TYPE=Debug 来构建项目,以获得调试标志 (debugging flags)。

如果你使用了 debug 模式构建,你就可以在上面运行调试器了,比如 gdb。

实践

CMake编译KO

使用Cmake编译KO,一般比较麻烦,主要就是通过add_custom_command、add_custom_target函数来自定义命令和target,这里提供一种实现方式,其本质还是使用CMake来编译Makefile,然后使用add_custom_target来自定义一个目标:

cmake_minimum_required(VERSION 3.10)
project(KernelModuleCMake)

set(KERNEL_DIR "/path/to/your/kernel/source")

set(CMAKE_C_COMPILER "/path/to/your/compiler/tools/bin/aarch64-linux-gnu-")

# 检查内核源代码路径是否存在
if(NOT EXISTS ${KERNEL_DIR})
    message(FATAL_ERROR "Kernel source directory does not exist: ${KERNEL_DIR}")
endif()

set(KMOD_NAME test_module)	# 输出的KO名
set(DIR_NAME  kmod_build)	# 创建的临时目录
set(FILE_NAME Makefile)		# 生成的文件名

file(GLOB_RECURSE SRC_FILE_LIST "/path/to/source/files/*.c")
set(INC_PATH_LIST /path/to/header/files)

# 创建一个临时目录
execute_process(
    COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}
    RESULT_VARIABLE result
    OUTPUT_VARIABLE output
    ERROR_VARIABLE error
    OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
    message(FATAL_ERROR "Create directory ${DIR_NAME} failed: ${error}")
endif()

# 创建一个Makefile文件
execute_process(
    COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME}
    COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME}
    RESULT_VARIABLE result
    OUTPUT_VARIABLE output
    ERROR_VARIABLE error
    OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
    message(FATAL_ERROR "Touch file ${FILE_NAME} failed: ${error}")
endif()

# 编写Makefile
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME} "obj-m := ${KMOD_NAME}.o\n")
foreach(FILE_PATH ${SRC_FILE_LIST})
    get_filename_component(OBJ_FILE ${FILE_PATH} NAME_WE)
    # message(STATUS "Write file ${OBJ_FILE}")
    file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME} "${KMOD_NAME}-objs += ${OBJ_FILE}.o\n")
endforeach()
foreach(INC_PATH ${INC_PATH_LIST})
    file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME} "EXTRA_CFLAGS += -I${INC_PATH}\n")
endforeach()
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME} "all:\n")
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${FILE_NAME} "\tmake -C ${KERNEL_DIR} M=${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME} ARCH=arm64 CROSS_COMPILE=${CMAKE_C_COMPILER} modules\n")

# 将源文件创建符号链接
foreach(FILE_PATH ${SRC_FILE_LIST})
    get_filename_component(OBJ_FILE ${FILE_PATH} NAME)
    execute_process(
        COMMAND ${CMAKE_COMMAND} -E create_symlink ${FILE_PATH} ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${OBJ_FILE}
        RESULT_VARIABLE result
        OUTPUT_VARIABLE output
        ERROR_VARIABLE error
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if(result)
        message(FATAL_ERROR "Create symlink ${FILE_NAME} failed: ${error}")
    endif()
endforeach()

# 创建一个自定义Target, 并且这个自定义目标为默认的构建目标之一(执行make就会执行)
# target进入${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}目录,并且执行make all和copy命令
add_custom_target(ko ALL 
    COMMAND make all
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}/${KMOD_NAME}.ko ${CMAKE_CURRENT_BINARY_DIR}
    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}
    COMMENT "Make kernel module"
    VERBATIM
)

完整项目

源码路径:https://gitee.com/mylayfolk/cmake-learning/tree/master/extended-project

目录说明

实现功能:

  • src目录下的文件编译出一个库libmy_test.so
  • app1依赖编译出来的libmy_test.so和三方库下的libext.so
  • app2仅依赖编译出来的libmy_test.so
  • src/add下面的源文件仅能够包含include和include/add下面的头文件
  • src/add下面的源文件仅能够包含include和include/sub下面的头文件
  • core目录下的文件编译出一个ko
$ tree 
.
├── apps
│   ├── app1	# 编译app1的源文件
│   │   └── main.c
│   └── app2	# 编译app2的源文件
│       └── main.c
├── build		# CMAKE构建输出目录
├── cmake
│   ├── app.cmake		# 构建app
│   ├── config.cmake	# 构建配置
│   ├── kernel.cmake	# 构建KO
│   ├── lib.cmake		# 构建库
│   ├── target.cmake	# 构建目标
│   └── user.cmake		# 用户态相关构建
├── CMakeLists.txt
├── core		# 编译KO源文件
│   └── module.c
├── docs		# 当前未使用
├── extern_lib	# 外部三方库路径
│   ├── extlib.h
│   └── libext.so
├── include
│   ├── add
│   │   └── add.h
│   ├── print.h
│   └── sub
│       └── sub.h
├── README.md
├── src			# 编译so源文件路径
│   ├── add
│   │   └── add.c
│   ├── print.c
│   └── sub
│       └── sub.c
└── tests		# 当前未使用

构建编译

构建

cmake -S . -B build
  • -S :表示源代码目录的路径
  • .:表示当前目录,CMake 在当前目录中查找 CMakeLists.txt 文件,作为项目的源代码目录。
  • -B:表示构建目录的路径
  • build:是构建目录的名称,CMake 会在当前目录下创建一个名为 build 的目录(如果不存在),并将生成的构建文件(如 Makefile 或其他构建系统所需的文件)存储在该目录中。

如果需要冗余方式构建,类似make V=1,则可以执行命令:

cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON

编译

进入build目录,然后执行make

$ cd build/
$ make
Scanning dependencies of target ko
[ 11%] SHELL_OUTPUT
[ 11%] Built target ko
Scanning dependencies of target my_test
[ 22%] Building C object CMakeFiles/my_test.dir/src/print.c.o
[ 33%] Building C object CMakeFiles/my_test.dir/src/add/add.c.o
[ 44%] Building C object CMakeFiles/my_test.dir/src/sub/sub.c.o
[ 55%] Linking C shared library libmy_test.so
[ 55%] Built target my_test
Scanning dependencies of target app2
[ 66%] Building C object CMakeFiles/app2.dir/apps/app2/main.c.o
[ 77%] Linking C executable app2
[ 77%] Built target app2
Scanning dependencies of target app1
[ 88%] Building C object CMakeFiles/app1.dir/apps/app1/main.c.o
[100%] Linking C executable app1
[100%] Built target app1

查看输出文件:

$ file libmy_test.so 
libmy_test.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=430735ea7148d002a148f8d51e0041f2217dd309, not stripped

$ file my_test.ko 
my_test.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=6abe75f9a2bf873f2cb1e8321b88dd5d2f9791c4, not stripped

$ ldd app1 
        linux-vdso.so.1 =>  (0x00007ffe0899d000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f2045de9000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f2045ae7000)
        libmy_test.so => /home/extended-project/build/libmy_test.so (0x00007f20458e5000)
        libext.so => /home/extended-project/extern_lib/libext.so (0x00007f20456e3000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2045315000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2046005000)

$ ldd app2 
        linux-vdso.so.1 =>  (0x00007ffdc8b92000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f717f6cb000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f717f3c9000)
        libmy_test.so => /home/extended-project/build/libmy_test.so (0x00007f717f1c7000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f717edf9000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f717f8e7000)

部分编译输出

gcc -Dmy_test_EXPORTS  -fPIC   -lpthread -lm -Wall -Werror -g -O0 -fstack-protector-strong -I/home/extended-project/include/ -I/home/extended-project/include/add -I/home/extended-project/include/sub  -o CMakeFiles/my_test.dir/src/print.c.o   -c /home/extended-project/src/print.c
gcc -Dmy_test_EXPORTS  -fPIC   -lpthread -lm -Wall -Werror -g -O0 -fstack-protector-strong -I/home/extended-project/include/ -I/home/extended-project/include/add  -o CMakeFiles/my_test.dir/src/add/add.c.o   -c /home/extended-project/src/add/add.c
gcc -Dmy_test_EXPORTS  -fPIC   -lpthread -lm -Wall -Werror -g -O0 -fstack-protector-strong -I/home/extended-project/include/ -I/home/extended-project/include/sub  -o CMakeFiles/my_test.dir/src/sub/sub.c.o   -c /home/extended-project/src/sub/sub.c
gcc  -I/home/extended-project/include -I/home/extended-project/include/sub  -lpthread -lm -Wall -Werror -g -O0 -o CMakeFiles/app2.dir/apps/app2/main.c.o   -c /home/extended-project/apps/app2/main.c
gcc    CMakeFiles/app2.dir/apps/app2/main.c.o  -o app2 -Wl,-rpath,/home/extended-project/build -lpthread -lm libmy_test.so -lpthread -lm 
gcc  -I/home/extended-project/include -I/home/extended-project/include/add -I/home/extended-project/extern_lib  -lpthread -lm -Wall -Werror -g -O0 -o CMakeFiles/app1.dir/apps/app1/main.c.o   -c /home/extended-project/apps/app1/main.c
gcc    CMakeFiles/app1.dir/apps/app1/main.c.o  -o app1  -L/home/extended-project/extern_lib -Wl,-rpath,/home/extended-project/build:/home/extended-project/extern_lib -lpthread -lm libmy_test.so -lext -lpthread -lm 
posted @   zhengcixi  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
回到顶部
点击右上角即可分享
微信分享提示

目录导航