cmake实践

https://www.hahack.com/codes/cmake/

从头到尾构建一个cmake系统,记录过程中的点点滴滴。

因为系统要从windows换到linux,不可避免的除了底层网络架构要重写外,整个工程也要重新搭建。windows下是visual studio,linux下只能用qt creator或者clion之类的,工程管理就要单独拿出来用cmake处理了。

创建一个cmake文件

cmake的文件,统一规定命名为CMakeLists.txt。是一个文本文件。一开始的内容如下,cmake的注释是#

#必须,需要什么版本的cmake,这个是最低版本
cmake_minimum_required(VERSION 3.5)

#必须,工程名字,可以不跟后面的LANGUAGES CXX,这个表示是c++工程
project(pserver LANGUAGES CXX)

#可以不设置这两条,如果有需要,建议设置,不然会根据默认值设置,这里第一条是使用c++第几个版本,第二条是上面那条是否起作用
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#必须,指定生成最后的目标文件,这里就是把main.cpp指定生成pserver可执行文件
add_executable(pserver main.cpp)

在当前目录执行cmake .,就可以编译cmake文件了,生成一个对应的makefile文件,然后再运行make,就可以编译出你的程序了。

引入其他源文件1

我们需要把其他的源文件引入进来,可以直接在add_executable增加,我们看一下add_executable的官方文档

add_executable

https://cmake.org/cmake/help/latest/command/add_executable.html

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

增加一个可执行目标,名字是<name>,通过列出的所有源文件编译而来。如果通过target_sources()增加源文件,这里可以忽略。<name>就是本地目标文件的名字,必须在工程中是唯一的。具体的文件名字会根据本地系统的平台而有所不通,比如<name>.exe 或者仅仅是<name>。

默认情况下,这个可执行文件会创建在编译树的目录中,这个目录与源码树是一致的。RUNTIME_OUTPUT_DIRECTORY可以改变这个路径。OUTPUT_NAME会修改最终的文件名字。

如果WIN32设置了,那么WIN32_EXECUTABLE就会被增加到工程中。

如果MACOSX_BUNDLE设置了,那么相应的参数也会被增加到工程中。

如果EXCLUDE_FROM_ALL设置了,相应的属性也会增加到工程中。

add_executable中的原始命令可以使用基本的表达式,也就是这种符号格式 $<...>来添加。

如果一些源文件已经提前处理了,你只是像让IDE知道源文件的位置,可以使用HEADER_FILE_ONLY

上面是常规模式,add_executable还有导入模式和重命名模式。从上面命令可以看出,我们可以把源文件全部都添加到这个命令中

#必须,需要什么版本的cmake,这个是最低版本
cmake_minimum_required(VERSION 3.5)

#必须,工程名字,可以不跟后面的LANGUAGES CXX,这个表示是c++工程
project(pserver LANGUAGES CXX)

#可以不设置这两条,如果有需要,建议设置,不然会根据默认值设置,这里第一条是使用c++第几个版本,第二条是上面那条是否起作用
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#必须,指定生成最后的目标文件,这里就是把main.cpp指定生成pserver可执行文件
add_executable(pserver main.cpp s1.cpp s2.cpp)

根据这个方法,把我们的cmake修改成上面的样子,在add_executable中增加s1.cpp s2.cpp。如果我们的工程文件很多,这种方法显然不合适了。我们看一下其他方法。

引入其他源文件2

如果你的工程,源文件都在一个目录或者几个目录下,并且非常多,可以使用aux_source_directory命令。

aux_source_directory

https://cmake.org/cmake/help/latest/command/aux_source_directory.html

aux_source_directory(<dir> <variable>)

查找一个目录下的所有源文件。搜集<dir>目录下所有源文件,并且把数据保存在给定的变量<variable>中。这个命令的目的是使工程使用一个明确的模板实例。模板实例的文件可以保存在模板的子目录中,并且自动的使用这个命令搜集起来,避免手动一个个增加。

这个是为了避免在创建类库或者可执行文件的时候写一长串源文件列表。虽然可以工作,但是不能让cmake生成编译系统知道一个新文件增加。一般是在重新构建编译系统的时候,才会知道,因为CMakeLists.txt被修改了。当一个源文件被增减到目录中,但是没有修改这个文件,那么需要手动重新运行CMake来构建基于这个新文件的编译系统。

这个命令就是把一个目录下的所有源文件保存到变量中,然后方便使用。其中官方文档也说了,如果你在这个目录中新增加了文件,那么最后生成的编译系统,比如make,是不知道有新文件增加的,你需要重新运行cmake,再次生成makefile文件。

#必须,需要什么版本的cmake,这个是最低版本
cmake_minimum_required(VERSION 3.5)

#必须,工程名字,可以不跟后面的LANGUAGES CXX,这个表示是c++工程
project(pserver LANGUAGES CXX)

#可以不设置这两条,如果有需要,建议设置,不然会根据默认值设置,这里第一条是使用c++第几个版本,第二条是上面那条是否起作用
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#把本目录中所有的源文件记录到SRCFS中
aux_source_directory(. SRCFS)

#必须,指定生成最后的目标文件,引用SRCFS,生成可执行文件
add_executable(pserver ${SRCFS})

这个命令可以多次调用,比如你有多个目录,可以增加aux_source_directory(path1 P1SRCFS) aux_source_directory(path1 P1SRCFS),然后只需在add_executable把对应的变量按照顺序增加就好了add_executable(pserver ${SRCFS} ${P1SRCFS} ${P2SRCFS})

引入其他源文件3

除了上面的方法外,还可以把其他模块的内容(比如一个目录一个模块,或者随便你怎么区分)编译成一个静态库,然后再链接引用静态库。

比如我们有一个模块的代码在subdir下面,那么先在这个下面创建一个CMakeLists.txt文件

#把子目录下的源文件保存到SUBDIR
aux_source_directory(. SUBDIR)

#根据源文件生成静态库submod
add_library (submod ${SUBDIR})

修改我们主目录下的CMakeLists.txt

#必须,需要什么版本的cmake,这个是最低版本
cmake_minimum_required(VERSION 3.5)

#必须,工程名字,可以不跟后面的LANGUAGES CXX,这个表示是c++工程
project(pserver LANGUAGES CXX)

#可以不设置这两条,如果有需要,建议设置,不然会根据默认值设置,这里第一条是使用c++第几个版本,第二条是上面那条是否起作用
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#把子目录添加进来,这样就会自动编译子目录的cmake文件
add_subdirectory(math)

#把本目录中所有的源文件记录到SRCFS中
aux_source_directory(. SRCFS)

#必须,指定生成最后的目标文件,引用SRCFS,生成可执行文件
add_executable(pserver ${SRCFS})

#把子目录的链接库链接到目标文件
target_link_libraries(pserver submod)

这样的话,就可以把子目录的源码按照静态库的方式添加到最后的可执行文件中了。

增加调试信息

有时候我们碰到崩溃的问题,需要获取崩溃时候的信息或者需要调试运行,这时需要增加调试信息。不然获取的线索可能不完全。

正常我们使用gcc/g++编译的时候,如果要增加调试信息,需要增加-g的参数。cmake最终是需要gcc/g++编译,所以就是在cmake中为最后gcc/g++编译的时候增加调试信息的参数。

第一种方法

在CMakeLists.txt生成目标文件前增加如下语句

add_definitions("-g")

这个命令就是为编译器增加一个-g的参数

https://cmake.org/cmake/help/latest/command/add_definitions.html

add_definitions

增加一个 -D 的定义标签为编译源文件的编译器。

add_definitions(-DFOO -DBAR ...)

为编译当前目录的编译器命令行增加一个宏定义,不管是在这个命令调用前,还是调用后,都可以,并且在子目录中的需要稍后增加。这个命令可以用作增加任意标签,但是建议增加预处理相关的宏。

注意

这个命令已经废弃了,建议使用如下命令:

  • add_compile_definitions() 增加预处理相关的定义

  • include_directories() 增加引入的目录

  • add_compile_options() 增加其他选项

以-D或者/D开头的标签,看上去像预处理的定义,被自动添加到 COMPILE_DEFINITIONS 当前目录属性中。定义非正常的值,有可能会保留在标志的集合中,而不是向后兼容。

如果有子模块,也需要调试信息,就要在添加子模块之前增加这条语句。比如上面第三种添加源文件的方法,在子目录编译了一个类库,那么就需要在引用那个类库对应的CMakeLists.txt之前增加,不然,类库编译出来的就没有调试信息,如果是类库中出现了崩溃或者需要跟踪调试,就无法获得对应的信息。

当然,从官方文档中得知,这个方法已经废弃了,那么我们就使用其他方法。

第二种方法

在CMakeLists.txt生成目标文件前增加如下语句

add_compile_options(-g)

add_compile_options

为编译源文件增加一个参数。

add_compile_options(<option> ...)

为目录属性 COMPILE_OPTIONS 增加一个参数。这个参数在编译当前目录或者下级目录的时候使用。

第三种方法

增加如下语句

set(CMAKE_BUILD_TYPE "Debug")

这个是设置编译环境为什么类型,可选类型有Debug, Release, RelWithDebInfo, MinSizeRel

https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html

CMAKE_BUILD_TYPE

定义编译类型在单独的配置构建器中。

这个静态的定义编译类型(配置),将会编译到编译树中。值有可能是空 Debug, Release, RelWithDebInfo, MinSizeRel 等等。这个数值仅仅意味着单独配置构建器(比如 Makefile 构建器或者 Ninja)。也就是这个选择一个单独的配置,在CMake运行去构建一个编译树的时候,而不是多个配置构建器提供在编译配置中构建编译环境时候的选择。有很多提前配置的属性和参数(一般明确的跟在 SOME_VAR_<CONFIG> 后面),比如 CMAKE_C_FLAGS_<CONFIG>,使用大写定义: CMAKE_C_FLAGS_[DEBUG|RELEASE|RELWITHDEBINFO|MINSIZEREL|...]。比如,在编译树中配置编译类型为 Debug,CMake可能会增加 CMAKE_C_FLAGS_DEBUG 设置到 CMAKE_C_FLAGS 中。

  • Debug 调试版本,调试信息最全,速度最慢,编译的产物最大

  • Release 发布版本,调试信息不全,速度快,产物小。基本上无法获得详细的调试信息和崩溃后的日志

  • RelWithDebInfo 附带调试信息的发布版本,调试信息现对来说全,有些地方可能被优化了,速度快,产物比较大

  • MinSizeRel 最小发布版本,调试信息不全,速度快,产物最小。基本上无发获得详细的调试信息和崩溃后的日志

这4个参数中,如果正常发布,就是Release或者MinSizeRel。后者就是进一步减小了编译产物的体积。如果是调试信息,那么就使用Debug。如果是线上,并且还需要调试信息,那么可以使用RelWithDebInfo,临时调试查找bug

引入目录

项目中我们肯定会遇到引入第三方库的情况,首先就是要先引入头文件,一般我们会把第三方库按目录分类,在代码中为了方便编写,会直接引用头文件的名字,而不必在意所在的目录。这样就需要我们工程配置的时候把对应头文件的目录引入进来。

https://cmake.org/cmake/help/latest/command/include_directories.html

include_directories

增加一个引入目录到工程中。

include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

增加一个给定的目录到工程中,用作引入文件的时候查找。相对路径是基于当前的源码目录。

引入的文件会被增加到当前CMakeLists文件的目录属性INCLUDE_DIRECTORIES中。同样会增加INCLUDE_DIRECTORIES的目标属性到当前的CMakeLists文件中。目标属性是用作构建的时候。

默认情况下,这个目录会被附加到当前的目录列表的最后。这个默认行为可以通过设置CMAKE_INCLUDE_DIRECTORIES_BEFORE为ON进行修改。通过使用AFTER或者BEFORE,可以选择插入到以来的目录前后。

如果SYSTEM设置了,编译器会把这个目录在一些平台下当作系统引入目录。标记这个设置可能会产生一些影响,比如跳过编译时的警告,或者这些固定安装的系统文件不在考虑范围内。

原文地址:https://www.cnblogs.com/studywithallofyou/p/14172791.html