Using CMake with libraries with diamond depedencies - cmake

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)

Related

CHECK_INCLUDE_FILE_CXX does not respect 'target_include_directories' when looking for include file

I'm trying to build simple-web-server using a local standalone copy of asios. As I don't have the library installed, and I can't install it due to security restrictions, I've modified the cmakelists file just a bit, to tell it where to search for the include file. I can clearly see that it's finding the location, but CHECK_INCLUDE_FILE_CXX isn't finding it for some reason, even though I've added the directory with target_include_directories. What is the correct way to do this?
if(USE_STANDALONE_ASIO)
### BEGIN CUSTOM ADDITIONS ###
message(NOTICE "Searching for asio.hpp")
find_path(ASIO_PATH asio.hpp ./asio/include)
if(ASIO_PATH-NOTFOUND)
message(AUTHOR_WARNING "Asio not found in ./asio/include")
else()
message(NOTICE "asio.hpp found in ${ASIO_PATH}")
target_include_directories(simple-web-server INTERFACE ASIO_PATH)
endif()
### END CUSTOM ADDITIONS ###
find_package(Threads REQUIRED)
target_link_libraries(simple-web-server INTERFACE ${CMAKE_THREAD_LIBS_INIT})
target_include_directories
target_compile_definitions(simple-web-server INTERFACE USE_STANDALONE_ASIO)
include(CheckIncludeFileCXX)
CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO)
if(NOT HAVE_ASIO)
message(FATAL_ERROR "Standalone Asio not found")
endif()
Command target_include_directories and include_directories affects on compilation, but doesn't affect on checking headers via CHECK_INCLUDE_FILE_CXX.
For hint the macro CHECK_INCLUDE_FILE_CXX to search in additional include directories, set variable CMAKE_REQUIRED_INCLUDES:
list(APPEND CMAKE_REQUIRED_INCLUDES ${ASIO_PATH})
...
CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO)
This variable is described in the documentation:
CMAKE_REQUIRED_INCLUDES
a list of header search paths to pass to the compiler.
While internally CHECK_INCLUDE_FILE_CXX performs compilation, it compiles in other CMake project (via try_compile). That other project doesn't receive properties (like INCLUDE_DIRECTORIES) of the main project. Instead, a specific set of variables is explicitly passed from the main project, and CMAKE_REQUIRED_INCLUDES is among that variables.

How to get include directories from a target for use in add_custom_target?

I'm modeling dependencies with target_link_libraries, as is done in this blog post.
target_link_libraries(Foo
LibraryA
LibraryB
)
This is working great, but for various reasons I need to use add_custom_target to preprocess to a file through a custom command. The problem is, this custom target depends on the includes of LibraryA and LibraryB. I was really hoping to do the following like how target_link_libraries works (see the LibraryA and LibraryB bit):
add_custom_target(Bar ALL
COMMAND ${CMAKE_C_COMPILER} thing.cpp LibraryA LibraryB /P
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Path/Here
COMMENT "Preprocessing to a file"
VERBATIM
)
However, this doesn't work. LibraryA and LibraryB are put in as they appear. Even if it did work, I imagine I would get more than the includes, since I think the targets include the library as well. Maybe this is not a good approach.
So, what can I do here? How can I extract the include directories from each target, for use in the custom command? I found if I find_package(Foo REQUIRED) I get access to Foo_DIR, but that points to the build directory and not the source directory where the includes are.
You can extract the include directories from each target using get_target_property(). A target's INCLUDE_DIRECTORIES property contains the include directories for that target. Since you have two targets, LibraryA and LibraryB, we have to call it twice. Then, we can concatenate the list of include directories together using foreach(). If you are using these as include directories in a compiler command (such as MSVC), you can append the /I compiler option to each directory in the loop also:
# Get the include directories for the target.
get_target_property(LIBA_INCLUDES LibraryA INCLUDE_DIRECTORIES)
get_target_property(LIBB_INCLUDES LibraryB INCLUDE_DIRECTORIES)
# Construct the compiler string for the include directories.
foreach(dir ${LIBA_INCLUDES} ${LIBB_INCLUDES})
string(APPEND INCLUDE_COMPILER_STRING "/I${dir} ")
endforeach()
Then, you can call the custom target command using the constructed INCLUDE_COMPILER_STRING variable:
add_custom_target(Bar ALL
COMMAND ${CMAKE_C_COMPILER} thing.cpp ${INCLUDE_COMPILER_STRING} /P
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Path/Here
COMMENT "Preprocessing to a file"
VERBATIM
)
If you wanted something more concise, you could use the generator expression example here, which gets the targets' include directories and expands them inline, within your custom target command. Something like this could work also:
add_custom_target(Bar ALL
COMMAND ${CMAKE_C_COMPILER} thing.cpp
"/I$<JOIN:$<TARGET_PROPERTY:LibraryA,INCLUDE_DIRECTORIES>,;/I>"
"/I$<JOIN:$<TARGET_PROPERTY:LibraryB,INCLUDE_DIRECTORIES>,;/I>"
/P
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Path/Here
COMMENT "Preprocessing to a file"
VERBATIM
COMMAND_EXPAND_LISTS
)
As the comment, the current accepted answer does not handle transitive dependencies. And this question has been confusing me all day, so I'll sort it out now.
I'm in build the LibraryLinkUtilities here. This is my CMakeLists.txt used in project:
cmake_minimum_required(VERSION 3.15.0)
project ("CMakeProject1")
set(LLU_ROOT "D:/test/LibraryLinkUtilities/install")
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
find_package(LLU NO_MODULE PATH_SUFFIXES LLU)
add_library(${PROJECT_NAME} SHARED ${PROJECT_NAME}.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE LLU::LLU)
When I open the .sln with Visual Studio, It work well, I mean I can build it in any build type. But I find the include directories is empty in Configuation. This make me crazy, because I want to know the project have include which directory exactly. Then I use the function print_target_properties fixed here to print all properties about imported target:
function(print_target_properties target)
if(NOT TARGET ${target})
message(STATUS "There is no target named '${target}'")
return()
endif()
foreach(property ${CMAKE_PROPERTY_LIST})
string(REPLACE "<CONFIG>" "DEBUG" property ${property})
get_property(was_set TARGET ${target} PROPERTY ${property} SET)
if(was_set)
get_target_property(value ${target} ${property})
message("${target} ${property} = ${value}")
endif()
endforeach()
endfunction()
print_target_properties(LLU::LLU)
Note the red line place, the LLU::LLU dependent with WSTP::WSTP and WolframLibrary::WolframLibrary. So I use this code to print all include directories:
include(CMakePrintHelpers)
get_target_property(LLUDEPENDS LLU::LLU INTERFACE_LINK_LIBRARIES)
cmake_print_properties(TARGETS LLU::LLU ${LLUDEPENDS} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES)

Get CMake to not be silent about sources it doesn't understand?

Suppose you have a very simple CMakeLists.txt
add_executable(silent T.cpp A.asm)
CMake will happily generate a C++ target for building silent, with T.cpp in it, but will silently drop any and all reference to A.asm, because it doesn't know what to do with the suffix.
Is there any way to get CMake to loudly complain about this source file it doesn't understand (to aid in porting a Makefile to CMake).
Ignoring unknown file extensions is - unfortunately for your case - by design.
If I look at the code of cmGeneratorTarget::ComputeKindedSources() anything unknown ends up to be classified as SourceKindExtra (to be added as such to generated IDE files).
So I tested a little and came up with the following script that evaluates your executable target source files for valid file extensions by overwriting add_executable() itself:
cmake_minimum_required(VERSION 3.3)
project(silent CXX)
file(WRITE T.cpp "int main() { return 0; }")
file(WRITE T.h "")
file(WRITE A.asm "")
function(add_executable _target)
_add_executable(${_target} ${ARGN})
get_property(_langs GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach(_lang IN LISTS _langs)
list(APPEND _ignore "${CMAKE_${_lang}_IGNORE_EXTENSIONS}")
endforeach()
get_target_property(_srcs ${_target} SOURCES)
foreach(_src IN LISTS _srcs)
get_source_file_property(_lang "${_src}" LANGUAGE)
get_filename_component(_ext "${_src}" EXT)
string(SUBSTRING "${_ext}" 1 -1 _ext) # remove leading dot
if (NOT _lang AND NOT _ext IN_LIST _ignore)
message(FATAL_ERROR "Target ${_target}: Unknown source file type '${_src}'")
endif()
endforeach()
endfunction()
add_executable(silent T.cpp T.h A.asm)
Since you wanted a rather loudly complain by CMake I declared it an FATAL_ERROR in this example implementation.
CMake doesn't just drop unknown files in add_executable().
If alongside with
add_executable(silent T.cpp A.asm)
you have
add_custom_command(OUTPUT A.asm COMMAND <...>
DEPENDS <dependees>)
then whenever <dependees> changed CMake will rerun command for create A.asm before compiling the executable.
Note, that automatical headers scanning doesn't provide such functionality: if your executable includes foo.h then executable will be rebuilt only when foo.h itself is changed. Any custom command creating this header will be ignored.
However, you may change behavior of add_executable by redefining it. See #Florian's answer for example of such redefinition.

Where to set CMAKE_CONFIGURATION_TYPES in a project with subprojects

Lets say I have a project with two independent subprojects. If I understood cmake correctly, the idea would be to have one root CMakeLists.txt defining a project(...) and then using add_subdirectory(...) to include the subprojects. Each subproject would have its own CMakeLists.txt defining its own project. This way projects can be build either together (using the root cmake file) or individually (using the subprojects cmake file).
I now would like to change the CMAKE_CONFIGURATION_TYPES. Should I do this in the root CMakeLists.txt or in each subproject, or both?
Changing it in the root would mean that building a subproject individually would offer the wrong configuration types; the other options would duplicate the cmake code. I think I'm missing something here.
Factorize out the code that sets up configuration-dependent settings. Create a file, say, SetUpConfigurations.cmake with this content:
if(NOT SET_UP_CONFIGURATIONS_DONE)
set(SET_UP_CONFIGURATIONS_DONE TRUE)
# No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator
# Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator.
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release;Profile" CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE)
message("Defaulting to release build.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
# set the valid options for cmake-gui drop-down list
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;Profile")
endif()
# now set up the Profile configuration
set(CMAKE_C_FLAGS_PROFILE "...")
set(CMAKE_CXX_FLAGS_PROFILE "...")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "...")
endif()
Then include(..) this file at the beginning of the CMakeLists.txt's.
You have two choices about where to put SetUpConfigurations.cmake, it depends on how you organize your projects, repositories:
The quick'n'dirty way: Copy and commit this script into each project that needs it. Its location will be fixed, relative to the CMakeLists.txt of the project. So you can include it, for example, with include(${CMAKE_CURRENT_SOURCE_DIR}/<...>/SetUpConfigurations.cmake)
The disciplined way: Maintain a repository with your custom CMake scripts, like this one. Each time you generate a project with the cmake command, you pass the path to this repository in the CMAKE_MODULE_PATH variable:
cmake -DCMAKE_MODULE_PATH=<dir-of-cmake-script-repo> ...
In this case include the script with include(SetUpConfigurations) (no .cmake extension).
A note about what a multiconfig generator is:
Xcode and Visual Studio are multiconfig generators. They respect the value of CMAKE_CONFIGURATION_TYPES but CMAKE_BUILD_TYPE has no effect since no concrete configuration is defined when the CMakeLists.txt is processed. It will be selected on the IDE's user interface later.
On the other hand, the makefile-style generators are not interested in CMAKE_CONFIGURATION_TYPES. CMAKE_BUILD_TYPE defines the configuration. It is a concrete value when the CMakeLists.txt file is processed but still: never make any decisions based on the value of CMAKE_BUILD_TYPE:
if(CMAKE_BUILD_TYPE STREQUAL "Release") # WRONG!
....
endif()
You project won't work as intended in multiconfig generators.
When use add_subdirectory into subproject dir, you propagate almost all variables into that subproject, which contradicts to "subproject independency".
Instead, it is better to build and install subproject using nested cmake call inside execute_process(). If you want to make some subproject's definitions available for top-level project, you need to "export" this definitions when subproject is installed. This question/answer post describes, how to do that.

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.