一、基本的CMake语法
标准的CMake工程结构应该是这样的:
-
CMakeLists.txt
此CMakeLists.txt是顶层CMake文件,内容包括:#指定版本号 CMAKE_MINIMUM_REQUIRED(VERSION 3.17.5) #指定工程名字,工程语言,定义了PROJECT( DIR ),则此CMake文件所在的目录会被视为全局变量PROJECT_SOURCE_DIR的值 PROJECT(demo4 CXX) #添加源文件所在的目录 ADD_SUBDIRECTORY(${PROJECT_SOURCE_DIR}/mylib) ADD_SUBDIRECTORY(${PROJECT_SOURCE_DIR}/src)
-
build
build目录是编译工程文件,我们执行cmake命令生成makefile文件就是在此目录中,生成的内容都是中间文件和编译过后的二进制文件。build文件夹下应该包括bin和lib文件夹,bin文件夹中存放最终的可执行文件,lib文件夹下存放依赖库。
我们会在此文件夹中执行:cmake .. #执行完上述命令后,就会生成makefile文件 make #执行完上述命令后,就会编译链接源文件 #进入build目录下盛放入口函数的目录下,一般是src目录,而后执行入口函数二进制文件
-
lib
lib文件夹盛放库源代码文件,库文件的内容都在此文件夹内,此文件夹内的Cmake文件内容如下:#如果需要包含头文件,应该加上INCLUDE_DIRECTORIES(PATH) INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include) #设置生成的二进制库的存放位置位于build/lib SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) #将源文件添加进一个变量中 AUX_SOURCE_DIRECTORY(. DIR_LIB_SRCS) #static参数指明是静态库,会在编译的时候将二进制代码嵌入最后唯一的可执行文件, #shared参数指明是动态库,编译的时候不会将二进制代码嵌入执行文件,而是生成一个二进制文件,在执行时再动态加载这个二进制文件供别的模块调用 #此处生成了一个二进制库叫做Mylib ADD_LIBRARY(Mylib static/shared ${DIR_LIB_SRCS})
-
src
src文件夹盛放项目的源文件,源文件的Cmake文件内容如下:#源文件可能会用到其他文件夹下的头文件,增加头文件寻找路径 INCLUDE_DIRECTORIES(path) #设置产生的二进制文件放在bin文件夹中 #PROJECT_BINARY_DIR的含义是在那个文件夹中执行cmake命令 #哪个文件夹就是PROJECT_BINARY_DIR SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) #将当前文件夹下的源文件打包为DIR_SRCS变量 AUX_SOURCE_DIRECTORY(./ DIR_SRCS) #将源文件编译为demo二进制文件 ADD_EXECUTABLE(demo ${DIR_SRCS}) #指明demo二进制文件需要链接的二进制库,此处Mylib正是我们在lib目录下的 #CMake文件中定义的二进制库 TARGET_LINK_LIBRARIES(demo Mylib)
-
include
此文件夹存放头文件
二、自定义编译选项
通过ccmake实现自定义编译选项
一般情况下,使用宏来确定代码到底如何执行。
CMake支持在编译时动态调整宏。
具体而言:
-
假设有一个CMake文件:
CMakeLists.txtOPTION(TEST "define for TEST" ON) #假设PATH1代表一个文本文件,PATH2代表根据此文本文件生成的头文件 CONFIGURE_FILE("PAHT1" "PATH2")
-
假设有PATH1指代的文本文件:
#cmakedefine TEST
-
执行cmake .
会生成PATH1对应的文本文件指定的头文件
这其中的过程是这样的:
OPTION中定义的变量TEST和PATH1文件中指定的宏TEST绑定,因为我们默认TEST为ON,所以宏TEST被定义了
生成的头文件内容为:#define TEST
这个头文件可以被其他文件包含,一旦被包含,也就相当于包含了#define TEST,TEST就被宏定义了。我们在源文件中可以通过
#ifdef TEST xxx #else xxx #endif
来指定执行部分代码。
-
以上我们通过一个OPTION命令指定了头文件中有TEST的宏定义。如何让TEST没有宏呢?
执行ccmake .
弹出的界面中使用方向键上下选择宏,回车改变宏的值,ON为开启,OFF为关闭。
修改完后,按c键保存配置,再按g键生成新的头文件。
之后重新执行make命令,就可以用新生成的头文件去重新编译代码。 -
一般情况下,把所有开关放在options.txt文件中,在顶层CMakeLists.txt文件中使用INCLUDE执行将开关内容包含进来。
-
在执行cmake命令的时候,可以通过-Dxxx=ON/OFF来指定宏开关一开始对应的值。
三、使用CMake和VScode搭建Debug环境
-
在顶层CMakeLists.txt中添加:
ADD_DEFINITIONS(-std=c++11) SET(CMAKE_BUILD_TYPE "Debug") #debug模式编译,参数-O0表示完全不进行代码优化 -Wall表示打印警告信息 #调试的选项: #-g 调试信息由系统生成,但是gdb可以合理封装使用 #-ggdb 专门为gdb生成调试信息 #-g3 调试级别更高,3级别可以调试宏 #-ggdb3 调试级别为3,且专门为gdb调试器生成调试信息 SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -ggdb") SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
-
在VScode文件夹下,点击运行——调试——C++(DLL/GDB)
-
对2中生成的launch.json配置文件,修改以下内容
"program": "${workspaceFolder}/build/bin/demo6",
关于${workspaceFolder},此变量指向的是.vscode文件夹所在的目录
四、 搭建简易工程
建立工程项目目录如下:
顶层CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.17.5)
project(Test CXX)
#源文件所在子目录
ADD_SUBDIRECTORY(${PROJECT_SOURCE_DIR}/src)
src/CMakeLists.txt文件:
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/out)
AUX_SOURCE_DIRECTORY(./ DIR_SRCS)
ADD_EXECUTABLE(main ${DIR_SRCS})
执行过程:
-
头文件一律放在include文件夹
-
源文件一律放在src文件夹
-
到build文件夹中执行:
#执行之前可以通过: scl --list 获取从scl安装的gcc各版本 scl enable devtoolset-11 bash 可以使用最新版本的gcc作为编译器 cmake .. ->通过顶层CMakeLists.txt文件构建makefile make ->执行makefile文件,构建项目
-
项目的输出文件在build/out文件夹下,一个名为main的可执行文件。
-
让简易工程处于可调式模式
在顶层CMakeLists.txt文件中添加以下内容SET(CMAKE_BUILD_TYPE "Debug") SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -ggdb") SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
重新cmake,make,生成的可执行文件就是可调试的。
如果不想调试,则将SET(CMAKE_BUILD_TYPE “Debug”)中的"Debug"修改为“Release”,而后重新cmake,make便可。
【注1】调试需要安装gdb,配置gdb的环境变量。
【注2】在vscode中,可以使用launch.json文件配置调试,过程如下:- 删除task.json,因为此文件是配置编译动作的文件。
- 将launch.json的“prelaunchtask”参数对应选项删除,这是删除了执行调试前编译工作,因为我们已经使用了CMake编译了工程。
- 将launch.json中"program": 后的参数为**"${workspaceFolder}/build/out/main"**,针对我们生成的main可执行文件进行调试
- 将launch.json 中的 “miDebuggerPath”:后的参数改为gdb的地址(如果有环境变量,可以直接填写gdb)
- 如果添加了新的源文件或者删除了原有的源文件,则可在被修改文件目录中把其对应的CMakeLists.txt文件中添加或者删除一个空格。然后直接make就可以重新编译整个工程,并且编译整个工程的时候可以利用以前的已经编译好的文件。
- 为什么?假装修改了CMakeLists.txt文件后,执行make,因为生成的makefile顶层文件中实际的项目目标main,是依赖于CMakeLists.txt项目的,所以一旦修改了CMakeLists.txt再执行make命令,就可以重新解析CMakeLists.txt,从而改变我们正在执行的makefile文件,给makefile文件增添或者减少子目标文件。依赖项CMakeLists.txt执行完成后,开始执行工程文件编译,但是因为已经修改了makefile,则会按照新的makefile来编译。但是因为没有改变原来的makefile缓存目录,所以就针对新添加的文件或者修改的文件进行重新编译,而其他未涉及到的源文件则不需要重新编译。
五、 修改文件之后cmake还是make?
修改了CMakeLists.txt文件,就把build文件夹下的CMakeChache.txt文件删掉,然后重新cmake,重新make。
如果修改的是源文件或者头文件,则直接make就可以。