How to set compile flags for external INTERFACE_SOURCES in CMake? - cmake

I use an external package in cmake, that uses INTERFACE_SOURCES. This means when I link the imported library to my target, the interface source files are automatically added to my target. When I compile my target, those external files are compiled too.
This causes a problem for me, because the external files cause compile warnings. I want to remove the warnings by setting a lower warning level when compiling the external files. But I do not know how to do this.
This is what I got so far.
# reduce the warning level for some files over which we have no control.
macro( remove_warning_flags_for_some_external_files myTarget )
# blacklist of files that throw warnings
set( blackListedExternalFiles
static_qt_plugins.cpp
)
get_target_property( linkedLibraries ${myTarget} LINK_LIBRARIES )
foreach(library ${linkedLibraries})
get_property( sources TARGET ${library} PROPERTY INTERFACE_SOURCES )
foreach(source ${sources})
get_filename_component(shortName ${source} NAME)
if( ${shortName} IN_LIST blackListedExternalFiles)
# everything works until here
# does not work
get_source_file_property( flags1 ${source} COMPILE_FLAGS)
# does not work
get_property(flags2 SOURCE ${source} PROPERTY COMPILE_FLAGS)
# exchange flags in list, this I can do
# set flags to source file, do not know how to
endif()
endforeach()
endforeach()
endmacro()
This is what this should do
Go through all linked libraries and get the external INTERFACE_SOURCES source files.
Check for each external source file if it appears in the black-list. If so, change its compile flags to a lower level.
The problem I have is with getting and setting the compile flags for those INTERFACE_SOURCES. The get_source_file_property() and get_property() calls return nothing.
How can I get and set the flags for these files that seem to not belong to my target, but are compiled at the same time?

The get_source_file_property() and get_property() calls return nothing
COMPILE_FLAGS property of source file is not modified by "target" commands like target_compile_options. Final set of flags is a mix of global + target specific + source specific. As far as I understand there is no way to turn off "inheriting" of global or target flags.
As a workaround you can disable warning by adding -w option:
get_source_file_property(flags "${external_source}" COMPILE_FLAGS)
if(NOT flags)
set(flags "") # convert "NOTFOUND" to "" if needed
endif()
set_source_files_properties(
"${external_source}"
PROPERTIES
COMPILE_FLAGS "-w ${flags}"
)
Just for your information: How to set warning level in CMake?.
For the record: originally from issue #408.

Related

CMake INTERFACE_INCLUDE_DIRECTORIES for IMPORTED target with CONFIG

I have an IMPORTED target which comes from a config package generated by conan. Since conan creates packages for the build type specified in the build_type of the profile, I have added CMAKE_MAP_IMPORTED_CONFIG_DEBUG to Release, so that even if I compile a Debug build of my project, I can still use Config files generated with conan using a Release profile.
This works fine for all the targets in my project. include directories are set correctly, libraries link correctly. except for one case:
I have an INTERFACE target which I use to use only the include directories of an IMPORTED target if one option is set, or use the whole target.
add_library(deTracing INTERFACE)
# Get include directories of Tracy::TracyClient target
get_target_property(TRACY_INCLUDE_DIRECTORIES Tracy::TracyClient INTERFACE_INCLUDE_DIRECTORIES)
# Include the directories unconditionally
target_include_directories(deTracing INTERFACE ${TRACY_INCLUDE_DIRECTORIES})
# Some debug stuff
message(status ${TRACY_INCLUDE_DIRECTORIES})
add_custom_target(genexdebug COMMAND ${CMAKE_COMMAND} -E echo "${TRACY_INCLUDE_DIRECTORIES}")
# If ENABLE_PROFILING is set, link to the library
if(ENABLE_PROFILING)
target_link_libraries(deTracing INTERFACE Tracy::TracyClient)
target_compile_definitions(deTracing INTERFACE ENABLE_PROFILING)
endif()
The issue is that TRACY_INCLUDE_DIRECTORIES is a generator expression with this content
$<$<CONFIG:Release>:/home/seddi/.conan/data/tracy/0.9/_/_/package/913e5b8c3a7b64b23fdaa63b58e3915a742a1112/include>, which is what I can expect, it just contains the include directory for the release config.
But when this expression is evaluated in a Debug build, it just collapses to an empty string, even though I have set the CMAKE_MAP_IMPORTED_CONFIG_DEBUG to Release, so it doesn't set any include directories.
I am out of ideas of how I set the correct INCLUDE_DIRECTORIES to deTracing target.
Edit:
According to CMake, the MAP_IMPORTED_CONFIG_ property is followed when evaluating properties of IMPORTED targets. I have modified deTracing to be an IMPORTED target, yet still doesn't work.
add_library(deTracing INTERFACE IMPORTED)
get_target_property(TRACY_INCLUDE_DIRECTORIES Tracy::TracyClient INTERFACE_INCLUDE_DIRECTORIES)
set_property(
TARGET deTracing
PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${TRACY_INCLUDE_DIRECTORIES}
APPEND) # ${TRACY_INCLUDE_DIRECTORIES})
get_target_property(deTracing_INCLUDE_DIRECTORIES deTracing INTERFACE_INCLUDE_DIRECTORIES)
message("deTracing INCLUDE_DIRECTORIES: ${deTracing_INCLUDE_DIRECTORIES}")
add_library(TestTarget INTERFACE)
target_link_libraries(TestTarget INTERFACE deTracing)
get_target_property(TestTarget_INCLUDE_DIRECTORIES TestTarget INTERFACE_INCLUDE_DIRECTORIES)
message("TestTarget INCLUDE DIRECTORIES: ${TestTarget_INCLUDE_DIRECTORIES}")
This snippet produces the following output
[cmake] deTracing INCLUDE_DIRECTORIES: $<$<CONFIG:Release>:/home/seddi/.conan/data/tracy/0.9/_/_/package/913e5b8c3a7b64b23fdaa63b58e3915a742a1112/include>
[cmake] TestTarget INCLUDE DIRECTORIES: TestTarget_INCLUDE_DIRECTORIES-NOTFOUND
which shows that deTracing is able to set the correct INCLUDE_DIRECTORIES for the Release config, but then is unable to set them on TestTarget, even though deTracing is an IMPORTED target.

CMake per file optimizations

Elsewhere the question has been asked, "How do I turn off optimizations on one file?" The answer is usually something like this:
cmake_minimum_required( VERSION 3.8 )
project( Hello )
add_executable( hello hello.c foo.c bar.c )
set( CMAKE_C_FLAGS_RELEASE "" )
set( CMAKE_CXX_FLAGS_RELEASE "" )
set_source_files_properties( hello.c
PROPERTIES
COMPILE_FLAGS -O0 )
This works unless you invoke cmake like this:
cmake -GNinja -DCMAKE_BUILD_TYPE=Release ../hello
And you get this in your build.ninja
FLAGS = -O3 -DNDEBUG -O0
Checking the documentation on COMPILE_FLAGS
Additional flags to be added when compiling this source file.
This makes sense, it is added to the list of COMPILE_FLAGS, it does not override existing compiler flags.
So, within CMake how can you override the optimisation level on a single file and being able to compile the rest of the project in Release? Otherwise you can force the compile to CMAKE_BUILD_TYPE="" which is the default behavior, but that somewhat defeats a selling point of Cmake.
You can't overwrite compiler options with the makefile CMake generators on source file level. Options are always appended (see my answer at Is Cmake set variable recursive? for the complete formula).
This is - as far as I know - only supported with the Visual Studio solution/project generators. These generators have flag tables to identify flags that are in the same group/that does overwrite a previous defined flag.
So yours is more like a feature request to also add compiler option tables to CMake's makefile generators.
Alternatives
I just wanted to add some crazy CMake magic I came up with as a workaround. Add the following to your main CMakeLists.txt after the project() command:
if (CMAKE_BUILD_TYPE)
define_property(
SOURCE
PROPERTY COMPILE_FLAGS
INHERITED
BRIEF_DOCS "brief-doc"
FULL_DOCS "full-doc"
)
string(TOUPPER ${CMAKE_BUILD_TYPE} _build_type)
set_directory_properties(PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_${_build_type}}")
set(CMAKE_CXX_FLAGS_${_build_type} "")
endif()
This example moves the CMAKE_CXX_FLAGS_<build type> content into an new COMPILE_FLAGS directory property that is then linked to COMPILE_FLAGS source file property via define_property(... INHERITED ...).
Now the build type specific flags are only defined in COMPILE_FLAGS for each source file and you can overwrite/change them e.g. with the code snippet from your example:
set_source_files_properties(
hello.c
PROPERTIES
COMPILE_FLAGS -O0
)
References
Directory properties and subdirectories
CMake: How do I change properties on subdirectory project targets?

Are there other means of obtaining the properties of a target in cmake?

I have created a C++ static library, and in order to make it searchable easily, I create the following cmake files:
lib.cmake
# The installation prefix configured by this project.
set(_IMPORT_PREFIX "C:/------/install/win32")
# Create imported target boost
add_library(lib STATIC IMPORTED)
set_target_properties(lib PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "lib_define1;lib_define2"
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/../include"
)
# Load information for each installed configuration.
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
file(GLOB CONFIG_FILES "${_DIR}/lib-*.cmake")
foreach(f ${CONFIG_FILES})
include(${f})
endforeach()
lib-debug.cmake
# Import target "boost" for configuration "Debug"
set_property(TARGET lib APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(boost PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/Debug/staticlib/lib.lib"
)
When I want to use this library in an executable, I can simply invoke it by calling find_package command:
find_package(lib REQUIRED)
if(lib_FOUND)
message("lib has been found")
else()
message("lib cannot be found")
endif(boost_FOUND)
It works and if I want to know the head file directory of the library, I will have to call it this way:
get_target_property(lib_dir lib INTERFACE_INCLUDE_DIRECTORIES)
I was just wondering whether there are other ways of obtaining the properties of an target. In this case I expect some variable like lib_INCLUDE_DIRECTORIES will exist.
No, CMake does not automatically define variables for the properties of a target (or of anything else). If you need the value of a property, you have to query it explicitly (using get_property or the specific getters like get_target_property etc.).
In your specific case, INTERFACE_INCLUDE_DIRECTORIES is a property which I would expect you would not need to query at all. The whole point of INTERFACE_* properties is to propagate usage requirements automatically; their propagation is implemented in CMake itself.

custom target as a target library in cmake

I have a custom target that is in fact an externally generated library that I want to integrate in my build.
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/liblib2.a
COMMAND make -f ${CMAKE_CURRENT_SOURCE_DIR}/makefile liblib2.a)
add_custom_target(lib2
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/liblib2.a)
How can I tell cmake that this target is in fact a library, where it can be found and where are the headers ?
To be clear : I don't want the upper CMakeList using this library having to manually specify include folders and the library location folder It must be done automatically (from the target properties).
On a standard cmake library I would just have to add the INTERFACE_INCLUDE_DIRECTORIES property in the library CMakeLists to make cmake link my app with the relevant -I and -L gcc parameters :
set_target_properties(lib1
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR})
But in the case of a custom target I don't know how to to it.
Any clue ?
Thanks for your help.
Thanks to zaufi it works!
For others who may be interested in embedded externally build target inside cmake here is what I did :
cmake_minimum_required(VERSION 2.8)
SET(LIB_FILE ${CMAKE_CURRENT_SOURCE_DIR}/bin/liblib2.a)
SET(LIB_HEADER_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/include)
# how to build the result of the library
add_custom_command(OUTPUT ${LIB_FILE}
COMMAND make
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
# create a target out of the library compilation result
add_custom_target(lib2_target DEPENDS ${LIB_FILE})
# create an library target out of the library compilation result
add_library(lib2 STATIC IMPORTED GLOBAL)
add_dependencies(lib2 lib2_target)
# specify where the library is and where to find the headers
set_target_properties(lib2
PROPERTIES
IMPORTED_LOCATION ${LIB_FILE}
INTERFACE_INCLUDE_DIRECTORIES ${LIB_HEADER_FOLDER})
Now in a CMakeLists.txt I can do somthing like
add_subdirectory(${ROOT_DIR}/lib1 bin/lib1)
add_subdirectory(${ROOT_DIR}/lib2 bin/lib2)
add_executable(app app.c )
target_link_libraries(app lib1 lib2)
No need to specify where the .a and the .h are.
You can use add_library() and tell that it actually imported. Then, using set_target_properties() you can set required INTERFACE_XXX properties for it. After that, you can use it as an ordinal target like every other built by your project.
Thank you for posting the solution. I have wrapped your snippet in a function:
function(add_external_library)
set(options)
set(oneValueArgs TARGET WORKING_DIRECTORY OUTPUT COMMENT)
set(multiValueArgs COMMAND INCLUDE_DIRS)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" ${multiValueArgs}" ${ARGN})
# Specify how to build the result of the library
add_custom_command(OUTPUT "${ARGS_OUTPUT}"
COMMAND ${ARGS_COMMAND}
WORKING_DIRECTORY "${ARGS_WORKING_DIRECTORY}"
COMMENT "${ARGS_COMMENT}")
# Create a target out of the library compilation result
add_custom_target(${ARGS_TARGET}_target DEPENDS ${ARGS_OUTPUT})
# Create an library target out of the library compilation result
add_library(${ARGS_TARGET} STATIC IMPORTED GLOBAL)
add_dependencies(${ARGS_TARGET} ${ARGS_TARGET}_target)
# Specify where the library is and where to find the headers
set_target_properties(${ARGS_TARGET}
PROPERTIES
IMPORTED_LOCATION "${ARGS_OUTPUT}"
INTERFACE_INCLUDE_DIRECTORIES "${ARGS_INCLUDE_DIRS}")
endfunction()
# Example
add_external_library(TARGET YourLib
COMMAND /bin/bash compile_your_lib.sh
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT "output/yourlib.a"
INCLUDE_DIRS "include/a" "include/b"
COMMENT "Building YourLib")
add_executable(YourExe)
target_link_libraries(YourExe YourLib)

Conditional add_custom_command in CMake

I am using a macro to create precompiled headers for my cmake project. For gcc, this macro uses add_custom_command to create a *.h.gch file which can then be added to the target along with the other source files with add_executable/add_library. The problem is that sometimes the same *.h.gch file is used for two different targets, because some libraries are built both as static and dynamic libs.
I need to call the macro after each of the add_library calls because for MSVC/Xcode, one needs to adjust the target properties to enable PCH usage/compilation. But for gcc, this results in an error as I'm trying to use add_custom_command with an output that already has a build rule (the .gch). Currently I am avoiding this error by just skipping the add_custom_command for any target that contains "Static" in its name - this happens to work because all the static libraries in the project have a "Static" postfix, but its obviously not a very elegant solution.
Is there a way in cmake to check if a target already has a build rule, or alternatively, a way to allow add_custom_command to fail silently without causing an error? Or is there a way to change my design so that I can avoid the problem entirely? I suppose one "solution" would be to add a conditional check in each of the CMakeLists, but I really don't want to do that.
This is the code I am currently using:
The Macro:
macro(SET_PRECOMPILED_HEADER targetName PCHFile)
if(MSVC)
# PCH for MSVC
elseif(${CMAKE_GENERATOR} MATCHES "Xcode")
# PCH for Xcode
else() #gcc
if(NOT ${targetName} MATCHES "Static") ## <-- this is bad
## set the correct "compilerArgs"
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch
COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1} ${compilerArgs}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PCHFile}
)
endif()
endmacro(SET_PRECOMPILED_HEADER targetName PCHFile)
...then in the CMakeLists, something like this:
# Dynamic version:
set(MODULE_NAME MyLib)
project(${MODULE_NAME})
## set ${sources}
add_library(${MODULE_NAME} SHARED ${sources} "src/precompiled.h.${PCH_EXT}")
set_target_properties(${MODULE_NAME} PROPERTIES COMPILE_DEFINITIONS MY_DLL_DEFINITION)
SET_PRECOMPILED_HEADER(${MODULE_NAME} "src/precompiled.h")
# Static version:
set(MODULE_NAME MyLibStatic)
project(${MODULE_NAME})
add_library(${MODULE_NAME} ${sources} "src/precompiled.h.${PCH_EXT}")
set_target_properties(${MODULE_NAME} PROPERTIES COMPILE_DEFINITIONS MY_STATIC_DEFINITION)
SET_PRECOMPILED_HEADER(${MODULE_NAME} "src/precompiled.h")
Thanks for your help! I'm sorry if this is a duplicate - there are already several questions on add_custom_command, but none of them quite seem to address what I'm after.
First, you can create target for each PCH and then use this before declaring new target:
if(TARGET ${PCHFile}.gch)
Another way:
In the root CMakeLists.txt:
set(PRECOMPILED_HEADERS "" CACHE INTERNAL "")
In the macro:
list(FIND PRECOMPILED_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch res)
if(NOT res EQUAL -1)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch
COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1} ${compilerArgs}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PCHFile}
)
list(APPEND PRECOMPILED_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch)
endif()