CMake工具链文件 - 设置CMAKE_CXX_FLAGS

6
我看到在工具链文件中设置CMAKE_CXX_FLAGS的方法如下:
SET(CMAKE_CXX_FLAGS "-m32" CACHE STRING "C++ compiler flags" FORCE)

我应该在工具链文件中使用它,而不是

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")

它们之间有什么区别?


如果你是那种更喜欢 CMakeLists.txt 只包含编译项目所需最少信息的人,另一个值得考虑的选项是通过预设文件来设置它们。 - Mark G
第二种方式 - 不使用CACHE - 是否在工具链内起作用?根据那个问题,它不会。 - Tsyvarev
第二种方法可行。已使用cmake 3.23.2进行测试。构建的二进制文件已通过file命令进行验证。 - Irbis
第二种方式通常是不起作用的。实际上,它非常糟糕。有关更多详细信息,请参阅我的答案。 - Alex Reinking
2个回答

6
简述:有两种可接受的方法来完成这个任务。
首先,大多数情况下(90%以上),您可以使用文档建议的_INIT变量,如所示
set(CMAKE_CXX_FLAGS_INIT "-m32")

其次,如果CMake为您的编译器/平台组合添加了不正确/冲突的标志,您可以通过设置缓存变量而完全覆盖它,无需FORCE
set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "C++ compiler flags")

阅读以下详细信息。
让我们进行几个实验。我们将使用以下的CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.23)
project(test LANGUAGES CXX)

message(STATUS "CMAKE_CXX_FLAGS_DEBUG = ${CMAKE_CXX_FLAGS_DEBUG}")

在大多数系统上,默认情况下,CMake会将CMAKE_CXX_FLAGS留空。主要的例外是Windows与MSVC,在这种情况下,它会添加/EHsc和(在旧版本中)/GR以确保启用标准C++异常处理和RTTI。
由于我没有方便地访问Windows系统,所以我使用CMAKE_CXX_FLAGS_DEBUG,它在大多数编译器上具有默认初始化标志。不过,相同的原则适用于两种情况,因为平台模块负责设置这些标志。
$ cmake -S . -B build
-- The CXX compiler identification is GNU 11.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- CMAKE_CXX_FLAGS_DEBUG = -g
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/build

在这个编译器上,CMAKE_CXX_FLAGS_DEBUG被设置为-g。这是我们的基线。
实验2:使用force设置缓存
现在我们将创建一个名为set-cache-force.cmake的工具链文件:
# set-cache-force.cmake
set(CMAKE_CXX_FLAGS_DEBUG "-DMY_DEBUG" CACHE STRING "C++ compiler flags" FORCE)

我们将使用这个工具链来配置项目:
$ rm -rf build
$ cmake -S . -B build --toolchain set-cache-force.cmake 
...
-- CMAKE_CXX_FLAGS_DEBUG = -DMY_DEBUG
...

我们可以看到,原始的-g标志被抑制了,而-DMY_DEBUG缓存值“获胜”了。当然,这不再是一个调试模式,这说明为什么覆盖所有标志并不总是我们想要的。
更糟糕的是,在这里使用FORCE会禁用用户自己覆盖CMAKE_CXX_FLAGS_DEBUG的能力:

$ rm -rf build
$ cmake -S . -B build --toolchain set-cache-force.cmake -DCMAKE_CXX_FLAGS_DEBUG="-DOVERRIDE"
...
-- CMAKE_CXX_FLAGS_DEBUG = -DMY_DEBUG
...

这是极其不良的行为。用户需要编辑您的工具链文件以解决错误或添加其他自定义设置。
实验3:未使用FORCE设置的Set-cache 如果我们运行与之前相同的实验,但不设置FORCE,那么我们仍然会得到相同的标志,但保留了逐步覆盖工具链文件的能力。
# set-cache.cmake
set(CMAKE_CXX_FLAGS_DEBUG "-DMY_DEBUG" CACHE STRING "C++ compiler flags")

现在我们可以看到它起作用了:
$ rm -rf build
$ cmake -S . -B build --toolchain set-cache.cmake
...
-- CMAKE_CXX_FLAGS_DEBUG = -DMY_DEBUG
...

这意味着它仍然可以被覆盖:
$ rm -rf build
$ cmake -S . -B build --toolchain set-cache.cmake -DCMAKE_CXX_FLAGS_DEBUG="-DOVERRIDE"
...
-- CMAKE_CXX_FLAGS_DEBUG = -DOVERRIDE
...

它甚至可以再次被覆盖:

$ cmake -S . -B build -DCMAKE_CXX_FLAGS_DEBUG="-DOVERRIDE2"
...
-- CMAKE_CXX_FLAGS_DEBUG = -DOVERRIDE2
...

实验4:设置普通变量
现在我们将尝试将其设置为普通变量。同样,我们将创建一个名为set-normal.cmake的工具链文件:
# set-normal.cmake
set(CMAKE_CXX_FLAGS_DEBUG "-DMY_DEBUG")

再次运行此命令会显示,-DMY_DEBUG "获胜",覆盖了CMake的默认标志:
$ cmake -S . -B build --toolchain set-normal.cmake
...
-- CMAKE_CXX_FLAGS_DEBUG = -DMY_DEBUG
...

与实验2类似,这会防止用户覆盖它...很糟糕!
$ cmake -S . -B build --toolchain set-normal.cmake -DCMAKE_CXX_FLAGS_DEBUG="-DOVERRIDE"
...
-- CMAKE_CXX_FLAGS_DEBUG = -DMY_DEBUG
...

实验5:追加普通变量
现在我们将尝试使用您发布的代码。同样,我们将使用一个名为append-normal.cmake的工具链:
# append-normal.cmake
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DMY_DEBUG")

现在我们得到了一个非常不同的结果:
$ rm -rf build
$ cmake -S . -B build --toolchain append-normal.cmake
...
-- CMAKE_CXX_FLAGS_DEBUG =  -DMY_DEBUG -DMY_DEBUG
...

这完全是错误的!发生了什么?在项目初始化期间,工具链文件会被多次读取,这导致-DMY_DEBUG标志被重复附加。至少这是在第一次运行时发生的:
$ cmake -S . -B build
...
-- CMAKE_CXX_FLAGS_DEBUG = -g -DMY_DEBUG
...

第一次运行后,CMake默认被缓存,因此我们在后续运行中附加到该缓存中。此外,现在CMake只读取您的工具链文件一次。 您必须始终使您的工具链文件幂等。这意味着将其运行两次与将其运行一次是相同的。
实验6:使用_INIT变量 根据文档,这是开发者预期的做法。请查看此处的文档:https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS_INIT.html 引用:
用于初始化第一次为语言配置构建树时的CMAKE__FLAGS缓存条目的值。该变量应由工具链文件设置。基于环境和目标平台,CMake可能会在值之前或之后添加内容。
现在我们使用一个名为init-var.cmake的工具链文件:
# init-var.cmake
set(CMAKE_CXX_FLAGS_DEBUG_INIT "-DMY_DEBUG")

我们重新运行构建:
$ rm -rf build
$ cmake -S . -B build --toolchain init-var.cmake
...
-- CMAKE_CXX_FLAGS_DEBUG = -DMY_DEBUG -g
...

现在我们可以看到,CMake将其默认标志附加到我们提供的初始标志中。确实,这仍然允许用户覆盖一些东西。
$ cmake -S . -B build --toolchain init-var.cmake -DCMAKE_CXX_FLAGS_DEBUG="-DOVERRIDE"
...
-- CMAKE_CXX_FLAGS_DEBUG = -DOVERRIDE
...

根据我的经验,90%以上的情况下,使用实验6(_INIT变量)让CMake添加其额外的标志是正确的。但是偶尔你会想要完全覆盖CMake,使用实验3(set(CACHE)而不使用FORCE)。

你不想做的事情是任何在后续运行中表现不同的操作(如实验5),或者禁用关键的CMake功能(即尊重缓存变量,如实验2和4)。

-1

当您使用set(variable "value" CACHE STRING "..." FORCE)时,该变量将被设置为当前会话中构建的所有项目(包括子目录中的项目)。

但是,如果仅使用set(variable "value")而不是缓存部分,则只向立即项目范围(当前的CMakeLists.txt)添加标志,而不添加其拥有自己的CMakeLists.txt的上级目录。


这个答案的后半部分完全是错误的。 - Alex Reinking
你的注释已经过时了。 - Amin Ya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接