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 给编译器