How can I get a target's output-file's name during CMake's configuration phase (not the generation phase)? - cmake

I know we can use $<TARGET_FILE_NAME:Foo> to get the filename for add_custom_command and add_custom_target during build time, but I can't seem to find the answer on out how to get the filename during config time. For example,
add_library(Foo SHARED foo.cpp foo.h)
The best I get is get_target_property(FOO_NAME Foo NAME), but ${FOO_NAME} is Foo, what I want is something like libFoo.so or libFoo.dylib depends on the platform. How can we get the target file name during cmake config time?
For context on why I thought I initially thought I needed to be able to do this, see this other question: In CMake how can I copy a target file to a custom location when target based generator expression for OUTPUT is not supported?.

Through a combination of the following:
variable/CMAKE_BUILD_TYPE
prop_tgt/TYPE
prop_tgt/FRAMEWORK
variable/CMAKE_STATIC_LIBRARY_PREFIX and variable/CMAKE_STATIC_LIBRARY_SUFFIX
variable/CMAKE_SHARED_LIBRARY_PREFIX and variable/CMAKE_SHARED_LIBRARY_SUFFIX
variable/CMAKE_SHARED_MODULE_PREFIX and variable/CMAKE_SHARED_MODULE_SUFFIX
variable/CMAKE_EXECUTABLE_SUFFIX
prop_tgt/OUTPUT_NAME and prop_tgt/OUTPUT_NAME_<CONFIG>
prop_tgt/ARCHIVE_OUTPUT_NAME and prop_tgt/ARCHIVE_OUTPUT_NAME_<CONFIG>
prop_tgt/LIBRARY_OUTPUT_NAME and prop_tgt/LIBRARY_OUTPUT_NAME_<CONFIG>
prop_tgt/RUNTIME_OUTPUT_NAME and prop_tgt/RUNTIME_OUTPUT_NAME_<CONFIG>
I believe you could do something like this:
# cmake_minimum_required(VERSION 3.x)
project(hello)
function(get_target_filename target outvar)
get_target_property(prop_type "${target}" TYPE)
get_target_property(prop_is_framework "${target}" FRAMEWORK)
get_target_property(prop_outname "${target}" OUTPUT_NAME)
get_target_property(prop_archive_outname "${target}" ARCHIVE_OUTPUT_NAME)
get_target_property(prop_library_outname "${target}" LIBRARY_OUTPUT_NAME)
get_target_property(prop_runtime_outname "${target}" RUNTIME_OUTPUT_NAME)
# message("prop_archive_outname: ${prop_archive_outname}")
# message("prop_library_outname: ${prop_library_outname}")
# message("prop_runtime_outname: ${prop_runtime_outname}")
if(DEFINED CMAKE_BUILD_TYPE)
get_target_property(prop_cfg_outname "${target}" "${OUTPUT_NAME}_${CMAKE_BUILD_TYPE}")
get_target_property(prop_archive_cfg_outname "${target}" "${ARCHIVE_OUTPUT_NAME}_${CMAKE_BUILD_TYPE}")
get_target_property(prop_library_cfg_outname "${target}" "${LIBRARY_OUTPUT_NAME}_${CMAKE_BUILD_TYPE}")
get_target_property(prop_runtime_cfg_outname "${target}" "${RUNTIME_OUTPUT_NAME}_${CMAKE_BUILD_TYPE}")
# message("prop_archive_cfg_outname: ${prop_archive_cfg_outname}")
# message("prop_library_cfg_outname: ${prop_library_cfg_outname}")
# message("prop_runtime_cfg_outname: ${prop_runtime_cfg_outname}")
if(NOT ("${prop_cfg_outname}" STREQUAL "prop_cfg_outname-NOTFOUND"))
set(prop_outname "${prop_cfg_outname}")
endif()
if(NOT ("${prop_archive_cfg_outname}" STREQUAL "prop_archive_cfg_outname-NOTFOUND"))
set(prop_archive_outname "${prop_archive_cfg_outname}")
endif()
if(NOT ("${prop_library_cfg_outname}" STREQUAL "prop_library_cfg_outname-NOTFOUND"))
set(prop_library_outname "${prop_library_cfg_outname}")
endif()
if(NOT ("${prop_runtime_cfg_outname}" STREQUAL "prop_runtime_cfg_outname-NOTFOUND"))
set(prop_runtime_outname "${prop_runtime_cfg_outname}")
endif()
endif()
set(outname "${target}")
if(NOT ("${prop_outname}" STREQUAL "prop_outname-NOTFOUND"))
set(outname "${prop_outname}")
endif()
if("${prop_is_framework}")
set(filename "${outname}")
elseif(prop_type STREQUAL "STATIC_LIBRARY")
if(NOT ("${prop_archive_outname}" STREQUAL "prop_archive_outname-NOTFOUND"))
set(outname "${prop_archive_outname}")
endif()
set(filename "${CMAKE_STATIC_LIBRARY_PREFIX}${outname}${CMAKE_STATIC_LIBRARY_SUFFIX}")
elseif(prop_type STREQUAL "MODULE_LIBRARY")
if(NOT ("${prop_library_outname}" STREQUAL "prop_library_outname-NOTFOUND"))
set(outname "${prop_library_outname}")
endif()
set(filename "${CMAKE_SHARED_MODULE_LIBRARY_PREFIX}${outname}${CMAKE_SHARED_MODULE_LIBRARY_SUFFIX}")
elseif(prop_type STREQUAL "SHARED_LIBRARY")
if(WIN32)
if(NOT ("${prop_runtime_outname}" STREQUAL "prop_runtime_outname-NOTFOUND"))
set(outname "${prop_runtime_outname}")
endif()
else()
if(NOT ("${prop_library_outname}" STREQUAL "prop_library_outname-NOTFOUND"))
set(outname "${prop_library_outname}")
endif()
endif()
set(filename "${CMAKE_SHARED_LIBRARY_PREFIX}${outname}${CMAKE_SHARED_LIBRARY_SUFFIX}")
elseif(prop_type STREQUAL "EXECUTABLE")
if(NOT ("${prop_runtime_outname}" STREQUAL "prop_runtime_outname-NOTFOUND"))
set(outname "${prop_runtime_outname}")
endif()
set(filename "${CMAKE_EXECUTABLE_PREFIX}${outname}${CMAKE_EXECUTABLE_SUFFIX}")
else()
message(FATAL_ERROR "target \"${target}\" is not of type STATIC_LIBRARY, MODULE_LIBRARY, SHARED_LIBRARY, or EXECUTABLE.")
endif()
set("${outvar}" "${filename}" PARENT_SCOPE)
endfunction()
add_library(static_lib STATIC test.cpp)
add_library(shared_lib SHARED test.cpp)
add_executable(executable test.cpp)
get_target_filename(static_lib static_lib_filename)
get_target_filename(shared_lib shared_lib_filename)
get_target_filename(executable executable_filename)
message(STATUS "static_lib_filename: ${static_lib_filename}")
message(STATUS "shared_lib_filename: ${shared_lib_filename}")
message(STATUS "executable_filename: ${executable_filename}")
The above is a basic implementation. It doesn't handle some (perhaps important) nuances like:
The fact that most of those target properties can themselves have generator expressions in them (see their docs), which, if it happens to you, I think you're out of luck.
The fact that CMAKE_BUILD_TYPE is only relevant for single-config generators- not multi-config generators.
https://cmake.org/cmake/help/latest/variable/CMAKE_EXECUTABLE_SUFFIX_LANG.html
Other language-specific overrides like CMAKE_SHARED_LIBRARY_PREFIX_<LANG>
You'd need to check if those exist and handle them if they do... except in honesty I'm not quite sure how, given that it doesn't seem like targets have a LANGUAGE property. Source files do, but that's not what we need here. One might need to go to the CMake Discourse to ask about this.
Note: If you want the full path to the target output file... oh boy...
prop_tgt/ARCHIVE_OUTPUT_DIRECTORY and prop_tgt/ARCHIVE_OUTPUT_DIRECTORY_<CONFIG>
prop_tgt/LIBRARY_OUTPUT_DIRECTORY and prop_tgt/LIBRARY_OUTPUT_DIRECTORY_<CONFIG>
prop_tgt/RUNTIME_OUTPUT_DIRECTORY and prop_tgt/RUNTIME_OUTPUT_DIRECTORY_<CONFIG>
More fun notes: If you want to evaluate generator expressions recursively at generation time (for generator expressions that themselves evaluate to generator expressions), you can use the $<GENEX_EVAL:...> generator expression, but of course- that doesn't apply to this question, which is about configure time.

A little bit clumsy but the file name can be constructed in the following way:
set(FOO_NAME "${CMAKE_SHARED_LIBRARY_PREFIX}Foo${CMAKE_SHARED_LIBRARY_SUFFIX}")
See also cmake doc.
Note that the two variables will be overridden by the respective CMAKE_SHARED_LIBRARY_??FIX_<LANG> variables. So if that's a possibility, you need make sure you catch the right variable.
Let me add the final remark that the rationale behind CMake is you don't need to know. CMake operates on targets, not files. So whatever you're trying to achieve might be possible without getting the filename.

Related

cmake. define project languages according to architecture (CMAKE_SYSTEM_NAME)

My project definition expect different set of languages according to architecture (macOS or Windows).
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
project(myProj CXX OBJC OBJCXX)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
project(myProj)
endif()
However, it looks like the CMAKE_SYSTEM_NAME is only defined after the project command.
like in this sample code, only the second message will show valid arch.
message("CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
project(myProj)
message("CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
Any idea how to fetch the running arch before the project is defined ?
The following is what you intend:
project(myProj LANGUAGES NONE)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
enable_language(CXX)
enable_language(OBJC)
enable_language(OBJCXX)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
enable_language(C)
enable_language(CXX)
endif()
Of course, this leaves no languages configured on other systems, which is probably not what you want...

Have a CMake project default to the Release build type

How should I make my CMake project default to configuration for a Release rather than a Debug build?
You could try the following:
if (NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE)
endif()
endif()
I started out with simplistic:
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
but thanks to #RaulLaasner's suggestions, I now do:
if (NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE)
endif()
and that seems to be more robust.
Edit: Upon later reflection, I've decided to not change the default from within the CMake script itself. Perhaps it's better to leave the default the same as with all other CMake scripts in the world basically.

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.

list(REMOVE_ITEM) not working in cmake

Following is a part of my CMakeLists.txt file.
file(GLOB SOURCES "xyz/*.cpp")
message("${SOURCES}")
list(REMOVE_ITEM SOURCES "src1.cpp")
message("${SOURCES}")
Here in file "xyz/*.cpp" is a relative path.
Content of ${SOURCES} is the same before and after REMOVE_ITEM.
Why is list(REMOVE_ITEM) not working in my case? Any help would be invaluable.
I got a solution for your problem. The idea behind my solution is, to get the full path of the special cpp file, because I saw, that the command file(GLOB SOURCES "src/*.cpp") gives me a list of full paths. After getting the full path of the special file, I could do a remove from the list. Here is a small example
cmake_minimum_required(VERSION 3.4)
project(list_remove_item_ex)
file(GLOB SOURCES "src/*.cpp")
# this is the file I want to exclude / remove from the list
get_filename_component(full_path_test_cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/test.cpp ABSOLUTE)
message("${full_path_test_cpp}")
list(REMOVE_ITEM SOURCES "${full_path_test_cpp}")
message("${SOURCES}")
In addition to accepted answer you can use the following function to filter lists using regexp:
# Filter values through regex
# filter_regex({INCLUDE | EXCLUDE} <regex> <listname> [items...])
# Element will included into result list if
# INCLUDE is specified and it matches with regex or
# EXCLUDE is specified and it doesn't match with regex.
# Example:
# filter_regex(INCLUDE "(a|c)" LISTOUT a b c d) => a c
# filter_regex(EXCLUDE "(a|c)" LISTOUT a b c d) => b d
function(filter_regex _action _regex _listname)
# check an action
if("${_action}" STREQUAL "INCLUDE")
set(has_include TRUE)
elseif("${_action}" STREQUAL "EXCLUDE")
set(has_include FALSE)
else()
message(FATAL_ERROR "Incorrect value for ACTION: ${_action}")
endif()
set(${_listname})
foreach(element ${ARGN})
string(REGEX MATCH ${_regex} result ${element})
if(result)
if(has_include)
list(APPEND ${_listname} ${element})
endif()
else()
if(NOT has_include)
list(APPEND ${_listname} ${element})
endif()
endif()
endforeach()
# put result in parent scope variable
set(${_listname} ${${_listname}} PARENT_SCOPE)
endfunction()
For example in question it could look as the following:
file(GLOB SOURCES "xyz/*.cpp")
message("${SOURCES}")
filter_regex(EXCLUDE "src1\\.cpp" SOURCES ${SOURCES})
message("${SOURCES}")
(Because regexp is used then . is replaced with \\.)

Using CMake with libraries with diamond depedencies

Lets say I have four separate projects. Three are libraries, Common, Foo, and Bar, and one of them is an executable, App. Both Foo and Bar depend on the Common library, and App depends on Foo and Bar. Furthermore, some of these projects have some scripts that need to run to generate some header and source files.
Right now I have a mess of calls like this:
if (NOT TARGET common)
add_subdirectory(${common_lib_directory})
endif()
But this doesn't feel like the right solution. If I don't wrap it in that if guard, then there are errors because it tries to build Common more than once. Putting if guards in the CMakeLists for each project doesn't seem right either. Should I be writing Find<Library> scripts for each library? I've tried looking for examples or best practices on how to set up my CMakeLists files, but the only examples I can find are either too trivial or cover completely different use cases.
It was requested in the comments that I post some code. This is more or less what my CMakeLists.txt file looks like:
cmake_minimum_required(VERSION 2.8.12)
project(app)
# Temporary files (like object files) created while compiling projects.
set(tmp_dir ${CMAKE_BINARY_DIR}/obj)
# Directory which contains the source for 3rd party libraries.
if(NOT DEFINED dependencies_root)
get_filename_component(
dependencies_root "${CMAKE_CURRENT_SOURCE_DIR}/../../../../external"
REALPATH)
endif()
set(dependencies_foo_dir "${dependencies_root}/foo"
CACHE PATH "Directory containing the foo library.")
set(dependencies_bar_dir "${dependencies_root}/bar"
CACHE PATH "Directory containing the bar library.")
if(NOT TARGET foo)
add_subdirectory("${dependencies_foo_dir}" ${tmp_dir}/foo)
endif()
if(NOT TARGET bar)
add_subdirectory("${dependencies_bar_dir}" ${tmp_dir}/bar)
endif()
include_directories(${dependencies_foo_dir}/include)
include_directories(${foo_generated_include_dir})
include_directories(${dependencies_bar_dir}/include)
include_directories(${bar_generated_include_dir})
set(app_srcs ...)
add_executable(app ${app_SRCS})
target_link_libraries(app foo bar common)
As previously mentioned, the reason I have the if (NOT TARGET blah) guards is because if I don't then I get errors like these:
CMake Error at /path/to/my/project/CMakeLists.txt:267 (add_library):
add_library cannot create target "blah" because another target with the
same name already exists. The existing target is a static library created
in source directory
"/path/to/blah".
See documentation for policy CMP0002 for more details.
If you have such closely-linked projects, the best decision seems to use guard from re-including at the beginning of each project. Such guard can be easily implemented using return command, which returns from currently executed add_subdirectory() call:
Foo/CMakeLists.txt:
if(DEFINED Foo_GUARD)
if(NOT Foo_GUARD STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
return() # Project has been already included by someone else
endif()
else()
set(Foo_GUARD ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "Foo guard")
endif()
project(Foo)
...
So any project can use unprotected add_subdirectory() call for include given one:
App/CMakeLists.txt:
...
# Need *Foo* functionality? Simply include it!
add_subdirectory("${dependencies_foo_dir}" ${tmp_dir}/foo)
It is possible to implement guard as a macro, put it into the library and use it in every your project:
cmake/utils.cmake:
macro(project_guarded name)
if(DEFINED ${name}_GUARD)
if(NOT ${name}_GUARD STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
return() # return() *doesn't* terminate a macro!
endif()
else()
set(${name}_GUARD ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "${name} guard")
endif()
project(${name})
endmacro()
Macro usage is straightforward:
project_guarded(Foo)