What to do with the output of ExternalProject_add? IMPORTED vs INTERFACE libraries - cmake

This is my topmost CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(test LANGUAGES Fortran)
add_executable(main main.f90)
add_subdirectory(external)
target_link_libraries(main extlib)
Subdirectory "external" defines one external project, which produces some libraries and header files. What is the best practice of collecting the output of ExternalProject_add so that I could link that to the main executable? Currently, I'm using an INTERFACE library, like this:
include(ExternalProject)
ExternalProject_add(my-external
SOURCE_DIR ext_source
CONFIGURE_COMMAND
${CMAKE_CURRENT_LIST_DIR}/configure
--prefix=${CMAKE_CURRENT_BINARY_DIR}
BUILD_COMMAND make)
add_library(extlib INTERFACE)
add_dependencies(extlib my-external)
target_include_directories(extlib INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/include)
foreach(_lib lib1 lib2 lib3)
target_link_libraries(extlib INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/lib/${_lib}.a)
endforeach()
Searching the web, it seems to me that people often use IMPORTED libraries instead for capturing the results of ExternalProject_add. However, you can only connect one IMPORTED library with one file produced by ExternalProject_add and any header directories would need to be separately propagated back to the parent directory so that main could use them. It seems to me than an INTERFACE library is better here because you can glob everything produced by the external project into a single target. In my actual project I have several external projects and ideally I would like to have one target per external project to keep things clean.
In general, should I use IMPORTED or INTERFACE libraries when dealing with external libraries and what would be the main benefits?
EDIT
Based on the dicussion below, I made an attempt using imported libraries only:
foreach(_lib lib1 lib2 lib3)
add_library(${_lib} STATIC IMPORTED GLOBAL)
add_dependencies(${_lib} my-external)
set_target_properties(${_lib} PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/lib/${_lib}.a)
endforeach()
set_target_properties(lib1 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/include)
target_link_libraries(lib1 INTERFACE lib2 lib3)
It takes about the same effort as with an interface library and I guess is ultimately a matter of style.

Related

Including headers in Visual Studio project via TARGET_INCLUDE_DIRECTORIES? [duplicate]

I had a project which uses CMake as build tool and made a simple template for me and my collegues to use. As I searched for best and easy to use practices online, I've came across different approaches to make a library.
In this template, I've listed header files and source files in two seperate variables, and I'm not passing the headers to add_library command - just sources. And then I use set_target_properties with PUBLIC_HEADER variable to give the header-file list.
So far it seems to work, but I wonder if I'm making thing unnecessarily complex. Some people online give header files to add_library command as well and doesn't even use set_target_properties and such.
In short:
should we include header files to add_library or should we not (as a best practice)? And impacts of the two usage.
what is purpose being served by adding headers in the add_library/add_executable? As they seem working even without it (seems forward declaration and symbols only). confirm on understanding please.
(Here is the template I'm talking about:)
cmake_minimum_required(VERSION 3.1.0)
project(lae CXX C)
set(CMAKE_CXX_STANDARD 14)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)
set(SOURCE_FILES
...
)
set(HEADER_FILES
...
)
set( PRIVATE_HEADER_FILES
...
)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} )
set( REQUIRED_LIBRARIES
...
)
target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBRARIES} )
SET_TARGET_PROPERTIES(
${PROJECT_NAME}
PROPERTIES
FRAMEWORK ON
SOVERSION 0
VERSION 0.1.0
PUBLIC_HEADER "${HEADER_FILES}"
PRIVATE_HEADER "${PRIVATE_HEADER_FILES}"
ARCHIVE_OUTPUT_DIRECTORY "lib"
LIBRARY_OUTPUT_DIRECTORY "lib"
OUTPUT_NAME ${PROJECT_NAME}
)
In our projects we use a "simple" way of yours - add_library with both headers and sources.
If you add only sources, then you won't see headers in IDE-generated project.
However, when installing, we have to do it like that, using two install commands:
install(TARGETS library_name
LIBRARY DESTINATION lib)
install(FILES ${PUBLIC_HEADERS}
DESTINATION include/library_name)
If you want to do it as a single command, you can use set_target_properties with PUBLIC_HEADER, as you suggested.
Then, this kind of install is possible:
install(TARGETS library_name
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include/library_name)
Choose the one you like the most and stick to it.

Dependency between two subdirectories in CMake

In my root CMakeLists.txt, I have:
add_subdirectory(libs)
add_subdirectory(main)
In libs, I have my own CMakeLists.txt to build external projects.
In main, there is a CMakeLists.txt from another repository, which I do not control.
To build main, libs needs to be built. I do not know how to specify the
dependency between main and libs.
In libs, with my external projects lib1 and lib2, I used add_dependencies(lib1 lib2) and I have lib2 built before lib1. I did not find how to do that for
main and libs.
The difficulty for me is I have to mix external projects and subdirectories, and I did not find any answer or was not able to adapt them.
I converted add_subdirectory(main) into an external project. Since it is not possible to make a dependency on subdirectories, I use directly the inner targets. With all that I got:
include(ExternalProject)
add_subdirectory(libs)
ExternalProject_Add(main
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/main
...
)
add_dependencies(main lib1 lib2)

CMake: link order of imported targets incorrect

I have the following scenario:
I import two prebuilt libraries into my project (libA, libB)
libB has a dependency on libA
The executable depends on both libA and libB
However, the relative linking order in my link.txt is incorrect
/usr/bin/c++ CMakeFiles/bin.dir/main.cpp.o -o bin ../libA.a ../libB.a
I would expect libA.a to be listed after libB.a.
The CMakeLists.txt looks something along the following lines
cmake_minimum_required(VERSION 3.13)
project(cmake_test)
set(lib_dir ${CMAKE_CURRENT_SOURCE_DIR})
add_library(MY::libA IMPORTED INTERFACE)
set_target_properties(MY::libA PROPERTIES INTERFACE_LINK_LIBRARIES "${lib_dir}/libA.a")
add_library(MY::libB IMPORTED INTERFACE)
set_target_properties(MY::libB PROPERTIES INTERFACE_LINK_LIBRARIES "MY::libA;${lib_dir}/libB.a")
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC MY::libB MY::libA)
Below a description of my attempts to solve the problem. Some without success and some with sucecss but using modifications that render the code usless for the production environment.
Successful attempts:
Remove the depedency of bin on libA (i.e. replace the last line by target_link_libraries(bin PUBLIC MY::libB). This works but I cannot remove the dependency in real code.
Replace the target type IMPORTED INTERFACE by IMPORTED STATIC. Use IMPORTED_LOCATION instead of INTERFACE_LINK_LIBRARIES and use target_link_libraries to express the dependency of libB on libA. In this case the link.txt yields: [...] -o bin ../libA.a ../libB.a ../libA.a. As soon as I revert the target type for libB the link order breaks down again. In the production environment, however, one of the targets is created by conan as IMPORTED INTERFACE.
Attempts without success (same behaviour as described):
Create a separate IMPORTED target (use IMPORTED_LOCATION) for every lib and group them inside an INTERFACE target
Sprinkle the code with ADD_DEPENDENCIES
Remove libA from the INTERFACE_LINK_LIBRARIES in line 9 and use target_link_libraries(MY::libB INTERFACE MY::libA) instead. Same result.
Example code that shows the same failure using INTERFACES as a building block
cmake_minimum_required(VERSION 3.13)
project(cmake_test)
set(lib_dir ${CMAKE_CURRENT_SOURCE_DIR})
# libA
add_library(MY::libA_file1 IMPORTED STATIC)
set_target_properties(MY::libA_file1 PROPERTIES IMPORTED_LOCATION "${lib_dir}/libA.a")
add_library(libA INTERFACE)
target_link_libraries(libA INTERFACE MY::libA_file1)
# libB
add_library(MY::libB_file1 IMPORTED STATIC)
set_target_properties(MY::libB_file1 PROPERTIES IMPORTED_LOCATION "${lib_dir}/libB.a")
add_library(libB INTERFACE)
target_link_libraries(libB INTERFACE MY::libB_file1 libA)
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC libA libB)
You incorrectly think about INTERFACE_LINK_LIBRARIES property as a "content" of the library's target, which is ordered by target_link_libraries call.
Using
target_link_libraries(MY::libB INTERFACE MY::libA)
you setup link dependency between library targets MY::libB and MY::libA. That is, "content" of MY::libB target should come before "content" of MY::libA target in the linking command line.
But INTERFACE_LINK_LIBRARIES property is NOT a "content" of the library target! It is just an additional link dependency.
As opposite, IMPORTED_LOCATION (for non-INTERFACE IMPORTED target) is a "content" of the library, and target_link_libraries affects on its ordering.
It seems that you cannot add link dependency for a library, using INTERFACE library target. You should use IMPORTED library target for that purpose:
# Collect libraries related to 'libA'
file(GLOB libs_A "${lib_dir}/libA*.a")
# For each library create IMPORTED target with IMPORTED_LOCATION property.
set(libs_A_targets)
foreach(lib_A ${libs_A})
# Form a unique name for the IMPORTED target: subtarget_A_*
string(REGEX REPLACE "^${lib_dir}/libA([^.]*).a$" "subtarget_A_\\1" lib_A_target ${lib_A})
# Create a target with this name
add_library(${lib_A_target} STATIC IMPORTED)
set_target_properties(${lib_A_target} PROPERTIES IMPORTED_LOCATION ${lib_A})
# And add the target into the list
list(APPEND libs_A_targets ${lib_A_target})
endforeach()
# In a similar way collect libraries for libB.
set(lib_B_targets ...)
# Now link each libB* library with each libA*.
foreach(lib_B_target ${libs_B_targets})
target_link_libraries(${lib_B_target} INTERFACE ${libs_A_targets})
endforeach()
# Now interface libraries, which combine libA* and libB*, can be created
add_library(libA INTERFACE)
target_link_libraries(libA INTERFACE ${libs_A_targets})
add_library(libB INTERFACE)
target_link_libraries(libB INTERFACE ${libs_B_targets})
# Now these INTERFACE libraries can be linked into an executable in any order
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC libA libB)

Why add header files into ADD_LIBRARY/ADD_EXECUTABLE command in CMake

I had a project which uses CMake as build tool and made a simple template for me and my collegues to use. As I searched for best and easy to use practices online, I've came across different approaches to make a library.
In this template, I've listed header files and source files in two seperate variables, and I'm not passing the headers to add_library command - just sources. And then I use set_target_properties with PUBLIC_HEADER variable to give the header-file list.
So far it seems to work, but I wonder if I'm making thing unnecessarily complex. Some people online give header files to add_library command as well and doesn't even use set_target_properties and such.
In short:
should we include header files to add_library or should we not (as a best practice)? And impacts of the two usage.
what is purpose being served by adding headers in the add_library/add_executable? As they seem working even without it (seems forward declaration and symbols only). confirm on understanding please.
(Here is the template I'm talking about:)
cmake_minimum_required(VERSION 3.1.0)
project(lae CXX C)
set(CMAKE_CXX_STANDARD 14)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)
set(SOURCE_FILES
...
)
set(HEADER_FILES
...
)
set( PRIVATE_HEADER_FILES
...
)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} )
set( REQUIRED_LIBRARIES
...
)
target_link_libraries(${PROJECT_NAME} ${REQUIRED_LIBRARIES} )
SET_TARGET_PROPERTIES(
${PROJECT_NAME}
PROPERTIES
FRAMEWORK ON
SOVERSION 0
VERSION 0.1.0
PUBLIC_HEADER "${HEADER_FILES}"
PRIVATE_HEADER "${PRIVATE_HEADER_FILES}"
ARCHIVE_OUTPUT_DIRECTORY "lib"
LIBRARY_OUTPUT_DIRECTORY "lib"
OUTPUT_NAME ${PROJECT_NAME}
)
In our projects we use a "simple" way of yours - add_library with both headers and sources.
If you add only sources, then you won't see headers in IDE-generated project.
However, when installing, we have to do it like that, using two install commands:
install(TARGETS library_name
LIBRARY DESTINATION lib)
install(FILES ${PUBLIC_HEADERS}
DESTINATION include/library_name)
If you want to do it as a single command, you can use set_target_properties with PUBLIC_HEADER, as you suggested.
Then, this kind of install is possible:
install(TARGETS library_name
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include/library_name)
Choose the one you like the most and stick to it.

Overlapping dependencies between libraries in CMake

Let's say there's the following directory structure:
projects
|
+--lib1
| |
| +-CMakeFiles.txt
|
+--lib2
| |
| +-CMakeFiles.txt
|
+--test
|
+-CMakeFiles.txt
lib1/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
add_library(lib1 STATIC lib1.cpp)
lib2/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
add_subdirectory(../lib1 ${CMAKE_CURRENT_BINARY_DIR}/lib1)
add_library(lib2 STATIC lib2.cpp)
target_link_libraries(lib2 lib1)
test/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
project(test)
add_subdirectory(../lib1 ${CMAKE_CURRENT_BINARY_DIR}/lib1)
add_subdirectory(../lib2 ${CMAKE_CURRENT_BINARY_DIR}/lib2)
add_executable(test main.cpp)
target_link_libraries(test lib1 lib2)
I.e. lib2 depends on lib1 and test depends on both of them. (I know that technically static libraries don't "link", but that's just an example.)
The problem is that with the current setup, lib1 compiles twice - the first time it is within the "test" build directory, and a second time it is within "test/build_directory/lib2/build_directory". I'd like to avoid that.
I want to be able to add a dependency on lib1, lib2 or both of them (using add_subdirectory) to any project that's located elsewhere. So moving CMakeFiles isn't an option. I also want to avoid compiling any library several times.
How can I do that?
Platform: CMake v. 2.8.4 and Windows XP SP3
A top-level CMakeLists.txt file isn't an option, because I want to keep a clean top-level directory and be able to include libraries in other projects that can be located elsewhere. Because it is Windows, I can't "install package system-wide" - I don't want to lose ability to switch compiler on the fly. Utility libraries built with different compilers will use different C runtime libraries/ABI, and as a result will be incompatible.
One other solution is to add a guard at the top of the subdirectory-CMakeLists.txt:
if(TARGET targetname)
return()
endif(TARGET targetname)
Which will cause cmake to do nothing the second time the subdirectory is added (if targetname is being defined in that file, of course).
This will lead to the lib beeing build in an sort-of-arbitrary place (depending on which module added it first) in the build/ tree, but it will be built only once and linked everywhere.
In your example, you would add
if(TARGET lib1)
return()
endif(TARGET lib1)
at the top of lib1/CMakeFiles.txt
With CMake, library dependencies are transitive, so you shouldn't call add_subdirectory twice in test/CMakeFiles.txt (nor do you need to list lib1 as a dependency of test since it is already a dependency of lib2's).
So you could modify test's CMakeFiles.txt to:
cmake_minimum_required(VERSION 2.8.7) # Prefer the most current version possible
project(test)
add_subdirectory(../lib2 ${CMAKE_CURRENT_BINARY_DIR}/lib2)
add_executable(test main.cpp)
target_link_libraries(test lib2)
Also, you should probably remove the cmake_minimum_required calls from your non-project CMakeFiles.txt files (the lib ones). For further info, run:
cmake --help-policy CMP0000
This setup will still cause all libs to be recompiled if you add a similar test2 subdirectory and project which depends on lib1 and lib2. If you really don't want to have a top-level CMakeFiles.txt in projects/, then you're stuck with what you're doing, or you could use either the export or install command.
export would create a file which could be included by other projects and which imports the targets into the project which calls include.
install could install the libraries to another common subdirectory of projects/. Depending on your source directory structure, this could have the benefit of only making the intended library API headers available to dependent projects.
However, both of these options require the dependent library projects to be rebuilt (and installed) if they are modified, whereas your current setup includes all the dependent targets in your project, so any change to a source file in a dependent library will cause your test target to go out of date.
For further details about export and install, run:
cmake --help-command export
cmake --help-command install
Perhaps add a top-level CMakeLists.txt in your projects dir. Something like:
project( YourProjects )
add_subdirectory( lib1 )
add_subdirectory( lib2 )
add_subdirectory( test )
This should be sufficient and will give you a solution-file or makefile in your top-level build-dir. You should then remove the add_subdirectory( ../lib1 ... from your lib1 and lib2 projects, but instead simply link to them. CMake will know how to find lib1 and lib2 when compiling test.
I.e. in lib2:
project( lib2)
add_library(lib2 STATIC lib2.cpp)
target_link_libraries(lib2 lib1)
and in test:
project( test )
add_executable(test main.cpp)
target_link_libraries(test lib1 lib2)
Added bonus: you will get makefiles/solutionfiles for building lib2 (with dependent lib1) in the lib2 directory...