这个特殊的解决方案(?)适用于目标和单个翻译单元(单个.cpp-文件)。它将需要CMake 3.7 或更新版本,因为我们需要使用BUILDSYSTEM_TARGETS 属性迭代所有现有目标,该属性是在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。对于main.cpp,无论出于何种原因,我们都想删除-O2。
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。
其中大部分应该很容易修复。
我希望这至少会推动您朝着正确的方向解决这个问题。如果我需要在答案中添加一些内容,请告诉我。