Remove compile option which was populated from interface library - cmake

I have some existing cmake file where interface library is used for populating of numerous compile options into another targets. In one of my target I want to exclude one of these options and don't touch another ones.
I tried to use get_target_property/set_target_property but unfortunately didn't find proper usage them.
Does anyone know how to remove option (for example /we4800) from test_project?
cmake_minimum_required (VERSION 3.8)
add_library(common_compile_options INTERFACE)
set_property(TARGET common_compile_options PROPERTY INTERFACE_COMPILE_OPTIONS
/we4309
/we4800
)
add_executable (test_project "main.cpp")
target_link_libraries(test_project common_compile_options)
====UPDATE 1====:
I tried also followed workaround:
cmake_minimum_required (VERSION 3.8)
add_library(common_compile_options INTERFACE)
set_property(TARGET common_compile_options PROPERTY INTERFACE_COMPILE_OPTIONS
/we4309
/we4800
)
get_target_property(CACHE_PROPERTY common_compile_options INTERFACE_COMPILE_OPTIONS)
string(REPLACE "/we4800" "" TEMP_PROPERTY "${CACHE_PROPERTY}")
add_executable (project "main.cpp")
target_link_libraries(project common_compile_options)
set_property(TARGET project PROPERTY COMPILE_OPTIONS ${TEMP_PROPERTY})
get_target_property(PROJECT_PROPERTY project COMPILE_OPTIONS)
message("New properties: " ${PROJECT_PROPERTY})
In cmake output I got message as expected:
New properties: /we4309
But /we4800 option is still passed to the compiler :(

I found out the working solution, but the question is, will it work in all cases?
cmake_minimum_required (VERSION 3.8)
add_library(common_compile_options INTERFACE)
set_property(TARGET common_compile_options PROPERTY INTERFACE_COMPILE_OPTIONS
/we4309
/we4800
)
add_executable (project "main.cpp")
target_link_libraries(project common_compile_options)
set_source_files_properties(main.cpp
DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
PROPERTIES COMPILE_OPTIONS /wd4800
)

Related

INCLUDE_DIRECTORIES property of dependent target is not populated in CMake

I've read in few places, this is one of them, that when using the meaning of the PUBLIC, PRIVATE and INTERFACE keywords in the context of commands such as target_include_directories is as follows:
PRIVATE - directories after this keyword will be added to the INCLUDE_DIRECTORIES property of the target specified.
INTERFACE - directories after this keyword will be added to the INTERFACE_INCLUDE_DIRECTORIES property of the target specified.
PUBLIC - directories after this keyword will be added to both.
Directories that will be added to the INTERFACE_INCLUDE_DIRECTORIES will be added to the INCLUDE_DIRECTORIES of any target dependent on the current target.
OK I run the following experiment:
// libmymath/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
add_library(MyMath src/mymath.cpp)
target_include_directories(MyMath PUBLIC include)
get_target_property(MYMATH_INC_DIR MyMath INCLUDE_DIRECTORIES)
get_target_property(MYMATH_INC_DIR_INTERFACE MyMath INTERFACE_INCLUDE_DIRECTORIES)
message("Libmath INCLUDE_DIRECTORIES: ${MYMATH_INC_DIR}")
message("Libmath INCLUDE_DIRECTORIES INTERFACE: ${MYMATH_INC_DIR_INTERFACE}")
calculator/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
add_executable(calculator calculator.cpp)
target_link_libraries(calculator MyMath)
get_target_property(CALCULATOR_INC_DIR calculator INCLUDE_DIRECTORIES)
get_target_property(CALCULATOR_INC_DIR_INTERFACE calculator INTERFACE_INCLUDE_DIRECTORIES)
message("Calculator INCLUDE_DIRECTORIES: ${CALCULATOR_INC_DIR}")
message("Calculator INCLUDE_DIRECTORIES INTERFACE: ${CALCULATOR_INC_DIR_INTERFACE}")
// top-level CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(Math)
add_subdirectory(libmath)
add_subdirectory(calculator)
get_target_property(CALC_INC_DIR calculator INCLUDE_DIRECTORIES)
get_target_property(CALC_INC_DIR_INTERFACE calculator INTERFACE_INCLUDE_DIRECTORIES)
message("From top-level CMakeLists.txt: Calculator INCLUDE_DIRECTORIES: ${CALC_INC_DIR}")
message("From top-level CMakeLists.txt: Calculator INTERFACE_INCLUDE_DIRECTORIES: ${CALC_INC_DIR_INTERFACE}")
Running this gives:
Libmath INCLUDE_DIRECTORIES: /home/yoav/playground/cmake/math/libmath/include
Libmath INCLUDE_DIRECTORIES INTERFACE: /home/yoav/playground/cmake/math/libmath/include
Calculator INCLUDE_DIRECTORIES: CALCULATOR_INC_DIR-NOTFOUND
Calculator INCLUDE_DIRECTORIES INTERFACE: CALCULATOR_INC_DIR_INTERFACE-NOTFOUND
From top-level CMakeLists.txt: Calculator INCLUDE_DIRECTORIES: CALC_INC_DIR-NOTFOUND
From top-level CMakeLists.txt: Calculator INTERFACE_INCLUDE_DIRECTORIES: CALC_INC_DIR_INTERFACE-NOTFOUND
So we see that the INCLUDE_DIRECTORIES property of calculator is not populated, although I can see in the flags.make file of this target that the CXX_INCLUDE variable is set properly !
What gives?
Content of target property INTERFACE_INCLUDE_DIRECTORIES is actually transferred into property INCLUDE_DIRECTORIES of every target, which consumes (with target_link_libraries command) given target.
But CMake defers all transfers to a consuming target until the generation phase, which occurs after whole CMakeLists.txt has been processed. This is why one cannot obtain final value of the property using get_target_property command: this command can only obtain current value of the property, before the transferring.
However, target properties referred by the generator expressions are expanded to the final value. (But again, this expansion is observable only at the generation phase and after it).
One way to see the expanded value of a generator expression is file(GENERATE): this command will create a file, which contains expanded generator expressions.
Example:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project (hello)
add_library(foo SHARED foo.c)
target_include_directories(foo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_library(bar SHARED bar.c)
target_include_directories(bar PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include1)
target_link_libraries(bar PUBLIC foo)
get_target_property(bar_include_directories bar INCLUDE_DIRECTORIES)
message(STATUS "bar include directories: ${bar_include_directories}")
file(GENERATE
OUTPUT bar_include_directories.txt
CONTENT "include directories: $<TARGET_PROPERTY:bar,INCLUDE_DIRECTORIES>\n"
)
When configure this CMake project, only current value of the property will be printed:
build$ cmake ..
-- bar include directories: /home/tester/tests/cmake/include1
-- Configuring done
-- Generating done
-- Build files have been written to: /home/tester/tests/cmake/build
But the file bar_include_directories.txt will contain final value of the property:
$ cat bar_include_directories.txt
include directories: /home/tester/tests/cmake/include1;/home/tester/tests/cmake
Directories that will be added to the INTERFACE_INCLUDE_DIRECTORIES will be added to the INCLUDE_DIRECTORIES of any target dependent on the current target.
I believe this is a simple way to explain it that way, but indeed this is not something that can be observed inside cmake scripts, but it's rather "generated" when cmake generates the build system files.
target_link_libraries populates LINK_LIBRARIES and INTERFACE_LINK_LIBRARIES properties of a target. Then like after cmake has run all scripts and is generating build files, it goes through LINK_LIBRARIES of a target and takes INTERFACE_INCLUDE_DIRECTORIES of dependent targets and generates build system files that include these directories for that target.

Cannot specify compile options for imported target "..."

I want to provide the users of my library with two targets: one that specifies the include path etc., and one that carries useful extra compile options. However, for the extra target some of my users are getting the error
Cannot specify compile options for imported target "myproject::extra"
so it seems on older CMake versions.
I tested with CMake 3.9.2. The test project, including CI is on GitHub, with failing build here.
(How) can my approach be rendered robust for all CMake versions?
The project's main CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(myproject)
add_library(myproject INTERFACE)
set(MYPROJECT_VERSION "1.0.0")
target_include_directories(myproject INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION include)
install(TARGETS myproject EXPORT myproject-targets)
install(EXPORT myproject-targets FILE myprojectTargets.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" VERSION ${MYPROJECT_VERSION} COMPATIBILITY AnyNewerVersion)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/myprojectConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
The project's myprojectConfig.cmake:
include(CMakeFindDependencyMacro)
if(NOT TARGET myproject)
include("${CMAKE_CURRENT_LIST_DIR}/myprojectTargets.cmake")
endif()
if(NOT TARGET myproject::extra)
add_library(myproject::extra INTERFACE IMPORTED)
if(MSVC)
target_compile_options(myproject::extra INTERFACE /W4)
else()
target_compile_options(myproject::extra INTERFACE -Wall)
endif()
endif()
The user's project CMakeLists.txt could then look as follows:
cmake_minimum_required(VERSION 3.0)
project(myexec)
find_package(myproject REQUIRED)
add_executable(myexec main.cpp)
target_link_libraries(myexec PRIVATE myproject myproject::extra)
List of functions applicable for IMPORTED and INTERFACE targets changes as CMake evolves.
Most of such functions affects only on specific target properties. So, instead of calling a function, you may set the property directly. This will work in any CMake version:
# Works only in new CMake versions
target_compile_options(myproject::extra INTERFACE /W4)
# Equivalent which works in any CMake version
set_property(TARGET myproject::extra PROPERTY INTERFACE_COMPILE_OPTIONS /W4)

Unifying CMake's FIND_PACKAGE

Do you know a trick to get a unified output terminology when getting include and library paths from CMake's FIND_PACKAGE?
Sometimes it's FOO_INCLUDE. Sometimes it's FOO_INCLUDE_PATH. Etc.
For example, I would like to find a way to ensure that FOO_INCLUDE and FOO_LIB be always defined when FOO_FOUND is set to TRUE after a call to FIND_PACKAGE.
Turning my comment into an answer
To avoid the need to know the include path, library, etc. dependencies and their variable notations modern find_package's implementation do provide IMPORTED targets like Foo::Foo. But this is - as #Tsyvarev has commented - far from unified through all of CMake's find modules.
So generalizing the CMake's Sample Find Module implementation you could unify your find_package() calls with an overwritten find_package() macro version like the following:
cmake_minimum_required(VERSION 3.2)
project(UnifiedFindPackage)
macro(unify_vars _result)
set(${_result} "")
foreach(_i IN ITEMS ${ARGN})
if (${_i})
list(APPEND ${_result} "${${_i}}")
endif()
endforeach()
endmacro()
macro(find_package _name)
_find_package(${_name} ${ARGN})
if (${_name}_FOUND AND NOT TARGET ${_name}::${_name})
add_library(${_name}::${_name} STATIC IMPORTED GLOBAL)
unify_vars(_var ${_name}_LIBRARY ${_name}_LIB)
if (_var)
set_target_properties(${_name}::${_name} PROPERTIES IMPORTED_LOCATION "${_var}")
endif()
if (${_name}_LIBRARY_RELEASE)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(${_name}::${_name} PROPERTIES IMPORTED_LOCATION_RELEASE "${${_name}_LIBRARY_RELEASE}")
endif()
if (${_name}_LIBRARY_DEBUG)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(${_name}::${_name} PROPERTIES IMPORTED_LOCATION_DEBUG "${${_name}_LIBRARY_DEBUG}")
endif()
if (${_name}_LIBRARIES)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${${_name}_LIBRARIES}")
endif()
unify_vars(_var ${_name}_INCLUDE_DIRS ${_name}_INCLUDE_PATH)
if (_var)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${_var}")
endif()
unify_vars(_var ${_name}_COMPILE_FLAGS ${_name}_DEFINITIONS)
if (_var)
set_property(TARGET ${_name}::${_name} APPEND PROPERTY INTERFACE_COMPILE_OPTIONS "${_var}")
endif()
endif()
endmacro()
find_package(MPI REQUIRED)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} MPI::MPI)
This should only demonstrate a possible unification and could be extended on a need-by basis.
Edit: Turning this into an community wiki answer. Please feel free to contribute.
The code was tested in this example with MPI find module results.

Transitively require other imported library's interface from imported library

CMake 3.5
I have an existing external library, which I have made an IMPORTED library in my CMakeLists.txt:
find_path(
FOO_INCLUDE_DIR NAMES foo.h
PATHS "${FOO_ROOT}"
NO_DEFAULT_PATH
PATH_SUFFIXES include/foo
)
find_library(
FOO_LIBRARY NAMES foo
PATHS "${FOO_ROOT}"
PATH_SUFFIXES lib
)
mark_as_advanced(FOO_INCLUDE_DIR FOO_LIBRARY)
find_package_handle_standard_args(
FOO REQUIRED_VARS
FOO_INCLUDE_DIR
FOO_LIBRARY
)
add_library(Foo::Foo SHARED IMPORTED)
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_INCLUDE_DIRECTORIES
"${FOO_INCLUDE_DIR}" "${BAR_INCLUDE_DIR}"
"${Boost_INCLUDE_DIRS}" "${BAZ_INCLUDE_DIR}"
)
set_property(TARGET Foo::Foo
PROPERTY IMPORTED_LOCATION
"${FOO_LIBRARY}"
)
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_LINK_LIBRARIES
"${Boost_LIBRARIES}" "${BAR_LIBRARY}" "${BAZ_LIBRARIES}"
)
Programs that link with this particular library need thread support. Adding thread support to something is fairly straightforward:
set(THREADS_PREFER_PTHREAD_FLAG on)
include(FindThreads)
...
target_link_libraries(something PUBLIC Threads::Threads)
I would like anything that links against Foo::Foo to automatically include Threads::Threads. But you can't use target_link_libraries() on an IMPORTED library. So how do I transitively require Threads::Threads from Foo::Foo?
I managed to work around this by doing the following, but it depended on me checking to see what properties FindThreads sets on Threads::Threads. Is there a better way?
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_LINK_LIBRARIES
"${Boost_LIBRARIES}" "${BAR_LIBRARY}" "${BAZ_LIBRARIES}"
$<TARGET_PROPERTY:Threads::Threads,INTERFACE_LINK_LIBRARIES>
)
set_property(TARGET Foo::Foo
PROPERTY INTERFACE_COMPILE_OPTIONS
$<TARGET_PROPERTY:Threads::Threads,INTERFACE_COMPILE_OPTIONS>
)
It turns out the answer was quite simple. I simple needed to add Threads::Threads to the INTERFACE_LINK_LIBRARIES property of Foo::Foo. I have no idea why I had determined that would not work the first time around.

Project build configuration in CMake

My question is very similar to CMake : Changing name of Visual Studio and Xcode exectuables depending on configuration in a project generated by CMake. In that post the output file name will change according to the project configuration (Debug, Release and so on). I want to go further. When I know the configuration of the project, I want to tell the executable program to link different library names depending on project configurations. I was wondering whether there is a variable in CMake that can tell the project configuration. If there exists such a variable, my task will become easier:
if (Project_Configure_Name STREQUAL "Debug")
#do some thing
elseif (Project_Configure_Name STREQUAL "Release")
#do some thing
endif()
According to http://cmake.org/cmake/help/v2.8.8/cmake.html#command:target_link_libraries, you can specify libraries according to the configurations, for example:
target_link_libraries(mytarget
debug mydebuglibrary
optimized myreleaselibrary
)
Be careful that the optimized mode means every configuration that is not debug.
Following is a more complicated but more controllable solution:
Assuming you are linking to an imported library (not compiled in your cmake project), you can add it using:
add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION_RELEASE c:/path/to/foo.lib)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION_DEBUG c:/path/to/foo_d.lib)
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe foo)
See http://www.cmake.org/Wiki/CMake/Tutorials/Exporting_and_Importing_Targets for more details.
There is always another way:
if(CMAKE_BUILD_TYPE MATCHES "release")
SET(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE})
else(CMAKE_BUILD_TYPE MATCHES "debug")
SET(CMAKE_BUILD_TYPE "debug")
endif(CMAKE_BUILD_TYPE MATCHES "release")
We can use the variable CMAKE_BUILD_TYPE. We can also change this variable at the beginning of invoking CMAKE:
cmake .. -DCMAKE_BUILD_TYPE:STRING=debug
Then we can use this variable as an indicator of build configuration.