CMake基础用法CMake介绍CMake是一个跨平台的安装（编译）工具，可以用简单的语句来描述安装（编译）过程。他能够输出各种各样的makefile或者project文件。笔者主要用CMake来构建CPP项目（但不仅限于CPP）。由于makefile通常依赖于当前的编译平台，编写makefile相较于麻烦一些，依赖也容易出错，而CMake恰好能解决上述问题。使用CMake编译项目流程如下：项目源码编写CMakeLists.txt文件执行cmake命令生成makefile文件执行make命令生成目标文件前提一个项目的目录大概是这样的. ├── bin ├── build ├── CMakeLists.txt ├── include ├── lib └── src下面对其介绍：bin目标文件生成目录build cmake命令生成的文件存放目录include 项目自己编写的头文件目录lib 库的目录src 源文件的目录CMakeLists.txt CMake配置文件，注意这个名称必须这样写（大小写也一致）我们接下来就根据这个目录结构来进行编写CMake的配置文件CMake使用注释注释行# 这是个行注释注释块#[[ 这是块注释 ]]指定cmake版本如指定Cmake最低版本为3.26cmake_minimum_required(VERSION 3.26)该命令并不是必须的，但是在编写项目的时候使用高版本的CMake命令通常会向下兼容，但使用低版本的CMake却没有高版本的特性，所以通常指定一个最低版本CMake以减少不必要的错误。设置项目名称例如名称设置为HelloWorldproject(HelloWorld)set的使用set命令可以自定义变量，而使用这些变量需要用${}将其名称包括进去例如我指定变量名为VAR_NAME，其值为var_valueset(VAR_NAME var_value) # 定义变量 message(${VAR_NAME}) # 使用变量并输出它的值设置CPP标准同理编译器可能默认使用低标准的C++，但是项目中包含高标准的特性，所以一般指定一个项目所使用的C++ 表准。例如项目中使用了auto关键字，这个是C++11标准里的，编译器可能默认C++98标准，这时候编译就会报错。set(CMAKE_CXX_STANDARD 11)设置可执行文件输出路径设置EXECUTABLE_OUTPUT_PATH宏来指定可执行程序的输出命令set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)其中CMAKE_SOURCE_DIR为CMakeLists.txt的目录这句话的含义是将可执行文件放到了./bin目录下设置库的输出目录设置LIBRARY_OUTPUT_PATH宏来指定生成库的输出目录set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib)搜索文件搜索源文件，有两种方式我们项目的源文件都在./src目录下将搜索到的源文件路径存放到SRC这个变量里利用aux_source_directoryaux_source_directory(${CMAKE_SOURCE_DIR}/src SRC)使用filefile(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)这里file命令的第一个参数如果是GLOB则只搜索当前目录，如果是GLOB_RECURSE则进行递归搜索设置头文件目录include_directories(${PROJECT_SOURCE_DIR}/include)这里宏PROJECT_SOURCE_DIR对应项目根目录打印日志信息# 打印HelloWorld（重要信息） message("HelloWorld") # 打印Hello(非重要信息) message(STATUS "Hello")message命令第一个参数可指定消息类型：如果没有第一个参数，则这是重要信息STATUS：则这是非重要信息WARNING：警告信息，但会继续执行后面的命令AUTHOR_WARNING：警告信息(dev)，但会继续执行后面的命令SEND_ERROR：错误信息，继续执行，但会跳过生成的步骤FATAL_ERROR：错误信息，中止所有处理的过程字符串追加使用set拼接# 省略号表示参数数量不固定 set(变量名 ${变量1} ${变量2} ...)用法如下# 定义一些字符串 set(VAR1 hello) set(VAR2 world) # 拼接字符串，FINAL_STR的值为helloworldhaha123 set(FINAL_STR ${VAR1} ${VAR2} haha123)使用list拼接# FINAL_STR的值为helloworldhaha123 list(APPEND FINAL_STR ${VAR1} ${VAR2} "haha123")删除子字符串CMake的删除子字符串，是将上述字符串拼接中每一个字符串看作一个单位进行操作的。可以把它类比为一个list<string>类型，其删除操作是删除其中的一个元素，而非一个真正意义上的子串。以前文拼接好的FINAL_STR为例子：list(REMOVE_ITEM FINAL_STR "haha") # 此时FINAL_STR仍为helloworldhaha123，因为并没haha这个元素list(REMOVE_ITEM FINAL_STR "haha123") # 此时为helloworldlist的其他用法在前面已经提到过了list命令可以追加、删除子字符串，list第一个参数还能决定其他用法。获取list长度（字符串元素个数）list(LENGTH FINAL_STR RESULT_NUMBER) # RESULT_NUMBER为接受结果的变量，FINAL_STR是需要获取长度的字符串（列表）指定索引位置元素# 获取第一个元素，FIRST_ITEM为接受结果的变量 list(GET 0 FINAL_STR FIRST_ITEM) # 获取最后一个元素，索引可以为负数;-1是倒数第一个，-2是倒数第二个，以此类推 list(GET -1 FINAL_STR LAST_ITEM)生成库文件库文件一般有两种，动态库(共享库)和静态库，二者的区别我们将在下文中的引入库文件部分说明。我们设SRC为源文件目录，libName为库的名称生成静态库可用如下命令add_library(libName STATIC ${SRC})生成动态库可用如下命令add_library(libName SHARED ${SRC})生成可执行程序源文件地址在SRC中，生成app名称的可执行文件可用以下命令#生成可执行文件 add_executable(app ${SRC})引入库文件如果调用静态库会被打包进可执行文件中，操作系统不知道它们可以复用，因此每次启动都会随可执行程序加载到内存中（如果用的话，取决于操作系统的内存管理技术）；而动态库在可以在多个进程共享，它们只会被加载到内存一次，且不会打包进可执行文件中。（详情参考操作系统，此处不过多解释）我们以链接lib目录下的静态库为例#设置非系统提供的库目录 link_directories(${PROJECT_SOURCE_DIR}/lib) #链接静态库 link_libraries(model)如果库是系统给出的，则一般无需使用link_directories命令，如果是自己或第三方提供的则需要。注意link_libraries的参数，这里参数为库名称，可以是全名libmodel.a，也可以是lib与后缀名.a之间的部分。（在Linux下后缀为.a而Windows下为.lib，以Linux为例Win同理）然后是动态库，同样以lib目录为例假设我们生成的可执行文件为app，源文件目录变量为SRC，只需在add_executable之后添加target_link_libraries即可#设置非系统提供的库目录 link_directories(${PROJECT_SOURCE_DIR}/lib) #生成可执行文件 add_executable(app ${SRC}) #链接动态库 target_link_libraries(app model)对于target_link_libraries第一个参数为需要加载动态库的目标文件（不是动态库的文件，是需要将动态库加载进去的文件），后面可跟多个参数，为动态库文件。而每个动态库文件参数之前可设置动态库访问权限，有PUBLIC、PRIVATE和INTERFACE三种，默认为PUBLIC：PUBLIC： 在PUBLIC后面的库会被链接到目标文件，并且符号会被导出提供给第三方。也就是说动态库链接有传递性，如果目标程序引入了一个动态库，而这个动态库引入了其他库，目标程序也会引入其他库PRIVATE： 在PRIVATE后面的库仅会被链接到目标文件，传递性会被终结，中间动态库为PRIVATE不会将它引入的其他库导入到目标程序。INTERFACE：在INTERFACE后面的库不会被链接，只会导出符号CMake嵌套在大型项目中，通常分为多个模块，会有很多目录，CMake允许子目录下拥有相应的CMakeLists.txt文件。在这些文件中的变量有着各自的作用域：父节点的变量可以在子节点使用子节点变量只能在当前文件中使用根节点变量全局有效总结一下就是子节点能使用在它之上层级的变量我们首先来看一个项目的目录结构. ├── build ├── CMakeLists.txt ├── include │ ├── model2.h │ └── model.h ├── model │ ├── CMakeLists.txt │ └── model.cpp ├── model2 │ ├── CMakeLists.txt │ └── model2.cpp ├── testModel │ ├── CMakeLists.txt │ └── test.cpp └── testModel2 ├── CMakeLists.txt └── test2.cpp 其中，include存放头文件，model为模块1，model2为模块2，testModel与testModel2分别对模块1和模块2进行测试。在此之前我们在根目录文件下设置一些变量#静态库路径 set(LIBPATH ${PROJECT_SOURCE_DIR}/lib) #可执行程序的存储目录 set(EXECPATH ${PROJECT_SOURCE_DIR}/bin) #头文件路径 set(HEADPATH ${PROJECT_SOURCE_DIR}/include) #库文件名字 set(MODEL model) set(MODEL2 model2) #可执行文件名称 set(TEST1 test1) set(TEST2 test2)为了构建CMake目录的父子关系，我们需要使用一条命令add_subdirectory#添加子目录，注意名称与目录名称一致 add_subdirectory(model) add_subdirectory(model2) add_subdirectory(testModel) add_subdirectory(testModel2)接下来对子目录下的CMakeLists.txt来进行配置我们以模块1下的为例，模块2同理，只不过要改成对应的变量名cmake_minimum_required(VERSION 3.26) project(model) #搜索源文件，子节点变量SRC不能给父文件 aux_source_directory(./ SRC) #制定头文件,使用父节点的变量HEADPATH include_directories(${HEADPATH}) #指定输出目录，使用父节点变量LIBPATH set(LIBRARY_OUTPUT_PATH ${LIBPATH}) #生成静态库 add_library(${MODEL} STATIC ${SRC})然后对测试部分的CMakeLists.txt文件配置，以testModel为例，其余同理cmake_minimum_required(VERSION 3.26) project(test) #搜索源文件，子节点变量SRC不能给父文件 aux_source_directory(./ SRC) #制定头文件,使用父节点的变量HEADPATH include_directories(${HEADPATH}) #指定输出目录，使用父节点变量EXECPATH set(EXECUTABLE_OUTPUT_PATH ${EXECPATH}) #生成可执行文件 add_executable(${TEST1} ${SRC})其它CMake还有一些流程控制语句，有些与版本有关，这部分内容放在以后的文章写生成生成很简单，假设当前为项目文件根目录，执行以下命令，然后你就可以看到生成的文件了（cmake会先生成makefile文件，然后再编译）mkdir build cd build cmake .. make