CMake 入门/加入编译选项
显示更多建置讯息
编辑在预设的情况下,CMake 生成的 makefile 只会显示编译的进度,并不会把各步骤实际调用的命令、参数一一列出,但在很多时候我们需要确知编译时倒底使用了哪些编译选项。其中一个方法是直接在 CMakeList.txt 当中加入这一行
set(CMAKE_VERBOSE_MAKEFILE ON)
不过若是我们不希望更动 CMakeList.txt,可以等到执行时再加入选项
$ cmake -DCMAKE_VERBOSE_MAKEFILE=ON ...options... $ make 或 $ cmake ...options... $ make VERBOSE=1
预设的编译组态
编辑透过指定 CMAKE_BUILD_TYPE 变数即可改变编译组态,预设的 CMAKE_BUILD_TYPE 是 None,即不加上任何额外编译选项。内建的 CMAKE_BUILD_TYPE 支援以下几种组态:
- None : 编译器预设值
- Debug : 产生除错资讯
- Release : 进行执行速度最佳化
- RelWithDebInfo : 进行执行速度最佳化,但仍然会启用 debug flag
- MinSizeRel : 进行程式码最小化
虽然 CMAKE_BUILD_TYPE 可以由 CMakeList.txt 设定,一般来说我们更偏好到了执行 CMake 时才由命令列指定。
以前一章的例子来说明:
- ex2/
- build/
- src/
- CMakeLists.txt
- calc.c
- calc.h
- main.c
假设我们现在工作目录是 ex2/build,我们可以执行以下的指令产生 Release 组态的执行档
$ mkdir release $ cd release $ cmake -DCMAKE_BUILD_TYPE=Release ../../src
接下来执行 “make VERBOSE=1 ”,就可以看到每个编译单元都带有“-O3 -DNDEBUG”选项。对 Debug 也可比照办理。
控制编译选项的变数和指令
编辑加入编译选项
编辑变数 CMAKE_C_FLAGS 存放的内容会被传给 C 编译器,作用在所有的编译组态上。如果希望只针对特定一种组态有效,可以设定 CMAKE_C_FLAGS_<编译组态>,例如 CMAKE_C_FLAGS_RELEASE、CMAKE_C_FLAGS_DEBUG。
举个具体的例子,在 Visual C++ 底下编译含有 sprintf 的原始码时,常常收到 C4996 警告,除了可以在原始码中使用 #pragma 关闭,也可以透过编译选项搞定。在 CMakeLists.txt 设定编译选项的方式如下:
if(MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4996") endif()
在加入编译选项时,记得要先判断编译器的种类,因为每一种编译器的参数格式都不一样。在这里 MSVC 是一个 BOOL 值,当指定 Generator 为 Visual C++ 时 MSVC 的值为 TRUE,否则为 FALSE。
针对 C++ 编译器也有对应的变数 CMAKE_CXX_FLAGS 和 CMAKE_CXX_FLAGS_<编译组态>。
加入连结选项
编辑CMAKE_EXE_LINKER_FLAGS 变数决定了连结执行档时传给编译器的选项,同样也有各种不同编译组态的变形。
举个具体的例子,新版的 MinGW (GCC 4.5)预设动态连结基础程式库,然而在编译时却会因为没有启用 auto-import,使得编译器发出警告,有时连结出来的执行档有误。其中一种解决方法是在编译时加入“-Wl,--enable-auto- import”,另外我们也可以采用静态连结:
if(MINGW) set(CMAKE_EXE_LINKER_FLAGS "-static") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-s") endif()
上面使得 MinGW 各种组态都采用 -static 来连结可执行档,并且只有在 Release 情况下另外加入 -s 连结选项,亦即 Release 的连结选项为 -static -s。
加入 Preprocessor 定义
编辑指令 add_definitions 可以用来加入 Preprocessor 定义,作用范围为所在资料夹下的编译单元。格式如下
add_definitions("-DFOO -DBAR")
这个指令原本只是单纯用来加入 Preprocessor 定义,但事实上差不多所有编译选项都可以透过 add_definitions 传给编译器。
加入编译器搜寻路径
编辑以下两个指令分别用来加入编译器寻找标头档、程式库的路径,在 gcc 相当于 -I 和 -L 选项。
include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...) link_directories(dir1 dir2 ...)
include_directories(dir) 预设会将 dir 加入目前引入路径列表的后面,我们可以利用 AFTER 和 BEFORE 调整 dir 应该被加到列表的前端还是后端
使用属性控制编译选项
编辑以上介绍的变数和指令都是属于全域范围的设定,如果希望选项仅对一个 target 有效,就必须从 target 的属性(properties)下手。
在 CMake 里一个 target 最终的输出档名、编译连结选项等等都是由属性控制,若没有手工设定这些属性,则 CMake 会由相关的全域变数来决定属性值。我们可以利用 set_target_properties() 指令单独指定一个 target 的属性,如此一来就可以盖过全域变数的设定。
set_target_properties() 指令格式如下
set_target_properties(target1 target2 ... PROPERTIES 屬性名稱1 值 屬性名稱2 值 ... )
控制编译选项的属性是 COMPILE_FLAGS,连结则由 LINK_FLAGS 和 LINK_FLAGS_<编译组态> 属性控制。另外我们也可以透过 COMPILE_DEFINITIONS 和 COMPILE_DEFINITIONS_<编译组态> 加入 preprocessor 定义,如同 add_definitions() 指令一样,这两个属性实际上也可以用来加入几乎所有的编译选项。
举例来说
set_target_properties(app PROPERTIES LINK_FLAGS -static LINK_FLAGS_RELEASE -s )
会使 app 这个程式在所有情况下都采用 -static 选项,并且在 release build 时采用 -static -s 选项。这些属性会盖过原先全域变数的设定,而且仅作用在 app 这个 target 上。
在编译期指定环境变数
编辑除了透过变数和属性之外,还可以等到编译期才给定环境变数,如
$ export CXXFLAGS=-O2 $ cmake . $ make
注意事项
编辑小心覆写变数
编辑像 CMAKE_C_FLAGS_RELEASE、CMAKE_C_FLAGS_DEBUG 这些变数,CMake 会自动依照 Generator 指定的编译器初始化。例如在 gcc 下 CMAKE_C_FLAGS_RELEASE 预设的值为 -O3 -DNDEBUG,而 CMAKE_C_FLAGS_DEBUG 预设值为 -g。
小心下面这种写法:
set(CMAKE_C_FLAGS_RELEASE "-fno-inline")
原意可能是希望在做最佳化时忽略 inline 关键字,但是这样一来其实会把原本的 -O3 -DNDEBUG 给覆写掉,所以程式并不会做任何最佳化。
比较好的写法应该是:
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fno-inline")
或者
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG -fno-inline")
注意字串和串列的不同
编辑串接变数的时候,请注意字串和串列串接的不同。
set(CMAKE_EXE_LINKER_FLAGS "-static") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s") # ... (1) set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-s") # ... (2)
(1) 会传递 -static -s 给编译器 (2) 会传递 -static;-s 给编译器