使用CMake-移除单个编译单元的编译标志

51

我想移除一个单独的翻译单元的编译标志。有没有办法做到这一点?(例如使用set_property?)

注意:编译标志没有-fno-name否定(出于某种原因)。

我已经尝试过:

get_property(FLAGS TARGET target PROPERTY COMPILE_FLAGS)
string(REPLACE "-fname" "" FLAGS ${FLAGS})
set_property(TARGET target PROPERTY COMPILE_FLAGS ${FLAGS})

没有任何运气。我想要删除的属性是CMAKE_CXX_FLAGS的一部分,因此这不起作用。


你是否在自己的 CMakeLists.txt 中将 -fname 标志添加到 CMAKE_CXX_FLAGS 中? - Florian
这个是在 CMakeFiles.txt 中显式添加到 CMAKE_CXX_FLAGS 中的,还是其中一个默认组件? - John
注意:一旦发生https://gitlab.kitware.com/cmake/cmake/-/issues/21986,您可以定义编译选项,以便对特定源文件不起作用。 - starball
3个回答

43

这个解决方案既适用于目标文件,也适用于单个翻译单元(一个单独的.cpp文件)。它需要使用BUILDSYSTEM_TARGETS属性遍历所有现有目标,因此需要CMake 3.7或更新版本。如果您无法使用3.7或更新版本,则可以调整apply_global_cxx_flags_to_all_targets()以获取目标列表并手动指定它们。请注意,您应该将下面的宏视为概念验证。对于任何但非常小的项目,它们可能需要进行一些微调和改进。

第一步是遍历所有现有目标,并将CMAKE_CXX_FLAGS应用于每个目标。完成后,我们清除CMAKE_CXX_FLAGS。下面是一个执行此操作的宏。当所有目标已创建、所有标志已设置等时,应该在顶级CMakeLists.txt的底部某处调用此宏。重要的是要注意,命令get_property(_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)在目录级别上工作,因此如果您使用add_subdirectory(),则必须在该子目录的顶级CMakeLists.txt中调用此宏。

#
# Applies CMAKE_CXX_FLAGS to all targets in the current CMake directory.
# After this operation, CMAKE_CXX_FLAGS is cleared.
#
macro(apply_global_cxx_flags_to_all_targets)
    separate_arguments(_global_cxx_flags_list UNIX_COMMAND ${CMAKE_CXX_FLAGS})
    get_property(_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
    foreach(_target ${_targets})
        target_compile_options(${_target} PUBLIC ${_global_cxx_flags_list})
    endforeach()
    unset(CMAKE_CXX_FLAGS)
    set(_flag_sync_required TRUE)
endmacro()

这个宏首先从CMAKE_CXX_FLAGS创建一个列表,然后获取所有目标的列表,并将CMAKE_CXX_FLAGS应用于每个目标。最后,清除CMAKE_CXX_FLAGS。使用_flag_sync_required来指示是否需要强制重写缓存变量。
下一步取决于您是想从目标中删除标志还是从特定的翻译单元中删除标志。如果您想从目标中删除标志,可以使用类似以下的宏:
#
# Removes the specified compile flag from the specified target.
#   _target     - The target to remove the compile flag from
#   _flag       - The compile flag to remove
#
# Pre: apply_global_cxx_flags_to_all_targets() must be invoked.
#
macro(remove_flag_from_target _target _flag)
    get_target_property(_target_cxx_flags ${_target} COMPILE_OPTIONS)
    if(_target_cxx_flags)
        list(REMOVE_ITEM _target_cxx_flags ${_flag})
        set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "${_target_cxx_flags}")
    endif()
endmacro()

从特定翻译单元中删除标记有点棘手(除非我漏掉了什么)。无论如何,首先要从该文件所属的目标获取编译选项,然后将这些选项应用于该目标中的所有源文件,以允许我们操纵单个文件的编译标志。为此,我们通过维护每个要从中删除标志的文件的缓存编译标志列表来执行此操作,并且在请求删除时,我们会从缓存列表中移除它,然后重新应用其余标志。目标本身的编译选项被清除。
#
# Removes the specified compiler flag from the specified file.
#   _target     - The target that _file belongs to
#   _file       - The file to remove the compiler flag from
#   _flag       - The compiler flag to remove.
#
# Pre: apply_global_cxx_flags_to_all_targets() must be invoked.
#
macro(remove_flag_from_file _target _file _flag)
    get_target_property(_target_sources ${_target} SOURCES)
    # Check if a sync is required, in which case we'll force a rewrite of the cache variables.
    if(_flag_sync_required)
        unset(_cached_${_target}_cxx_flags CACHE)
        unset(_cached_${_target}_${_file}_cxx_flags CACHE)
    endif()
    get_target_property(_${_target}_cxx_flags ${_target} COMPILE_OPTIONS)
    # On first entry, cache the target compile flags and apply them to each source file
    # in the target.
    if(NOT _cached_${_target}_cxx_flags)
        # Obtain and cache the target compiler options, then clear them.
        get_target_property(_target_cxx_flags ${_target} COMPILE_OPTIONS)
        set(_cached_${_target}_cxx_flags "${_target_cxx_flags}" CACHE INTERNAL "")
        set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "")
        # Apply the target compile flags to each source file.
        foreach(_source_file ${_target_sources})
            # Check for pre-existing flags set by set_source_files_properties().
            get_source_file_property(_source_file_cxx_flags ${_source_file} COMPILE_FLAGS)
            if(_source_file_cxx_flags)
                separate_arguments(_source_file_cxx_flags UNIX_COMMAND ${_source_file_cxx_flags})
                list(APPEND _source_file_cxx_flags "${_target_cxx_flags}")
            else()
                set(_source_file_cxx_flags "${_target_cxx_flags}")
            endif()
            # Apply the compile flags to the current source file.
            string(REPLACE ";" " " _source_file_cxx_flags_string "${_source_file_cxx_flags}")
            set_source_files_properties(${_source_file} PROPERTIES COMPILE_FLAGS "${_source_file_cxx_flags_string}")
        endforeach()
    endif()
    list(FIND _target_sources ${_file} _file_found_at)
    if(_file_found_at GREATER -1)
        if(NOT _cached_${_target}_${_file}_cxx_flags)
            # Cache the compile flags for the specified file.
            # This is the list that we'll be removing flags from.
            get_source_file_property(_source_file_cxx_flags ${_file} COMPILE_FLAGS)
            separate_arguments(_source_file_cxx_flags UNIX_COMMAND ${_source_file_cxx_flags})
            set(_cached_${_target}_${_file}_cxx_flags ${_source_file_cxx_flags} CACHE INTERNAL "")
        endif()
        # Remove the specified flag, then re-apply the rest.
        list(REMOVE_ITEM _cached_${_target}_${_file}_cxx_flags ${_flag})
        string(REPLACE ";" " " _cached_${_target}_${_file}_cxx_flags_string "${_cached_${_target}_${_file}_cxx_flags}")
        set_source_files_properties(${_file} PROPERTIES COMPILE_FLAGS "${_cached_${_target}_${_file}_cxx_flags_string}")
    endif()
endmacro()

示例

我们有这样一个非常简单的项目结构:

source/
    CMakeLists.txt
    foo.cpp
    bar.cpp
    main.cpp

假设 foo.cpp 违反了 -Wunused-variable 规则,我们希望在这个特定文件中禁用该标志。对于 bar.cpp,我们希望禁用 -Werror。出于某种原因,我们想要移除 -O2,是针对 main.cpp 的。

CMakeLists.txt

cmake_minimum_required(VERSION 3.7 FATAL_ERROR)
project(MyProject)

# Macros omitted to save space.

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wunused-variable")
# CMake will apply this to all targets, so this will work too.
add_compile_options("-Werror")

add_executable(MyTarget foo.cpp bar.cpp main.cpp)

apply_global_cxx_flags_to_all_targets()
remove_flag_from_file(MyTarget foo.cpp -Wunused-variable)
remove_flag_from_file(MyTarget bar.cpp -Werror)
remove_flag_from_file(MyTarget main.cpp -O2)

如果您想从目标中删除一个标志,您可以使用remove_flag_from_target(),例如:remove_flag_from_target(MyTarget -O2)

结果

应用宏之前的输出

clang++  -Werror -O2 -Wunused-variable -o foo.cpp.o -c foo.cpp
clang++  -Werror -O2 -Wunused-variable -o bar.cpp.o -c bar.cpp
clang++  -Werror -O2 -Wunused-variable -o main.cpp.o -c main.cpp

应用宏后的输出结果

clang++  -Werror -O2  -o foo.cpp.o -c foo.cpp
clang++  -O2 -Wunused-variable  -o bar.cpp.o -c bar.cpp
clang++  -Werror -Wunused-variable -o main.cpp.o -c main.cpp

最终备注

如前所述,这应该被视为一个概念验证。需要考虑以下几个问题:

  • 它只考虑了CMAKE_CXX_FLAGS(对所有构建类型通用),而没有考虑CMAKE_CXX_FLAGS_<DEBUG|RELEASE>等。
  • 这些宏一次只能处理一个标志。
  • remove_flag_from_file()一次只能处理一个目标和输入文件。
  • remove_flag_from_file()期望一个文件名,而不是一个路径。例如传递source/foobar/foobar.cpp将不起作用,因为source/foobar/foobar.cpp将与foobar.cpp进行比较。可以使用get_source_file_property()LOCATION属性来修复此问题,并放置一个前提条件,即_file是完整路径。
    • 如果一个目标有两个或更多相同名称的文件会发生什么?
  • remove_flag_from_file()可能可以进行优化和改进。
  • separate_arguments()的调用假定Unix。

这些问题中大部分应该很容易解决。

我希望这至少能引导你在解决这个问题上朝着正确的方向前进。如果需要添加一些答案,请告诉我。


separate_arguments 函数中,为什么要使用 UNIX_COMMAND 而不是 NATIVE_COMMAND - Dino Saric
1
@DinoSaric 当时我使用的是 3.8 版本,NATIVE_COMMAND 是在 3.9 版本中引入的。我计划对这个答案进行精细化处理,届时我会切换到 NATIVE_COMMAND - thomas_f

13
如果没有编译选项的否定选项(这将是CMake处理非IDE配置器的标准方式),则必须从CMAKE_CXX_FLAGS中删除此编译选项。
可能的方法:
1. 将COMPILE_FLAGS作为源文件属性,从具有相同名称的目录属性中继承。现在你可以单独修改每个目录和源文件的COMPILE_FLAGS属性:
# Remove '-fname' from global flags
string(REPLACE "-fname" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")

define_property(
    SOURCE
    PROPERTY COMPILE_FLAGS 
    INHERITED 
    BRIEF_DOCS "brief-doc"
    FULL_DOCS  "full-doc"
)

# Add '-fname' for directory
set_directory_properties(PROPERTIES COMPILE_FLAGS "-fname")

add_executable(${PROJECT_NAME} "main.cpp")

# Remove '-fname' from source file
set_source_files_properties("main.cpp" PROPERTIES COMPILE_FLAGS "")
  • 如果某个属性(例如COMPILE_OPTIONS)仅在目标级别上可用,您必须将希望具有不同设置的文件移动到单独的新目标中。

    这可以通过使用对象库来完成:

  • # Remove '-fname' from global flags
    string(REPLACE "-fname" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
    
    # Set '-fname' for directory, but if you also use add_compile_options() be aware that
    # this will overwrite your previous setting. Append/remove properties instead.
    set_directory_properties(
        PROPERTIES 
            COMPILE_OPTIONS "-fname"
    )
    
    # Remove '-fname' from source file(s) in separate object library target
    add_library(${PROJECT_NAME}_no_name OBJECT "main.cpp")
    set_target_properties(
        ${PROJECT_NAME}_no_name
        PROPERTIES 
            COMPILE_OPTIONS ""
    )
    
    # Use object library target in e.g. executable target
    add_executable(${PROJECT_NAME} $<TARGET_OBJECTS:${PROJECT_NAME}_no_name>)
    

    参考文献


    5

    一种(不可扩展且不可移植的)解决方案是创建一个带有修改标志的自定义命令。我成功实现的最简单示例如下:

    cmake_minimum_required(VERSION 2.8)
    
    project(test)
    # Some flags
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wall")
    # Remove unwanted flag
    string(REPLACE "-Wall" "" CUSTOM_FLAGS ${CMAKE_CXX_FLAGS})
    # Split the flags into a cmake list (; separated)
    separate_arguments(CUSTOM_FLAGS UNIX_COMMAND ${CUSTOM_FLAGS})
    # Custom command to build your one file.
    add_custom_command(
        OUTPUT out.o
        COMMAND ${CMAKE_CXX_COMPILER} 
        ARGS ${CUSTOM_FLAGS} -c ${CMAKE_SOURCE_DIR}/out.cpp 
                             -o ${CMAKE_BINARY_DIR}/out.o
        MAIN_DEPENDENCY out.cpp)
    
    add_executable(test test.cpp out.o)
    

    虽然还不够完美,但它能正常运行才是最重要的。必须使用separate_arguments,否则CUSTOM_FLAGS中的所有空格都会被转义(不知道如何快速解决这个问题)。

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