Preface
- 在阅读这篇文章之前,建议先阅读另外一篇文章 基于VSCode的C++环境搭建及多文件编译、【G++编译器和GDB调试器】用法简介,尤其是第二篇。如果不了解
g++
编译过程及相关参数、不了解gdb
的调试特性及相关参数,那么我想对CMake语法的学习和理解也不可能太到位。 - 进阶小试,可以阅读另外一篇文章:cmake:target_** 中的 PUBLIC,PRIVATE,INTERFACE
- 另附vscode Linux环境下的
launch.json
、tasks.json
配置文件,适配CMake的构建,https://blog.csdn.net/qq_35866736/article/details/113943712
CMake语法特性介绍
-
基本语法格式:指令(参数1 参数2…)
- 参数使用括号括起
- 参数之间使用空格或分号分开
-
指令是大小写无关的,参数和变量是大小写相关的
set(Hello hello.cpp) add_executable(hello main.cpp hello.cpp) ADD_EXECUTABLE(hello main.cpp ${HELLO})
-
变量使用
${}
方式取址,但是在IF
控制语句中是直接使用变量名
1. 重要的指令和CMAKE常用变量
1.1 重要指令
cmake_minimum_required
-指定CMAKE的最小版本要求
#CMake最小版本要求
cmake_minimum_required(VERSION 2.8.3)
语法:cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])
project
- 定义工程名称,并可指定工程支持语言
# 指定工程名为HELLOWORLD
project(HELLOWORLD)
语法:project(projectname [CXX] [C] [Java])
set
- 显式定义变量
# 定义SRC变量,其值为main.cpp hello.cpp
set(SRC sayhello.cpp hello.cpp)
语法:set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
include_directories
- 向工程添加特定的头文件搜索路径 —>相当于指定g++编译器的-I
参数
# 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径
include_directories(/usr/include/myincludefolder ./include)
语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
link_directories
- 向工程添加特定的库文件搜索路径 —>相当于指定g++编译器的-L
参数
# 将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径
link_directories(/usr/lib/mylibfolder ./lib)
语法:link_directories(dir1 dir2 ...)
add_library
生成库文件
# 通过变量 SRC 生成 libhello.so 共享库
add_library(hello SHARED ${SRC})
语法:add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 … sourceN)
add_compile_options
- 添加编译参数
# 添加编译参数 -Wall -std=c++11
add_compile_options(-Wall -std=c++11 -O2)
add_executable
- 生成可执行文件
# 编译main.cpp生成可执行文件main
add_executable(main main.cpp)
语法:add_executable(exename source1 source2 … sourceN)
target_link_libraries
- 为 target 添加需要链接的共享库 —> 相同于指定g++编译器 − l -l −l 参数
# 将hello动态库文件链接到可执行文件main
target_link_libraries(main hello)
语法:target_link_libraries(target library1library2…)
add_subdirectory
- 向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
# 添加src子目录,src中需有一个CMakeLists.txt
add_subdirectory(src)
语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
aux_source_directory
- 发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
# 定义SRC变量,其值为当前目录下所有的源代码文件
aux_source_directory(. SRC)
# 编译SRC变量所代表的源代码文件,生成main可执行文件
add_executable(main ${SRC})
语法:aux_source_directory(dir VARIABLE)
1.2 CMake 常用变量
-
CMAKE_C_FLAGS
gcc编译选项 -
CMAKE_CXX_FLAGS
g++编译选项
# 在CMAKE_CXX_FLAGS编译选项后追加-std=c++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
CMAKE_BUILD_TYPE
编译类型(Debug, Release)
# 设定编译类型为debug,调试时需要选择debug
set(CMAKE_BUILD_TYPE Debug)
# 设定编译类型为release,发布时需要选择release
set(CMAKE_BUILD_TYPE Release)
-
CMAKE_BINARY_DIR
、PROJECT_BINARY_DIR
、<projectname>__BINARY_DIR
- 这三个变量指代的内容是一致的,指的是编译成的二进制目标文件目录。
- 如果是 in source build,指的就是工程顶层目录。
- 如果是 out-of-source 编译,指的是工程编译发生的目录。
- PROJECT_BINARY_DIR 跟其他指令稍有区别,不过现在,你可以理解为他们是一致的。
-
CMAKE_SOURCE_DIR
、PROJECT_SOURCE_DIR
、<projectname>__SOURCE_DIR
- 这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
- 也就是在 in source build时,他跟 CMAKE_BINARY_DIR 等变量一致。
- PROJECT_SOURCE_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
-
CMAKE_C_COMPILER
:指定C编译器 -
CMAKE_CXX_COMPILER
:指定C++编译器 -
EXECUTABLE_OUTPUT_PATH
:可执行文件输出的存放路径 -
LIBRARY_OUTPUT_PATH
:库文件输出的存放路径
2. CMake编译工程
CMake目录结构:项目主目录存在一个CMakeLists.txt文件
2.1 两种方式设置编译规则:
- 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加子目录即可;
- 包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中;
2.2 编译流程
在 linux 平台下使用 CMake 构建C/C++工程的流程如下
- 手动编写
CMakeLists.txt
- 执行命令
cmake PATH
生成Makefile(PATH是顶层CMakeLists.txt 所在的目录) - 执行命令
make
进行编译
2.3 两种构建方式
-
内部构建(in-source build):不推荐使用
内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终需要的,和工程源文件放在一起会显得杂乱无章。
## 内部构建
# 在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件
cmake .
# 执行make命令,生成target
make
-
外部构建(out-of-source build):推荐使用
将编译输出文件于源文件放在不同目录中
# 外部构建
# 1. 在当前目录下,创建build文件夹
mkdir build
# 2. 进入到build文件夹
cd build
# 3. 解析上级目录的CMakeLists.txt,生成Makefile和其他文件
cmake ..
# 4. 执行make命令,生成taget
make
3. CMake 代码实践
代码结构
.
├── CMakeLists.txt
├── include
│ └── swap.h
├── main.cpp
└── src
├── CMakeLists.txt
└── swap.cpp
构建思路:
- 将src下面的swap.cpp编译成
共享库
目标文件libswap.so
,并将libswap.so
放置在项目目录下的bin
目录中 - 利用外部构建方式。在项目目录下新建
build
文件夹,进而可以在build
下做cmake ..
操作
源码
- main.cpp
#include <iostream>
#include "swap.h"
using namespace std;
int main(int argc, char *argv[])
{
// int tmp = 5;
int a = 10;
int b = 20;
cout << "Before swap:" << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
swap(a, b);
cout << "After swap:" << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
- swap.cpp
#include "swap.h"
void swap(int& a, int& b){
int tmp = a;
a = b;
b = tmp;
}
CMakeLists.txt
./CMakeLists.txt
cmake_minimum_required(VERSION 3.2)
project(SWAP)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 设定库文件输出的存放路径
# 注意添加的顺序
set(CMAKE_BUILD_TYPE Debug) # 设定CMake的构建类型为Debug,发布时可设置为Release
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") #g++编译选项设定
add_subdirectory(${PROJECT_SOURCE_DIR}/src)
add_executable(cmake_main main.cpp) # 该行要放在下面两行的前面
target_include_directories(cmake_main PRIVATE ${PROJECT_SOURCE_DIR}/include) # 设定目标文件的头文件搜索路径
# 注意,目标文件可通过add_executable或add_library设定
target_link_libraries(cmake_main swap) #目标生成所需链接的库文件
./src/CMakeList.txt
add_library(swap SHARED swap.cpp) # 生成 共享库 目标文件
target_include_directories(swap PRIVATE ${PROJECT_SOURCE_DIR}/include)
构建
- 下面就是实践的构建操作指令,非常简单
mkdir build
cd build
cmake ..
make
- 实际的交互输入输出如下:
benjamin@ubuntu:~/CPPDev/g++prac$ mkdir build
benjamin@ubuntu:~/CPPDev/g++prac$ cd build
benjamin@ubuntu:~/CPPDev/g++prac/build$ cmake ..
-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/benjamin/CPPDev/g++prac/build
benjamin@ubuntu:~/CPPDev/g++prac/build$ make
Scanning dependencies of target swap
[ 25%] Building CXX object src/CMakeFiles/swap.dir/swap.cpp.o
[ 50%] Linking CXX shared library ../../bin/libswap.so
[ 50%] Built target swap
Scanning dependencies of target cmake_main
[ 75%] Building CXX object CMakeFiles/cmake_main.dir/main.cpp.o
[100%] Linking CXX executable cmake_main
[100%] Built target cmake_main
- 我们再次看看目录层次结构:
benjamin@ubuntu:~/CPPDev/g++prac/build$ cd ..
benjamin@ubuntu:~/CPPDev/g++prac$ tree .
.
├── bin
│ └── libswap.so
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ │ ├── 3.10.2
│ │ │ ├── CMakeCCompiler.cmake
│ │ │ ├── CMakeCXXCompiler.cmake
│ │ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ │ ├── CMakeSystem.cmake
│ │ │ ├── CompilerIdC
│ │ │ │ ├── a.out
│ │ │ │ ├── CMakeCCompilerId.c
│ │ │ │ └── tmp
│ │ │ └── CompilerIdCXX
│ │ │ ├── a.out
│ │ │ ├── CMakeCXXCompilerId.cpp
│ │ │ └── tmp
│ │ ├── cmake.check_cache
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── cmake_main.dir
│ │ │ ├── build.make
│ │ │ ├── cmake_clean.cmake
│ │ │ ├── CXX.includecache
│ │ │ ├── DependInfo.cmake
│ │ │ ├── depend.internal
│ │ │ ├── depend.make
│ │ │ ├── flags.make
│ │ │ ├── link.txt
│ │ │ ├── main.cpp.o
│ │ │ └── progress.make
│ │ ├── CMakeOutput.log
│ │ ├── CMakeTmp
│ │ ├── feature_tests.bin
│ │ ├── feature_tests.c
│ │ ├── feature_tests.cxx
│ │ ├── Makefile2
│ │ ├── Makefile.cmake
│ │ ├── progress.marks
│ │ └── TargetDirectories.txt
│ ├── cmake_install.cmake
│ ├── cmake_main
│ ├── Makefile
│ └── src
│ ├── CMakeFiles
│ │ ├── CMakeDirectoryInformation.cmake
│ │ ├── progress.marks
│ │ └── swap.dir
│ │ ├── build.make
│ │ ├── cmake_clean.cmake
│ │ ├── CXX.includecache
│ │ ├── DependInfo.cmake
│ │ ├── depend.internal
│ │ ├── depend.make
│ │ ├── flags.make
│ │ ├── link.txt
│ │ ├── progress.make
│ │ └── swap.cpp.o
│ ├── cmake_install.cmake
│ └── Makefile
├── CMakeLists.txt
├── include
│ └── swap.h
├── main.cpp
└── src
├── CMakeLists.txt
└── swap.cpp
15 directories, 53 files
从生成的目录结构可以看出,cmake生成的所有中间文件都在build目录中,这样会使得我们的开发主体结构十分清晰,这就是外部构建的好处。