CMake - remove a compile flag for a single translation unit
I would like to remove a set compile flag for a single translation unit. Is there a way to do this? (e.g. using set_property
?)
Note: the compile-flag has no -fno-name
negation (for whatever reason).
I've tried:
get_property(FLAGS TARGET target PROPERTY COMPILE_FLAGS)
string(REPLACE "-fname" "" FLAGS ${FLAGS})
set_property(TARGET target PROPERTY COMPILE_FLAGS ${FLAGS})
without any luck. The property I want to remove is part of CMAKE_CXX_FLAGS
and thus this does not work.
Solution 1:
This particular solution(?) will work both for targets and for single translation units (a single .cpp
-file). It will require CMake 3.7
or newer, since we need to iterate through all existing targets using the BUILDSYSTEM_TARGETS
property, which was added in 3.7
. If you are unable to use 3.7
or newer, you can adapt apply_global_cxx_flags_to_all_targets()
to take a list of targets and specify them manually. Note that you should consider the macros below as proof-of-concept. They will probably need some tweaking and improvement for any but very small projects.
The first step is to iterate through all existing targets and apply CMAKE_CXX_FLAGS
to each of them. When this is done, we clear CMAKE_CXX_FLAGS
. Below you'll find a macro that will do this. This macro should probably be called somewhere at the bottom of your top-level CMakeLists.txt
, when all targets have been created, all flags have been set etc. It's important to note that the command get_property(_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
works at directory level, so if you use add_subdirectory()
, you must call this macro in the top-level CMakeLists.txt
of that sub-directory.
#
# 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()
This macro first creates a list from CMAKE_CXX_FLAGS
, then it gets a list of all targets and applies CMAKE_CXX_FLAGS
to each of the targets. Finally, CMAKE_CXX_FLAGS
is cleared. _flag_sync_required
is used to indicate if we need to force a rewrite of cached variables.
The next step depends if you want to remove a flag from a target or from a particular translation unit. If you want to remove a flag from a target, you can use a macro similar to this:
#
# 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()
Removing a flag from a particular translation unit is a bit trickier (unless I have greatly missed something). Anyway, the idea is to first obtain the compile options from the target to which the file belongs, and then applying said options to all source files in that target, which allows us to manipulate the compile flags for individual files. We do this by maintaining a cached list of compile flags for each file we want to remove flags from, and when a remove is requested, we remove it from the cached list and then re-apply the remaining flags. The compile options for the target itself is cleared.
#
# 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()
Example
We have this very simple project structure:
source/
CMakeLists.txt
foo.cpp
bar.cpp
main.cpp
Let's assume that foo.cpp
violates -Wunused-variable
, and we want to disable that flag on that particular file. For bar.cpp
, we want to disable -Werror
. For main.cpp
, we want to remove -O2
, for whatever reason.
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)
If you instead want to remove a flag from a target, you would use remove_flag_from_target()
, for example: remove_flag_from_target(MyTarget -O2)
Result
Output before applying macros
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
Output after applying macros
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
Final notes
As mentioned, this should be seen as a proof-of-concept. There a few immediate issues to consider:
- It only considers
CMAKE_CXX_FLAGS
(common to all build types), notCMAKE_CXX_FLAGS_<DEBUG|RELEASE>
etc. - The macros can only handle one flag at a time
-
remove_flag_from_file()
can only handle one target and input file as input at a time. -
remove_flag_from_file()
expects a filename, not a path. Passing, say,source/foobar/foobar.cpp
will not work, sincesource/foobar/foobar.cpp
will be compared againstfoobar.cpp
. This can be fixed by usingget_source_file_property()
and theLOCATION
property and placing a precondition that_file
is a full path.- What happens if a target have two or more files with the same name?
-
remove_flag_from_file()
can probably be optimized and improved greatly. - The call to
separate_arguments()
assumes Unix.
Most of these should be fairly easy to fix.
I hope that this will at least nudge you in the right direction in solving this problem. Let me know if I need to add something to the answer.
Solution 2:
If there is no compile flag negation option (which would be the standard way of CMake handling this for non-IDE configurators), you have to remove this compile flag from CMAKE_CXX_FLAGS
.
Possible Approaches
-
Make
COMPILE_FLAGS
a source file property which isINHERITED
from a directory property with the same name. Now you can modifyCOMPILE_FLAGS
property of each directory and source file individually:# 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 "")
-
If some property - like
COMPILE_OPTIONS
- is only available on target level, you have to move the files you want to have different settings for into a separate new target itself.This can be done by using OBJECT libraries:
# 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>)
References
- CMake per file optimizations
- Override compile flags for single files
- clang-tidy cmake exclude file from check