提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
–
前言
一、Cmake的介绍
Cmake是什么?
CMake 是一个跨平台的开源构建系统工具,它使用 CMakeLists.txt 文件描述构建流程。可以让我们通过编写简单的配置文件去生成本地的Makefile,这个配置文件是独立于运行平台和编译器的,这样就不用亲自去编写Makefile文件了。它简化了构建过程,使得跨平台开发变得更加方便。
Makeflie的介绍:
Makefile 是一个用于自动化构建过程的文件,它与 make 工具一起使用。Makefile 文件包含了一系列规则和指令,用于指定如何编译和链接程序中的源文件,从而生成最终的可执行文件或库。Makefile 的主要目的是简化编译过程,使得项目的构建变得自动化和高效。
Cmake的主要功能(主要作用)
1.生成构建系统文件:CMake 通过读取 CMakeLists.txt 文件,生成适合当前平台的构建系统文件(如 Makefile 或项目文件)。
2.跨平台支持:CMake 支持多种操作系统和编译器,包括 Unix-like 系统、Windows 系统以及各种编译器(GCC、Clang、MSVC 等)。
3.配置和生成:CMake 能够处理复杂的配置需求,比如设置编译选项、定义宏、指定编译目标等
C++项目的Cmake构建流程
1.创建 CMakeLists.txt 文件:
a. 在项目的根目录下创建一个 CMakeLists.txt 文件,定义项目的基本信息(如项目名称、版本等)。
b. 通过 include_directories 和 link_directories 指定包含文件和库的路径。
c. 通过 add_executable 或 add_library 等命令指定编译目标(可执行文件或库)。
2.定义子目录和模块:
a. 对于大型项目,通常会将项目分成多个模块或子目录。可以在主 CMakeLists.txt 文件中使用 add_subdirectory 命令来包含子目录中的 CMakeLists.txt 文件。
b. 每个子目录可以有自己的 CMakeLists.txt 文件,负责该目录下文件的编译和链接。
3.运行cmake,配置项目:
a.运行指令: CMake ..
配置项目。这会生成适合当前平台的构建文件(如 Makefile)。
b.可以在配置过程中指定编译选项、调试信息、安装路径等参数。
4.构建项目:
a.运行指令:make
, 使用生成的构建系统文件makefile进行编译
简单的示例:假设你的目录如下:
project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── foo.cpp
└── include/
└── foo.h
project/CMakeLists.txt文件的内容示例如下:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# 包含头文件目录
include_directories(include)
# 添加可执行文件
add_executable(MyExecutable src/main.cpp src/foo.cpp)
# 链接库(如果有的话)
# target_link_libraries(MyExecutable SomeLibrary)
构建的指令:
# 创建构建目录,存放中间编译文件的地方
mkdir build
cd build
# 运行 CMake 生成构建系统文件
cmake ..
# 使用生成的构建系统文件进行编译
make
二、环境搭建与简单的使用
1.编译多个源文件
(1)在同一个目录下的有多个源文件(.cpp)
简单的示例:假设你的目录如下:
project/
├── CMakeLists.txt
├── main.cpp
├── foo.cpp
└── foo.h
CMakeLists.txt文件的内容:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 包含头文件目录
# include_directories(include)
# 添加可执行文件
add_executable(MyExecutable main.cpp foo.cpp)
注意
:头文件 foo.h 位于与源文件相同的目录下,因此编译器会自动找到它,不需要单独指定包含目录。
如果在同一目录下有多个源文件,那么只要在add_executable里把所有源文件都添加进去就可以了。但是如果有一百个源文件,再这样做就太麻烦了,无法体现cmake的优越性。
因此,cmake中提供aux_source_directory指令来解决这个问题:
aux_source_directory(dir var)
指令参数介绍:
a
. 第一个参数dir是指定源文件目录
b
. 第二个参数var是用于存放源文件列表的变量
例如,文件结构如下:
project/
├── CMakeLists.txt
├── main.cpp
├── a.cpp
├── a.h
├── foo.cpp
└── foo.h
CMakeLists.txt的内容为:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 指定源文件的路径
aux_source_directory(. SRC_LIST)
# 添加可执行文件
add_executable(main ${SRC_LIST})
解释:
a
. ” . “:当前目录
b
. 使用aux_source_directory把当前目录下的源文件存列表存放到变量SRC_LIST里
c
. 然后在add_executable里调用SRC_LIST(调用变量时的写法)。
但是,aux_source_directory()也存在弊端,它会把指定目录下的所有源文件都加进来,可能会加入一些我们不需要的文件,此时我们可以使用set命令去新建变量来存放需要的源文件,可以使用以下的写法:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 指定源文件的路径
set( SRC_LIST
./main.cpp
./a.cpp
./foo.cpp)
# 添加可执行文件
add_executable(main ${SRC_LIST})
(2) 在不同的目录下有多个源文件
一般来说,当程序文件比较多时,我们会进行分类管理,把代码根据功能放在不同的目录下,这样方便查找。
示例,文件存放的结构如下:
project/
├── CMakeLists.txt
├── main.cpp
├── func_a/
│ ├── a.cpp
│ └── a.h
└── func_b/
├── b.cpp
└── b.h
示例的CMakeLists.txt内容:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 包含头文件目录
include_directories(func_a func_b)
# 指定源文件的路径
aux_source_directory(func_a SRC_LIST)
aux_source_directory(func_b SRC_LIST1)
# 添加可执行文件
add_executable(main ${SRC_LIST} ${SRC_LIST1})
出现一个新的指令:include_directories
该命令是用来向工程添加多个指定头文件的搜索路径,路径之间用空格分隔,如:
# include_directories(func_a func_b)
2.项目级的组织结构
对于真实的项目规范里面,一般会把源文件放到src目录下,把头文件放入到include文件下,生成的对象文件放入到build目录下,最终输出的可执行程序文件会放到bin目录下,这样整个结构更加清晰。
如文件结构:
project/
├── bin
├── build
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── foo.cpp
└── include/
└── foo.h
示例的CMakeLists.txt源码:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 包含头文件目录
include_directories(include)
# 指定源文件的目录
aux_source_directory (src SRC_LIST)
# 添加可执行文件
add_executable(MyExecutable ${SRC_LIST})
对于的大型的项目,所有的功能文件如果都写在一个CMakeLists.txt进行管理,是不利于调试与维护,因此,将不同的功能模块拆分成独立的子目录和独立的构建配置。这样可以使得构建过程更加清晰和模块化,同时便于维护和扩展。
例如,文件结构如下:
project/
├── bin
├── build
├── CMakeLists.txt
├── src/
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── foo.cpp
└── include/
└── foo.h
CMakeLists.txt的内容为:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 加编译子目录
add_subdirectory (src)
出现一个新的指令:add_subdirectory
这个语句的作用是增加编译子目录。其基本语法格式是:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
一共有三个参数,后两个是可选参数:
a
.source_dir 源代码目录:指定一个包含CMakeLists.txt和代码文件所在的目录,该目录可以是绝对路径,也可以是相对路径,对于后者相对路径的起点是CMAKE_CURRENT_SOURCE_DIR。此外,如果子目录再次包含的CMakeLists.txt,则将继续处理里层的CMakeLists.txt,而不是继续处理当前源代码。
b
.binary_dir 二进制代码目录:这个目录是可选的,如果指定,cmake命令执行后的输出文件将会存放在此处,若没有指定,默认情况等于source_dir没有进行相对路径计算前的路径,也就是CMAKE_BINARY_DIR。
c
.EXCLUDE_FROM_ALL标记
src/ CMakeLists.txt的内容为:
aux_source_directory (. SRC_LIST)
include_directories (../include)
add_executable (main ${SRC_LIST})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
上面的例子也可以只使用一个CMakeLists.txt,把最外层的CMakeLists.txt内容改成如下
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
aux_source_directory (src SRC_LIST)
include_directories (include)
add_executable (main ${SRC_LIST})
同时,还要把src目录下的CMakeLists.txt删除。然后正常编译运行就可以。
示例2:
project/
├── bin
├── build
├── CMakeLists.txt
├── src/
│ ├── func_a/
│ │ ├── CMakeLists.txt
│ │ ├── src/
│ │ │ └── a.cpp
│ │ ├── include/
│ │ │ └── a.h
│ └── func_b/
│ ├── CMakeLists.txt
│ ├── src/
│ │ └── b.cpp
│ └── include/
│ └── b.h
三、动态库和静态库的编译控制
1. 生成库文件
文件的存放结构:
project/
├── build
├── CMakeLists.txt
├── lib
├── func_a/
│ ├── a.cpp
│ └──a.h
其中,会在build目录下运行cmake,并把生成的库文件存放到lib目录下。
CMakeLists.txt的源码为:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 指定源文件的目录
set (SRC_LIST ${PROJECT_SOURCE_DIR}/func/a.cpp)
add_library (func_a_shared SHARED ${SRC_LIST})
add_library (func_a_static STATIC ${SRC_LIST})
set_target_properties (func_a_shared PROPERTIES OUTPUT_NAME "func_a")
set_target_properties (func_a_static PROPERTIES OUTPUT_NAME "func_a")
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
出现新的指令和预定义的变量:
a
. add_library: 生成动态库或静态库(第1个参数指定库的名字;第2个参数决定是动态还是静态,如果没有就默认静态;第3个参数指定生成库的源文件)
b
. set_target_properties:设置最终生成的库的名称,还有其它功能,如设置库的版本号等
c
. LIBRARY_OUTPUT_PATH: 库文件的默认输出路径,这里设置为工程目录下的lib目录
说明:
前面使用set_target_properties重新定义了库的输出名称,如果不使用set_target_properties也可以,那么库的名称就是add_library里定义的名称,只是连续2次使用add_library指定库名称时(第一个参数),这个名称不能相同,而set_target_properties可以把名称设置为相同,只是最终生成的库文件后缀不同(一个是.so,一个是.a),这样相对来说会好看点。
运行查看
cd build/
cmake ..
make
cd ../lib/
ls
可以看到在lib目录下生成了libfunc_a.a和libfunc_a.so这两个文件,这就是生成的静态库和动态库文件
2. 链接库文件
重新建一个工程目录,然后把上节生成的库拷贝过来,然后在在工程目录下新建src目录和bin目录,在src目录下添加一个main.c,main.c中调用库中的函数。
文件的存放结构如下所示:
project/
├── bin
├── build
├── CMakeLists.txt
├── src/
│ └── main.cpp
├── func_a/
│ ├── include/
│ │ └── a.h
│ └── lib/
│ ├── libfunc_a.a
│ └── libfunc_a.so
CMakeLists.txt的内容:
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)
# 指定可执行文件生成的目录
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.cpp)
# find a.h
include_directories (${PROJECT_SOURCE_DIR}/func_a/include)
find_library(TESTFUNC_LIB func_a HINTS ${PROJECT_SOURCE_DIR}/func_a/lib)
add_executable (main ${SRC_LIST})
target_link_libraries (main ${TESTFUNC_LIB})
出现两个新的命令:
a.
find_library: 在指定目录下查找指定库,并把库的绝对路径存放到变量里,其中,第一个参数是变量名称,第二个参数是库名称(find_library 将会自动添加 lib 前缀和库的后缀.a 或 .so),第三个参数是HINTS,第4个参数是路径,其它用法可以参考cmake文档
b
.target_link_libraries: 把目标文件与库文件进行链接
运行查看:
cd build/
cmake ..
make
cd ../bin/
./main
注意:
ps:在lib目录下有testFunc的静态库和动态库,find_library(TESTFUNC_LIB testFunc
…默认是查找动态库,如果想直接指定使用动态库还是静态库,可以写成:
链接动态库:
find_library(TESTFUNC_LIB libfunc_a.so HINTS ${PROJECT_SOURCE_DIR}/func_a/lib)
链接静态库:
find_library(TESTFUNC_LIB libfunc_a.a HINTS ${PROJECT_SOURCE_DIR}/func_a/lib)
四、条件编译
五、相关的问题
既然最后都是要生成Makeflie文件,为什么不直接编写?而是通过编写Cmake的CMakeLists.txt来生成?各自有什么有优缺点?
可以简单提一下cmake是什么:
1.对于简单的项目或小型项目,直接编写 Makefile 是很直接和高效的,因为你可以完全控制编译和链接过程。
2.对于中大型的项目来讲,手动编写和维护 Makefile 可能变得非常复杂。需要管理多个目标、依赖关系、条件判断等。
3.对于跨平台来讲,在不同的平台(如 Windows、Linux、macOS)上,编写和维护 Makefile 需要处理平台特定的差异。而Cmake设计用于跨平台构建,通过 CMakeLists.txt 文件,你可以定义项目构建的逻辑,CMake 会根据目标平台生成适当的构建系统文件(如 Makefile、Visual Studio 工程等)。这大大简化了在不同平台上的构建过程。
使用 CMake 生成 Makefile 的优缺点:没什么太大的缺点,唯一的可能就是需要花时间学习如何编写CmakeLists.txt如何编写吧,哈哈哈哈!!!!