使用 CMake 时处理编译器标志的正确方法

Correct way to handle compiler flags when using CMake

我正在努力寻找一种正确的方法来为所有目标传播正确的编译器标志。

假设有一个包含库和单元测试的项目。

ProjectFolder
|-WorkerLibFolder
  |-Worker.cpp
  |-Worker.hpp
  |-CMakeLists.txt  (2)
|-main.cpp
|-CMakeLists.txt (1)
|-TestsFolder
  |-UnitTests.cpp
  |-CMakeLists.txt  (3)

在CMakeLists.txt (1) 我想全局设置编译选项,因为我假设项目的所有库的优化级别和其他标志应该相同。 add_compile_options(-Wall -Werror -Wno-error=maybe-uninitialized)是用来实现的

我还使用 CMAKE_BUILD_TYPE 功能,它在 CMAKE_CXX_FLAGS_RELEASECMAKE_CXX_FLAGS_DEBUG 标志的帮助下自动设置所需的优化级别。

此外,这些变量之一被隐式传递给编译器的事实似乎很烦人,因为无论如何我必须提前设置这些变量以获得所需的优化标志 set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -DNDEBUG -DBOOST_DISABLE_ASSERTS") 只是因为 -O3 默认设置为发布配置。

无论如何,直到我想在编译测试环境(UniTests.cpp)时始终设置-O0的那一刻,一切都很好。 CMakeLists.txt (3) 生成 project_ut 可执行文件。我希望使用配置的优化级别(取自 CMAKE_BUILD_TYPE)编译 WorkerLib,但它自动意味着 CMAKE_CXX_FLAGS_RELEASE-Ofast 传播到 UnitTest.cpp

我想我可能在这里做了一些奇怪的事情,并且有更好的方法来处理问题。 这里的一个选择是在没有 CMAKE_BUILD_TYPE 功能帮助的情况下传递优化标志,但这似乎是错误的(因为我不想为每个目标维护标志列表)。

编辑:

#CMakeLists.txt(1):
cmake_minimum_required(VERSION 3.14.1)
project(TestProject LANGUAGES CXX C)

#####  common comp flags #####

add_compile_options(-Wall -Werror -Wno-error=maybe-uninitialized)

##############################

set(RELEASE_FLAGS "-Ofast -DNDEBUG -DBOOST_DISABLE_ASSERTS")
set(DEBUG_FLAGS "-O0 -ggdb3")

set(CMAKE_CXX_FLAGS_RELEASE ${RELEASE_FLAGS})
set(CMAKE_C_FLAGS_RELEASE ${RELEASE_FLAGS})

set(CMAKE_CXX_FLAGS_DEBUG ${DEBUG_FLAGS})
set(CMAKE_C_FLAGS_DEBUG ${DEBUG_FLAGS})

add_subdirectory(WorkerLibFolder)
add_subdirectory(TestsFolder)


add_executable(mainExec main.cpp)
target_link_libraries(mainExec PRIVATE worker)

#CMakeLists.txt(3):
add_executable(tests UnitTests.cpp)
target_link_libraries(tests PRIVATE worker)
#I want sommehow fix optimization flags for that particular target, while for worker library left them as they were set
#CMakeLists.txt(2):
add_library(worker Worker.cpp)
target_include_directories(worker PUBLIC ${CMAKE_CURRENT_LIST_DIR})

设置标志的正确方法是使用 set_compile_optionstarget_compile_options 以及使用 add_compile_definitionstarget_compile_definitions 的宏。您不应该(或很少)自己触摸 CMAKE_*_FLAGS,并且随着生成器表达式的创建,您也很少应该触摸 CMAKE_*_FLAGS_*。使用 $<CONFIG:RELEASE> 更简单,因为您不需要关心大小写(-DCMAKE_BUILD_TYPE=Release-DCMAKE_BUILD_TYPE=rElEaSe 都是发布版本)并且让我的眼睛更清晰地阅读。

在你的主CMakeLists.txt中做:

add_compile_options(
       -Wall -Werror -Wno-error=maybe-uninitialized
       $<$<CONFIG:RELEASE>:-Ofast>
       $<$<CONFIG:DEBUG>:-O0>
       $<$<CONFIG:DEBUG>:-ggdb3>
)
add_compile_definitions(
        $<$<CONFIG:RELEASE>:NDEBUG>
        $<$<CONFIG:RELEASE>:BOOST_DISABLE_ASSERTS>
)
add_subdirectory(WorkerLibFolder)
add_subdirectory(TestsFolder)
add_executable(main ...)

I want to alway have -O0 being set when compiling test environment

那就照做吧。在 TestsFolder 内做:

add_compile_options(-O0)
add_library(test_environment  ....)

或更好:

add_library(test_environment ..
target_compile_options(test_environment PRIVATE -O0)

因为编译选项是累积的,所以在 TestsFolder 中添加的选项将是 suffixed/the 编译行上的最后一个选项,所以它会起作用,我的意思是 ex。 gcc -O3 -Ofast -O0 将与 gcc -O0.

编译相同

target_include_directories(worker PUBLIC ${CMAKE_CURRENT_LIST_DIR})

CMAKE_CURRENT_LIST_DIR 通常用于 include(this_files) 文件,以指示文件所在的位置。要包含当前目录,CMAKE_CURRENT_SOURCE_DIR 更常用,这表示 cmake 正在处理的当前源目录。

PS:我不会使用不同的优化选项对 program/library 进行单元测试,然后再构建版本。我使用与发布版本完全相同的编译选项进行单元测试。一些错误仅在启用优化时出现。我最好对(以前的)库进行单元测试的方法是在禁用和启用优化的情况下进行编译和单元测试。

有几个最近的最佳实践 (here, here and here) 建议您 CMakeLists.txt 应该干净灵活。

我建议将这些标志移到外部(即不放在您的 CMakeLists.txt 中),方法是将它们保留在相关的 bash 脚本中,例如

C_FLAGS=""
C_FLAGS="${C_FLAGS} -Wall"
C_FLAGS="${C_FLAGS} -g -gline-tables-only"

LINKER_FLAGS=""

BUILD_TYPE=Debug

#

cmake \
  -GNinja \
  -DCMAKE_C_COMPILER=clang \
  -DCMAKE_CXX_COMPILER=clang++ \
  -DCMAKE_POLICY_DEFAULT_CMP0056=NEW \
  -DCMAKE_EXPORT_COMPILE_COMMANDS=On \
  -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
  -DCMAKE_C_FLAGS="${C_FLAGS}" \
  -DCMAKE_EXE_LINKER_FLAGS="${LINKER_FLAGS}" \
  -DCMAKE_SHARED_LINKER_FLAGS="${LINKER_FLAGS}" \
  -DCMAKE_MODULE_LINKER_FLAGS="${LINKER_FLAGS}" \
  "${SRC_DIR}"

这样您就可以为您尝试做的事情设置标志集。

下一步是允许生成器表达式并为每个目标配置每个构建类型的特定选项,但如果没有看到您的 CMakeLists.txt 中的示例,很难提出建议。

您甚至可以拥有特定于项目的 CMake 变量,这些变量可以针对每个目标进行影响和使用,但这对于您的项目来说可能太复杂了。