CMake构建类型和编译选项

Posted 木大白易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CMake构建类型和编译选项相关的知识,希望对你有一定的参考价值。

本文转自:https://hijk.tech/cmake-tutorial/05/

构建类型

构建类型(Build Type)指的是编译构建一个代码工程时采用的配置。对于使用IDE的工程,一般可以在IDE内的选项上修改构建类型,例如Visual Studio工程中的Configuration,默认分为Debug和Release,在每次编译前可以选择使用哪一种。但对于命令行式的构建系统,一般需要自己调整makefile中的编译选项,来实现不同的构建类型。使用cmake可以不必手动修改编译选项,能够方便的切换构建类型。

设置构建类型

CMake预先内置了四种构建类型:Debug,Release,RelWithDebInfo,MinSizeRel,可以满足大部分的使用情况,并通过预置的变量CMAKE_BUILD_TYPE表示当前的构建类型,可以通过修改它的值来改变构建类型,变量的初始值为空,表示不指定任何构建类型。

下面我们使用不同的构建模式来编译一个简单工程。

CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(P1 LANGUAGES CXX)
set(CMAKE_BUILD_TYPE Debug)
message(STATUS "Build Type: $CMAKE_BUILD_TYPE")
add_executable(app main.cpp)

在这个工程中,我们使用set命令将变量CMAKE_BUILD_TYPE的值设置成Debug,表示使用debug模式编译。需要注意,设置CMAKE_BUILD_TYPE要在添加target之前进行。

在构建目录中,运行cmakemake VERBOSE=1VERBOSE=1可以使编译过程输出更详细的信息,包括编译器的调用参数。查看输出的信息,可以看到编译main.cpp时,添加了-g参数来产生调试信息,而不指定构建模式的时候是不会有-g参数的。

CMake预置的构建模式对应的编译参数示例如下(Linux GCC环境):

无:
c++ -o CMakeFiles/app.dir/main.cpp.o -c /home/zldd/cmake/project_1/main.cpp
Debug:
c++ -g -o CMakeFiles/app.dir/main.cpp.o -c /home/zldd/cmake/project_1/main.cpp
Release:
c++ -O3 -DNDEBUG -o CMakeFiles/app.dir/main.cpp.o -c /home/zldd/cmake/project_1/main.cpp
RelWithDebInfo:
c++ -O2 -g -DNDEBUG -o CMakeFiles/app.dir/main.cpp.o -c /home/zldd/cmake/project_1/main.cpp
MinSizeRel:
c++ -Os -DNDEBUG -o CMakeFiles/app.dir/main.cpp.o -c /home/zldd/cmake/project_1/main.cpp

除了在CMakeLists.txt中使用set命令设置CMAKE_BUILD_TYPE之外,还可以在运行cmake时直接指定。cmake提供了一个-D参数,用来指定某个变量的初始值,调用格式为-D<variable_name>=<value>。例如:

cmake ../source_dir -DCMAKE_BUILD_TYPE=Release

这样也可以设置构建类型。但请注意,-D参数只是设置变量的初始值,如果在CMakeLists.txt中使用set命令再次修改了CMAKE_BUILD_TYPE,那么构建类型以最后set修改的为准。

实际中,CMakeLists.txt作为项目的配置,一般不宜经常修改,更常用cmake运行参数来控制构建类型。但我们可以在CMakeLists.txt中指定默认的构建类型,例如:

if(CMAKE_BUILD_TYPE STREQUAL "")
    set(CMAKE_BUILD_TYPE "Debug")
endif()

这样一来,如果没有用-D参数指明构建类型,则采用Debug构建。

系统平台相关性

有些时候,代码项目需要做跨平台支持,比如在Windows和Linux上都需要做编译。这时不可避免的需要做一些平台相关性的处理,CMake提供了一些预置的变量来表示当前的系统平台或编译环境,当对应的环境匹配时被定义,且值为True,常见的有:

  • WIN32:表示当前系统为Windows(包64位系统)。
  • UNIX:表示当前系统为Unix以及Unix-like系统(包括linux,cygwin等)。
  • APPLE:表示当前系统为MacOS。
  • MSVC:表示当前编译环境为Microsoft Visual C++。
  • MINGW:表示当前编译环境位MinGW。
    利用这些变量进行判断,就可以进行一些平台或环境的差异处理了。比如:
if(WIN32)
   ... ...
elseif(UNIX)
   ... ...
else()

自定义编译选项

除了可以让cmake根据构建类型自动调整编译选项外,也可以自己添加编译选项,可以对所有target添加全局的编译选项,也可以为某个target单独添加编译选项。

全局编译选项

添加全局编译选项使用add_compile_option命令,它接收任意个参数,在编译时,无论当前是什么构建类型,这些参数都会被传递给编译器。比如,GCC默认不输出警告信息,可以通过这个命令添加开启警告的选项。

cmake_minimum_required(VERSION 3.0)
project(P1 LANGUAGES CXX)
set(CMAKE_BUILD_TYPE Debug)
message(STATUS "Build Type: $CMAKE_BUILD_TYPE")
if(UNIX)
    add_compile_options("-Wall" "-Wextra" "-Wpedantic")
endif()
add_executable(app main.cpp)

修改后的CMakeLists.txt中,通过add_compile_options命令添加了"-Wall" "-Wextra" "-Wpedantic"这三个编译选项,它们都是gcc的警告开关选项。再次运行cmakemake,如果代码存在一些问题的话,输出中就会包含警告信息。

需要注意:add_compile_options命令只对其后添加的target有效。也就是说,如果一个target是在调用add_compile_options之前添加的,那这些编译参数不会对这个target生效。

实际项目中,一般在配置的开始,在增加任何target之前使用add_compile_options命令,来设置一些全局的编译选项,比如为所有target打开编译警告输出。另外,除了可以添加全局的编译参数,还可以为某个target单独增加编译选项。

Target编译选项

要为某个target添加编译选项需要使用target_compile_options命令,其完整形式如下:

target_compile_options(<target> [BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

例如,

target_compile_options(app PRIVATE "-Wall" "-Wextra" "-Wpedantic")

表示为app增加这三个警告选项。关键字PRIVATE表示这写选项仅对app自己生效(此命令中的INTERFACE|PUBLIC|PRIVATE参数与target_link_libraries命令中的意思一样)。可以一次性添加多个选项,也可以多次调用添加,比如,

target_compile_options(app PRIVATE "-Wall")
target_compile_options(app PRIVATE "-Wextra")
target_compile_options(app PRIVATE "-Wpedantic")

关键字BEFORE是可选的,表示添加的选项要放在所有编译选项的最前面,而不是追加。

一般来说,编译选项中,预定义宏(Predefined Macro)和包含路径(Include Path)这两个选项较为常见,CMake提供了单独的命令来设置它们。

预定义宏(Predefined Macro)

同样,设置预定义宏也有两个命令,分别用于全局的和某个target。

命令add_definitions用于添加全局的宏定义,对所有target都有效。例如:

# 预定义宏AAA和BBB
add_definitions(-DAAA -DBBB)
# 带值的预定义宏
add_definitions(-DVERSION=123 -DNAME="string") 

命令target_compile_definitions用于给某个target添加预定义宏,形式与target_compile_options非常类似,例如:

# 预定义宏AAA和BBB
target_compile_definitions(app PRIVATE AAA BBB) 
# 带值的预定义宏
target_compile_definitions(app PRIVATE VERSION=123 NAME="string")

其中,关键字PRIVATE表示仅对app自身有效。

注意:add_definitions中的-D前缀不可省略,target_compile_definitions中通常可以省略-D前缀。

另外,也可以对一组或一个文件添加预定义宏
为一个文件添加预定义宏:

# 为aa.cpp文件添加宏LOG_TAG="aa"
set_source_files_properties(xx/xx/aa.cpp PROPERTIES COMPILE_DEFINITIONS LOG_TAG="aa")

为一组文件添加预定义宏:

# 将aa bb cc.cpp设置成一组为变量TEST_SRC_FILES
set(TEST_SRC_FILES
        xx/xx/aa.cpp
        xx/xx/bb.cpp
        xx/xx/cc.cpp
        )
# 为TEST_SRC_FILES设置宏,则aa,bb,cc都设置
set_source_files_properties($TEST_SRC_FILES PROPERTIES COMPILE_DEFINITIONS LOG_TAG="test")

包含路径(Include Path)

设置include path也有两个命令,分别用于全局的和某个target。

命令include_directories用于添加全局的包含路径,对所有target都有效。例如:

# 添加路径/opt/python3/include到include path
include_directories(/opt/python3/include) 

命令target_include_directories用于给某个target添加包含路径,例如:

# 为app添加包含路径/opt/python3/include到include path
target_include_directories(app PRIVATE /opt/python3/include)

注意:如果路径中包含空格则需要使用双引号包含路径。

关键字PRIVATE/PUBLIC/INTERFACE

在CMake中,一些针对target做设置的命令经常会用到PRIVATE/PUBLIC/INTERFACE关键字参数,在不同的命令中,它们表示的意思是一样的:

  • PUBLIC:表示相关的设置不仅作用于当前指定的target,而且会随着依赖关系进行传递
  • PRIVATE:表示相关的设置仅作用于当前指定的target,不会随依赖关系传递
  • INTERFACE:表示相关的设置不作用于当前指定的target,但反而会随依赖关系传递
    例如:
add_library(student SHARED student.cpp student.h)
add_executable(app main.cpp)
target_link_libraries(app student)
target_compile_definitions(student PRIVATE STU_MAKE_LIBRARY)

以上配置中,app依赖student,target_compile_definitions命令为student添加了一个宏定义STU_MAKE_LIBRARY,关键字PRIVATE表示这个宏定义仅对student有效。

如果将PRIVATE改为INTERFACE,则正好相反,宏定义仅对依赖student的target有效,也就是app,结果导致编译student库时编译选项中没有预定义STU_MAKE_LIBRARY,而编译app时有。如果将PRIVATE改为PUBLIC,则对student和app都有效。

推荐阅读

CMake教程(02):基础语法

以上是关于CMake构建类型和编译选项的主要内容,如果未能解决你的问题,请参考以下文章

CMake:取决于构建类型的默认标志

CMake - 构建没有首选 32 位标志的 C#

cmake的缓存变量可以用于cpp源码中吗

在 CMake 项目中动态更改编译器标志

在 CMake 中为多个配置混合构建标志

CMake 默认编译链接选项