What are the differences between IMPORTED target and INTERFACE libraries? - cmake

As I understand it INTERFACE libraries are like Visual Studio property sheets, so very useful. We can use it to link with static libs and propagate properties.
But IMPORTED targets bother me : I can't see a problem which can be solved with IMPORTED target only.

When you create an imported target, you're telling CMake: I have this { static library | shared library | module library | executable } already built in this location on disk. I want to be able to treat it just like a target built by my own buildsystem, so take note that when I say ImportedTargetName, it should refer to that binary on disk (with the associated import lib if applicable, and so on).
When you create an interface library, you're telling CMake: I have this set of properties (include directories etc.) which clients can use, so if they "link" to my interface library, please propagate these properties to them.
The fundamental difference is that interface libraries are not backed by anything on disk, they're just a set of requirements/properties. You can set the INTERFACE_LINK_LIBRARIES property on an interface library if you really want to, but that's not really what they were designed for. They're to encapsulate client-consumable properties, and make sense primarily for things like header-only libraries in C++.
Also notice that an interface library is a library—there's no such thing as an interface executable, but you can indeed have imported executables. E.g. a package config file for Bison could define an imported target for the Bison executable, and your project could then use it for custom commands:
# In Bison package config file:
add_executable(Bison IMPORTED)
set_property(TARGET Bison PROPERTY IMPORTED_LOCATION ...)
# In your project:
find_package(Bison)
add_custom_command(
OUTPUT parser.c
COMMAND Bison tab.y -o parser.c
DEPENDS tab.y
...
)
(Bison is used just as an example of something you might want to use in custom commands, and the command line is probably not right for it).

It seems like there is a lot of overlap. Say you have a shared library and headers on disk and you want to make it available so that bits of your CMake can do this
target_link_libraries(my_target foo)
and automatically link with it and get the necessary include directories.
You can do it either like this:
find_package(Foo)
add_library(foo SHARED IMPORTED)
set_target_properties(foo PROPERTIES
IMPORTED_LOCATION ${FOO_LIBRARIES} # The DLL, .so or .dylib
INTERFACE_INCLUDE_DIRECTORIES ${FOO_INCLUDE_DIR}
INTERFACE_COMPILE_DEFINITIONS "ENABLE_FOO"
)
Or like this:
add_library(foo INTERFACE)
target_link_libraries(foo INTERFACE ${FOO_LIBRARIES})
target_include_directories(foo INTERFACE ${FOO_INCLUDE_DIR})
target_compile_definitions(foo INTERFACE "-DENABLE_FOO")
They both work and behave identically as far as I can tell. There is even an 'imported interface library' available via add_library(foo INTERFACE IMPORTED) though that didn't seem to work and I have no idea what it is for.
Frankly the docs don't really explain which you should use for libraries, and I'm afraid I don't find Angew's "that's not really what they were designed for" very compelling.
I guess use either. I think the interface library is easier to understand and more consistent with the use of INTERFACE properties from internal libraries.

Related

How is CMake supposed to work for shared libraries?

I am trying to figure out how CMake is supposed to work for shared libraries. I create a shared library like so:
(in /mdp_opt/CMakeLists.txt:)
add_library (mdp_opt SHARED librarycomponent.cpp)
Now, a test executable uses this library:
(/test/CMakeLists.txt:)
add_executable (test test.cpp)
target_link_libraries(test PRIVATE mdp_opt)
If the library is marked STATIC (instead of SHARED as above), I can cmake -> built (under Visual Studio) and successfully run the executable. When it is marked SHARED (as in above), I need to do two things. First thing, is adding:
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
That is fine. However, now it still only works if I copy the file mdp_opt.dll from build/x64-debug/mdp_opt to build/x64-debug/test. But I don’t understand why this is needed?
In the book “professional CMake”, I read:
LINK_LIBRARIES
This target property holds a list of all libraries the target should
link to directly. It is initially empty when the target is created and
it supports generator expressions. An associated interface property
INTERFACE_LINK_LIBRARIES is supported. Each library listed can be one
of the following (emphasis mine):
• A path to a library, usually specified as an absolute path.
• Just the library name without a path, usually also without any
platform-specific file name prefix (e.g. lib) or suffix (e.g. .a, .so,
.dll).
• The name of a CMake library target. CMake will convert this to a
path to the built library when generating the linker command,
including supplying any prefix or suffix to the file name as
appropriate for the platform. Because CMake handles all the various
platform differences and paths on the project’s behalf, using a CMake
target name is generally the preferred method.
I was under the impression that
target_link_libraries(test PRIVATE mdp_opt)
expresses that I intend to link the output associated with the target mdp_opt with the test executable? Also, in my reading of the above book excerpt, my understanding is that the location of the .dll will convert to a path? If the purpose of this conversion is not to somehow tell the executable where to find the shared library, then what is that conversion for?
Basically, can anybody tell me how CMake is supposed to work for shared libraries, and why is works like that? Is a manual post-copy (possibly via CMake instructions) really needed, and the best for this scenario (of intermediate builds while developing)?
On windows you need to export symbols of a shared library or add a linker option that results in symbols being exported; otherwise the .lib file for linking the dll simply isn't generated. This is why you need
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
, use __declspec( dllexport ) or similar.
The other issue is the way windows locates dlls: It basically first searches the directory containing the executable and then continues the search via the PATH environment variable. This can result in issues locating the dll at runtime.
You can avoid this issue by setting the directory to place the runtime artefacts before generating the first one of the targets involved:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
...
add_library(mdp_opt ...)
...
add_executable(test ...)
Alternatively you could simply change the environment variable visible to the vs debugger:
set_target_properties(test PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$<TARGET_FILE_DIR:mdp_opt>;$ENV{PATH}")
If you're working with CTest, there's also a similar property for tests: ENVIRONMENT
If the purpose of [target_link_libraryes] is not to somehow tell the executable where to find the shared library, then what is that conversion for?
You still need to link a dll, or more precisely the corresponding import library; this is what target_link_libraries does, in addition to adding build system dependencies, transferring some properties (e.g. include directories with PUBLIC visibility), ect.

Is there a declarative way to copy dlls using CMake?

Existing solutions suggest using add_custom_command, and that works, but I consider it ugly and error prone, so I wonder if there is a way when I declare my 3rdparty library to say that any executable using my library should have dll copied to where executable is built. Obviously support for different dlls in Release/Debug is also necessary.
You are clearly on Windows since you are building dll's. But on Linux I would just comment that one can set the RPATH for a binary. In Cmake this looks something like
set_target_properties(target PROPERTIES INSTALL_RPATH ${path_to_libs})
# Maybe also this line...
target_link_libraries(target PUBLIC -Wl,--disable-new-dtags)
I have no idea what effect this has in Windows. However, in Linux this basically tells the binary where to look for libraries that it will need to link to, thus avoiding the need to either copy the libraries to the same directory or to copy the libraries to the standard library path.
If you have a target A in Cmake, and you either export that target, or include it in another Cmake project as a subdirectory (via add_subdirectory), then you could easily just bundle all of the shared libraries together into the target like this:
CMakeLists.txt for target A:
add_library(A ... )
target_link_libraries(A PUBLIC other_libs_needed_by_consumers_of_A)
CMakeLists.txt for target B:
target_link_libraries(B PUBLIC A)

Why use add_library({tgt} IMPORTED) versus target_link_libraries( -l {.so | .a})?

What is the purpose of using the statement:
add_library(<tgt> [SHARED|STATIC] IMPORTED)
From what I have found even if you create an imported library target above you still would need to specify the specific location of the actual .so or .a. This would take at least 3 cmake commands to link to an executable and the compiler still would not automatically search through the common include directories on your OS.
Example:
cmake code to link IMPORTED lib
From the CMake documentation I understand there are really 3 ways to link a library that is not built as a target in a subproject of your overall application/library.
CMake target_link_libraries() documentation
Using a CMake package for one of the shipped package scripts.
Using a linker flag:
target_link_libraries(<tgt> [SHARED|STATIC|...] -lncursesw)
Or using the IMPORTED library method (showcased in code at top).
A major difference when using the second method is that it only takes a single line of code and will search through all of your compiler's predefined include directories on you OS. Could anyone help me understand why the add_library() method is used?
Additional Realated SO Posts:
Include directories for IMPORTED libs
CMake imported library behavior
You should use add_library(<tgt> [SHARED|STATIC] IMPORTED) whenever you need to set properties such as dependencies, compile definitions, compile flags etc for <tgt>, and/or by extension, any targets that are linking against <tgt>.
Let's say you have two static libraries; libfoobar.a and libraboof.a, where libfoobar.a requires libraboof.a. Let's also say that these libraries contain some features that are enabled by -DSOME_FEATURE.
add_library(raboof STATIC IMPORTED)
set_target_properties(raboof PROPERTIES
IMPORTED_LOCATION <path-to-libraboof.a>
INTERFACE_COMPILE_DEFINITIONS "SOME_FEATURE"
)
add_library(foobar STATIC IMPORTED)
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION <path-to-libfoobar.a>
INTERFACE_LINK_LIBRARIES raboof
)
So when you link against libfoobar.a:
add_executable(my_app main.cpp)
target_link_libraries(my_app foobar)
CMake will make sure to link all dependencies in the correct order and will in this case also append -DSOME_FEATURE to the compile flags when you build my_app. Note that since we added libraboof.a as a dependency to libfoobar.a, -DSOME_FEATURE is added to any target that link against libfoobar.a through the transitive property.
If you don't use add_library(<tgt> <SHARED|STATIC> IMPORTED) in a scenario like this, you would have to manage any dependencies and required build options yourself for each target, which is quite error-prone.
This method is also often used in Config-modules for multi-component libraries to manage dependencies between the components.

Which cmake property should hold privately linked shared libraries for imported targets?

Consider importing a target with find_package( foo REQUIRED ) that provides a shared library. Target foo itself depends on another shared library bar.dll, which is only used in its implementation and not visible in its headers. But as a client of foo I also have to deploy bar.dll in order to run my code.
So which property of the imported target foo should give me the information that I also have to deploy bar?
Should it be in LINK_INTERFACE_LIBRARIES, IMPORTED_LINK_INTERFACE_LIBRARIES, LINK_LIBRARIES, IMPORTED_LINK_DEPENDENT_LIBRARIES or something else?
Thank you for your time.
Assuming you're on a "modern" version of cmake (3.anything), then you are looking for IMPORTED_LINK_DEPENDENT_LIBRARIES.
LINK_INTERFACE_LIBRARIES and IMPORTED_LINK_INTERFACE_LIBRARIES are both deprecated, so don't consider them.
LINK_LIBRARIES is for building a target; since you're importing a library with find_package, it shouldn't be of much use.
INTERFACE_LINK_LIBRARIES, on an imported target, tells you which libraries are dependencies in the interface (visible in the headers).

CMake: header-only library with generated files

I have a library that needs to carry some constant data injected from the content of non-source files (in this case, OpenGL shader code). To achieve this, I'm using add_custom_command() to generate include files that I can then #include into my code to initialize const static variables.
This works perfectly with regular libraries (static or shared), but now I'd like to make my library header-only. The ability of C++ to let static methods return static data without running the risk of having that data duplicated in each translation unit ("magic statics") makes this possible.
The problem however is that CMake seems to assume that an INTERFACE library (which is the CMake feature that I'm using to create header-only libraries) does not need building - which, in this case, is wrong.
(I realize that there is no actual obligation for my library to be header-only. In this particular case, the reason I want this is that I would like the library, which is doing OpenGL, to remain independent of any specific binding library [such as GLEW or GLee or the newcomer glbinding]. By keeping my library header-only, I can leave that choice to the user - all he needs to do is #include the header of the binding library before mine.)
Does anyone see a way to have CMake trigger the header-generating custom commands, at the latest when the consumer project is being built?
EDIT: I just realized that I could have the "best of both worlds" as it were by keeping my library static but still keeping all my code except for the constant data in the header files. That way, there would still be no need to choose a specific OpenGL binding library.
However, there are still advantages to having a library be header-only - simplicity of use for one - so I'm leaving my question open.
EDIT #2: Here is the relevant part of my CMakeLists.txt file (I only stripped the library dependencies - all header-only - from the end):
set(SHADER_FILES "src/vertex.glsl" "src/fragment.glsl")
add_library(libGPCGUIGLRenderer INTERFACE)
target_sources(libGPCGUIGLRenderer INTERFACE ${SHADER_FILES})
target_include_directories(libGPCGUIGLRenderer BEFORE
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Embed shader files
source_group("Shader files" FILES ${SHADER_FILES})
set(GENERATED "${CMAKE_CURRENT_BINARY_DIR}/generated")
target_include_directories(libGPCGUIGLRenderer INTERFACE ${GENERATED})
# Find the GPC Bin2C utility
find_package(GPCBin2C REQUIRED)
# Add a custom target and a dependency for each shader file
foreach(shader ${SHADER_FILES})
get_filename_component(name "${shader}" NAME)
set(shader_header "${GENERATED}/${name}.h")
add_custom_command(
OUTPUT ${shader_header}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${shader}
COMMAND GPCBin2C --input=${CMAKE_CURRENT_SOURCE_DIR}/${shader} --output=${shader_header}
)
target_sources(libGPCGUIGLRenderer INTERFACE ${shader_header})
endforeach()
Creating a static library with headers as the only sources worked for me. It is, of course, only a work-around.
Creating a static library with only header files results in an empty library. Mine says !<arch> as the only content.
CMake will automatically get the dependencies correct across sub-directories.
Since all sources are headers, you need to tell CMake which linker language should be used.
Code:
set(OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/generated_include")
add_custom_command(
OUTPUT "${OUTDIR}/outfile.h"
# Replace the next two lines with a proper generating script.
COMMAND mkdir -p ${OUTDIR}
COMMAND touch ${OUTDIR}/outfile.h
)
# Note, I am only adding header files to the library.
add_library(generated-headers STATIC
"${OUTDIR}/outfile.h"
)
set_target_properties(generated-headers
PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(generated-headers PUBLIC ${OUTDIR})
Use in other directories like this:
# In any other directory of the same CMake project:
add_executable(main main.cpp)
target_link_libraries(main generated-headers)
Tested on CMake 3.2, 3.8 and 3.9. Using Ninja and Make generators.
You can use target_sources in CMake 3.1 to tell consumers to compile interface files:
add_library(source_only INTERFACE)
target_sources(source_only INTERFACE foo.cpp)
http://www.cmake.org/cmake/help/v3.1/command/target_sources.html
I ran into comparable problems when trying to use glad: https://github.com/Dav1dde/glad
It uses a custom CMake command to build a binding, which means the files you need to include in the project which uses glad do not exist, so that CMake does not build glad (which would create those files)...
I did not get to try it yet, but example 3 of the following link seems to be a good solution and I believe it may work in your case:
https://samthursfield.wordpress.com/2015/11/21/cmake-dependencies-between-targets-and-files-and-custom-commands/