文章目录
- 一、一个目录下有一个源文件(入门)
- 1.1 预定义的变量
- 1.2 语法介绍
- cmake_minimum_required
- project
- set
- message
- add_executable
- 1.3 最基础的实例
- 1.4 第一步优化:build目录
- 实操流程
- 1.5 第二步优化:src目录
- 1.5.1 实操流程
- 1.5.2 add_subdirectory
- 二、一个目录下有多个源文件
- 2.1 基础的实例
- 2.1.1 项目结构和实例
- 2.1.2 编译运行
- 2.2 进阶优化
- 2.2.1 项目结构和实例
- 2.2.2 编译运行
- 2.2.3 aux_source_directory
- 三、多个目录有多个源文件
- 3.1 基础的实例
- 3.1.1 项目结构和实例
- 3.1.2 编译运行
- 3.1.3 include_directories
- 3.2 进阶优化
- 3.2.1 项目结构和实例
- 3.2.2 编译运行
- 3.3 进阶优化2
- 3.3.1 项目结构和实例
- 3.2.2 编译运行
- 四、生成库(待更新)
- DCMAKE_INSTALL_PREFIX
- install
CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程),能够输出各种各样的makefile或者project文件。可以理解为,编写Makefile难度太大,CMake基于Makefile做了二次开发。
安装CMake
我使用的是Ubuntu 22.04.2,在终端输入
sudo apt install cmake
完成之后,检查版本号,看是否安装成功
cmake -version
结果是
zxm@ubuntu:~$ cmake -version
cmake version 3.22.1
一、一个目录下有一个源文件(入门)
1.1 预定义的变量
CMAKE_CURRENT_LIST_DIR
:CMakeLists 所在的路径CMAKE_BINARY_DIR
、PROJECT_BINARY_DIR
、<projectname>_BINARY_DIR:
工程编译发生的目录。表示当前的构建目录,即执行 CMake 构建命令时所在的目录的路径。CMAKE_SOURCE_DIR
、PROJECT_SOURCE_DIR
、<projectname>_BINARY_DIR
: 工程顶层目录。表示包含项目名的最近一个 CMakeLists.txt 文件所在的目录的路径。CMAKE_CURRENT_SOURCE_DIR
:表示当前正在处理的 CMakeLists.txt 文件的所在目录的绝对路径。CMAKE_C_COMPILER
:指定 C 编译器CMAKE_CXX_COMPILER
:指定 C++ 编译器EXECUTABLE_OUTPUT_PATH
:可执行文件输出的存放路径LIBRARY_OUTPUT_PATH
:库文件输出的存放路径CMAKE_BUILD_TYPE
: 构建的类型,例如 Debug(默认有-g) ,Release
1.2 语法介绍
cmake_minimum_required
用于指定 cmake 的最小版本要求。
cmake_minimum_required (VERSION 2.8)
project
通常作为 CMakeLists.txt 文件的第一条指令出现,用于定义项目的基本信息。通过设置属性,可以提供项目的名称、版本号、描述等信息,并指定项目所用的编程语言。
project(<project_name>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project_description>]
[HOMEPAGE_URL <homepage_url>]
[LANGUAGES <language>...])
其中,<project_name> 是项目的名称,是 project 指令的必填参数。
<major>, <minor>,<patch>,<tweak> 可选地指定项目的版本号。
除了名称和版本号外,project 指令还支持设置其他可选属性:
-DESCRIPTION:可选地设置项目的描述信息。
HOMEPAGE_URL:可选地设置项目的主页 URL。
LANGUAGES:可选地指定项目使用的编程语言。默认情况表示支持所有语言。
set
用于创建新的变量,修改已存在的变量的值,以及删除变量。
set(<variable_name> <value> CACHE <type> <docstring> [FORCE])
其中:
- <variable_name> 是要设置的变量名。
- <value> 是要设置的变量的值。
- CACHE 是一个可选的关键字,用于将变量设置为缓存变量,允许用户通过命令行或图形界面进行配置。
- <type> 是变量的类型,可以是 STRING、BOOL、PATH 等。
- <docstring> 是关于这个变量的描述文本,可选。
- FORCE 是一个可选的关键字,用于强制覆盖之前定义的同名变量的值。
举个例子
// 设置了一个名为 SRC_FILES 的变量,其值为一组源文件的路径。
set(SRC_FILES src/main.cpp src/foo.cpp)
// 设置了一个名为 ENABLE_DEBUG 的缓存变量,其值为布尔类型,用于控制是否启用调试模式。
set(ENABLE_DEBUG TRUE CACHE BOOL "Enable debug mode")
// 设置了一个名为 MY_PATH 的缓存变量,其值为路径类型,指定了某个目录的路径。
set(MY_PATH "/path/to/something" CACHE PATH "Path to something")
message
用于在配置过程中打印一些特定信息,方便调试和打印状态。
message([<mode>] "message to display" ...)
其中,<mode> 是一个可选参数,用于指定消息的模式。常见的模式有以下几种:
- STATUS:将消息以普通状态打印出来。
- WARNING:将消息以警告的方式打印出来。
- AUTHOR_WARNING:将消息以作者警告的方式打印出来。
- SEND_ERROR:将消息以错误的方式打印出来,并停止配置过程。
- FATAL_ERROR:将消息以致命错误的方式打印出来,并终止配置和构建过程。
add_executable
创建可执行文件目标,用于将一组源文件编译为可执行文件。
add_executable(<target_name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
其中
- <target_name> 是要创建的可执行文件的名称,可以自定义。
- [WIN32] 和 [MACOSX_BUNDLE] 是可选的标志,用于指定平台特定的属性。
- [EXCLUDE_FROM_ALL] 也是可选的标志,用于将目标从默认构建中排除。
举个例子
// 创建了一个名为 MyExecutable 的可执行文件目标,
// 并指定了三个源文件 main.cpp、foo.cpp 和 bar.cpp。
add_executable(MyExecutable main.cpp foo.cpp bar.cpp)
1.3 最基础的实例
1)创建两个文件main.c
和CMakeLists.txt
main.c
里面编写
#include <stdio.h>
int main(){
printf("This is CMake;\n");
return 0;
}
CMakeLists.txt
里面编写
# 单个目录的实现
# CMake最低版本号的要求
cmake_minimum_required(VERSION 2.8)
# 工程项目的名称,注意不是执行文件名
project(Zxm)
# 手动加入文件 ${变量名},比如${SRC_LIST}
set(SRC_LIST main.c)
# message 和 echo 类型
message(STATUS "PROJECT_BINARY_DIR DIR " ${PROJECT_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR DIR " ${PROJECT_SOURCE_DIR})
# 创建可执行文件,名为hello
add_executable(hello ${SRC_LIST})
2)在终端输入cmake .
,.
表示当前目录
生成了下面几个文件
3)在终端输入make
,生成可执行文件hello
3)执行./hello
4)如果还有一个main2.c
,那么修改CMakeLists.txt
,其他的一样
#单个目录的实现
# CMake最低版本号的要求
cmake_minimum_required(VERSION 2.8)
# 工程项目的名称,注意不是执行文件名
project(Zxm)
# 手动加入文件 ${变量名},比如${SRC_LIST}
set(SRC_LIST main.c)
set(SRC_LIST2 main2.c)
# message 和 echo 类型
message(STATUS "PROJECT_BINARY_DIR DIR " ${PROJECT_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR DIR " ${PROJECT_SOURCE_DIR})
# 创建可执行文件,名为hello
add_executable(hello ${SRC_LIST})
add_executable(hello2 ${SRC_LIST})
1.4 第一步优化:build目录
把很多生成的文件和源码混在一起,是一个很容易挨打的行为。我们可以单独创建一个子目录build,用来存放cmke后的结果。
实操流程
1)创建子目录build,进入到build
cd build/
2)终端输入cmake ..
# ..表示二级目录
cmake ..
3)终端输入make
这样就显得干净多了
1.5 第二步优化:src目录
为工程添加一个子目录src,用来放置工程源代码。结构如图
1.5.1 实操流程
1)创建子目录src,把main.c移到该目录下。并为任何子目录建立一个 CMakeLists.txt(跟工程1的一致)
#单个目录的实现
# CMake最低版本号的要求
cmake_minimum_required(VERSION 2.8)
# 工程项目的名称,注意不是执行文件名
project(Zxm)
# 手动加入文件 ${变量名},比如${SRC_LIST}
set(SRC_LIST main.c)
# message 和 echo 类型
message(STATUS "PROJECT_BINARY_DIR DIR " ${PROJECT_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR DIR " ${PROJECT_SOURCE_DIR})
message(STATUS "CMAKE_CURRENT_SOURCE_DIR " ${CMAKE_CURRENT_SOURCE_DIR})
# 创建可执行文件,名为hello
add_executable(hello ${SRC_LIST})
2)在主目录下,建立一个 CMakeLists.txt。主要点是add_subdirectory(src bin)
#单个目录的实现
# CMake最低版本号的要求
cmake_minimum_required(VERSION 2.8)
# 工程项目的名称,注意不是执行文件名
project(Zxm)
# 手动加入文件 ${变量名},比如${SRC_LIST}
set(SRC_LIST main.c)
# message 和 echo 类型
message(STATUS "PROJECT_BINARY_DIR DIR " ${PROJECT_BINARY_DIR})
message(STATUS "PROJECT_SOURCE_DIR DIR " ${PROJECT_SOURCE_DIR})
# CMAKE_CURRENT_SOURCE_DIR它的CMakeLists.txt所在的当前源目
MESSAGE(STATUS "CMAKE_CURRENT_SOURCE_DIR " ${CMAKE_CURRENT_SOURCE_DIR})
# 添加子目录src,并将生成的二进制文件存放到 bin 目录中
add_subdirectory(src bin)
3)进入build目录,执行cmake ..
和 make
cd build
cmake ..
make
注意到生成的可执行文件是存放到 bin 目录中,因此需要在bin目录下执行
cd bin
./hello
1.5.2 add_subdirectory
用于向 CMake 构建系统添加一个子目录。这个子目录可以包含另一个独立的 CMakeLists.txt 文件,用于管理子目录下的源码、库文件或其他构建内容。
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
其中:
- source_dir:要添加为子目录的源码目录,需要包含一个独立的 CMakeLists.txt 文件。
- binary_dir:可选参数,指定生成的二进制文件(如可执行文件、库文件)所存放的目录。如果不提供该参数,则默认为在当前构建目录生成二进制文件。
- EXCLUDE_FROM_ALL:可选参数,表示将该子目录排除在默认构建(make 或 build)之外。
举个例子,假设我们的项目包含两个子目录,分别是 src 和 tests:
project/
├── CMakeLists.txt
├── src/
│ ├── CMakeLists.txt
│ └── ...
└── tests/
├── CMakeLists.txt
└── ...
在主项目的 CMakeLists.txt 文件中,我们可以使用 add_subdirectory命令将这两个子目录添加进来:
add_subdirectory(src )
add_subdirectory(tests)
这样,在构建过程中,CMake 会先依次进入 src 目录和 tests 目录,执行这两个子目录下的 CMakeLists.txt 文件,分别进行对应的构建操作。
二、一个目录下有多个源文件
2.1 基础的实例
2.1.1 项目结构和实例
main.c编写
#include <stdio.h>
#include "testFunc.h"
int main(void)
{
func(100);
return 0;
}
testFunc.h编写
void func(int data);
testFunc.c编写
#include <stdio.h>
#include "testFunc.h"
void func(int data)
{
printf("data is %d\n", data);
}
CMakeLists.txt编写(不加打印信息了)
cmake_minimum_required (VERSION 2.8)
project (zxm)
add_executable(hello main.c testFunc.c)
2.1.2 编译运行
进入build,运行查看
cd build/
cmake ..
make
./hello
2.2 进阶优化
如果源文件很多呢,几十个,一个个加入到add_executable
就很麻烦。这时候可以使用aux_source_directory
,用于自动将指定目录下的源文件列表添加到变量中。
并且,这么多源文件,最好放到单独的文件夹里面。
2.2.1 项目结构和实例
创建子目录src,存放源文件。在2.1例子的基础上,增加testFun1.c,testFun1.h文件,修改main.c文件。
testFun1.c编写
#include <stdio.h>
#include "testFunc1.h"
void func1(int data)
{
printf("data is %d\n", data);
}
testFun1.h编写
void func1(int data);
main.c编写
#include <stdio.h>
#include "testFunc.h"
#include "testFunc1.h"
int main(void)
{
func(100);
func1(200);
return 0;
}
src目录下的CMakeLists.txt编写
cmake_minimum_required (VERSION 2.8)
project (zxm)
# 把当前目录下的源文件添加到变量SRC_LIST中
aux_source_directory(. SRC_LIST)
add_executable(hello ${SRC_LIST})
主目录下的CMakeLists.txt编写
cmake_minimum_required (VERSION 2.8)
project (zxm)
add_subdirectory(src bin)
2.2.2 编译运行
cd build/
cmake ..
make
cd bin/
./hello
2.2.3 aux_source_directory
用于自动查找指定目录 dir 下的所有源文件,并将它们的完整路径(包括文件名)存储在变量 variable 中。
aux_source_directory(dir variable)
其中:
- dir 是要进行源文件搜索的目录路径。
- variable 是用于存储找到的源文件列表的变量名。
例如
假设有一个名为 src 的目录,其中包含以下源文件:main.cpp、helper.cpp 和 utils.cpp。你可以使用 aux_source_directory 命令来自动获取这些源文件:
# 搜索 src 目录下的所有源文件,并将它们的完整路径存储在变量 SOURCES 中。
aux_source_directory(src SOURCES)
然后,你可以使用 SOURCES 变量来指定要编译的源文件列表
add_executable(my_app ${SOURCES})
三、多个目录有多个源文件
3.1 基础的实例
3.1.1 项目结构和实例
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
├── main.c
├── test_func
│ ├── testFunc.c
│ └── testFunc.h
└── test_func1
├── testFun1.c
└── testFunc1.h
把testFunc.c和testFunc.h放在test_func目录下,把testFunc1.c和testFunc1.h放在test_func1目录下。
修改src目录下的CMakeLists.txt为
重点是include_directories
,向工程添加多个特定的头文件搜索路径;相当于指定g++编译器的-I参数值。
cmake_minimum_required (VERSION 2.8)
project (zxm)
# 向工程添加多个指定头文件的搜索路径
include_directories(test_func test_func1)
# 把当前目录下的源文件添加到变量SRC_LIST中
aux_source_directory(test_func SRC_LIST)
aux_source_directory(test_func1 SRC_LIST1)
add_executable(hello main.c ${SRC_LIST} ${SRC_LIST1})
当然,也可以在main.c里包含具体的头文件路径,这就不需要include_directories
#include "test_func/testFunc.h"
#include "test_func1/testFunc1.h"
3.1.2 编译运行
cd build/
cmake ..
make
cd bin/
./hello
3.1.3 include_directories
用于向 CMake 构建系统添加一个或多个包含目录,告诉 CMake 在编译源代码时搜索头文件所在的目录。
include_directories(dir1 [dir2 ...])
其中,dir1、dir2 等是要添加的包含目录的路径。
例如,假设你的项目中有一个名为 include 的目录,里面包含了一些头文件。你可以使用 include_directories 命令将该目录添加到项目中:
include_directories(include)
3.2 进阶优化
综合上面介绍的,工程项目中,一般会把源文件放在src目录,头文件放在include目录,cmake生成的对象文件放在build目录,最终输出可执行文件放在build目录下的bin目录
3.2.1 项目结构和实例
.
├── build
├── CMakeLists.txt
├── include
│ ├── testFunc1.h
│ └── testFunc.h
└── src
├── CMakeLists.txt
├── main.c
├── testFunc1.c
└── testFunc.c
修改src目录下的CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
project (zxm)
# 向工程添加多个指定头文件的搜索路径
include_directories(../include)
# 把当前目录下的源文件添加到变量SRC_LIST中
aux_source_directory(. SRC_LIST)
add_executable(hello ${SRC_LIST})
3.2.2 编译运行
cd build/
cmake ..
make
cd bin/
./hello
3.3 进阶优化2
3.3.1 项目结构和实例
进一步,如果想修改可执行文件的生成路径,比如在src目录下的bin目录
修改src目录下的CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
project (zxm)
# 向工程添加多个指定头文件的搜索路径
include_directories(../include)
# 把当前目录下的源文件添加到变量SRC_LIST中
aux_source_directory(. SRC_LIST)
add_executable(hello ${SRC_LIST})
# EXECUTABLE_OUTPUT_PATH 可执行文件输出的存放路径
# 包PROJECT_SOURCE_DIR 含项目名的最近一个 CMakeLists.txt 文件所在的目录的路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
修改主目录下的CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
project (zxm)
add_subdirectory(src)
3.2.2 编译运行
cd build/
cmake ..
make
cd ../src/bin/
./hello
四、生成库(待更新)
在软件开发中,动态库(Dynamic Library)和静态库(Static Library)是两种常见的库文件类型。
1) 动态库:
- 动态库是一种在运行时加载的库文件。它以共享的方式被多个程序使用,可以在不同的应用程序之间共享代码和数据。
- 动态库具有独立编译和链接的优势,因此可以以相对较小的体积提供功能模块。
- 当一个程序调用动态库时,操作系统会在内存中加载该库,并将其映射到运行进程的地址空间。这种共享的方式可以节省系统资源和内存使用,并允许库的更新和升级。
- 动态库的扩展名通常为 .so(在Linux/Unix)或 .dll(在Windows)。
2)静态库:
- 静态库是一种在编译时链接到程序中的库文件。它会将库的代码和数据复制到最终生成的可执行文件中。
- 静态库使得可执行文件完全自包含,不依赖于外部的库文件。这可以提供更好的可移植性和独立性,因为所有需要的代码都包含在一个文件中。
- 静态库通常比动态库更容易部署和分发,因为不需要处理共享库的加载和依赖关系。
- 静态库的扩展名通常为 .a(在Linux/Unix)或 .lib(在Windows)。
在实际开发中,可以根据项目需求和约束来选择使用动态库还是静态库。动态库适合用于提供公共功能的共享库,而静态库则适合用于构建独立、可移植的可执行文件。
DCMAKE_INSTALL_PREFIX
指定 camke install 时的相对路径前缀
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
install
参考cmake的install指令
用于指定在安装时运行的规则。它可以用来安装很多内容,可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
install(TARGETS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])
目标文件的安装
install(TARGETS targets... [EXPORT <export-name>]
[[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[NAMELINK_COMPONENT <component>]
[OPTIONAL] [EXCLUDE_FROM_ALL]
[NAMELINK_ONLY|NAMELINK_SKIP]
] [...]
[INCLUDES DESTINATION [<dir> ...]]
)
参数中的TARGET可以是很多种目标文件,最常见的是通过ADD_EXECUTABLE或者ADD_LIBRARY定义的目标文件,即可执行二进制、动态库、静态库。
为了符合一般的默认安装路径,如果设置了DESTINATION参数,推荐配置在安装目录变量下的文件夹。
例如:
INSTALL(TARGETS myrun mylib mystaticlib
# 可执行二进制myrun安装到${CMAKE_INSTALL_BINDIR}目录
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
# 可执行二进制myrun安装到${CMAKE_INSTALL_BINDIR}目录
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
# 可执行二进制myrun安装到${CMAKE_INSTALL_BINDIR}目录
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
普通文件的安装
install(<FILES|PROGRAMS> files...
TYPE <type> | DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])
FILES|PROGRAMS若为相对路径给出的文件名,将相对于当前源目录进行解释。其中,FILES为普通的文本文件,PROGRAMS指的是非目标文件的可执行程序(如脚本文件)。
# 将文本文件 myfile.txt 安装到目标路径的 share/myproject 目录下
install(FILES myfile.txt DESTINATION share/myproject)
目录的安装
install(DIRECTORY dirs...
TYPE <type> | DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>] [EXCLUDE_FROM_ALL]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
该命令将一个或多个目录的内容安装到给定的目的地,目录结构被逐个复制到目标位置。
# 将目录 mydir/ 及其内容安装到目标路径的 include/myproject 目录下
install(DIRECTORY mydir/ DESTINATION include/myproject)