Is it possible to `unset` a library target in cmake? - cmake

Please see the below minimal example,
CMakeLists.txt
set(MY_DIR "/path/one")
add_library(third-party-lib INTERFACE IMPORTED)
set_target_properties(third-party-lib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
${MY_DIR}/usr/local/include)
add_executable(foo main.cpp)
target_link_libraries(foo third-party-lib)
add_subdirectory(bar)
bar/CMakeLists.txt
set(MY_DIR "/path/two")
add_library(third-party-lib INTERFACE IMPORTED)
set_target_properties(third-party-lib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
${MY_DIR}/usr/local/include)
add_executable(bar main.cpp)
target_link_libraries(bar third-party-lib)
I have a third party imported library that have properties that depend on a certain variable, in the case of the minimal example it's MY_DIR, I want to set this variable differently for different targets. Is this possible to achieve by "unset"-ing the imported target?
The current minimal example results in add_library cannot create imported target "third-party-lib" because another target with the same name already exists. I tried something like adding unset(third-party-lib) on top of bar/CMakeLists.txt, and I still see the same error.
To provide more context for the real situation, the third party library I need to link to is called by find_package, there are various versions of this third party library that are compiled with different compilers, but only one single config.cmake file is provided, and the IMPORTED_LOCATION has a variable in it's path that determines which version of the third party library it points to. I want to be able to compile one of my target linking to this third party library that is compiled by compiler A, and compile another one of my target linking to this third party library that is compiled by compiler B.

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.

cmake - two targets that depends on single static library that should be compiled based on the target that is being built

I have a static library and two target executables, let's call them libA, EXE1, EXE2.
libA has pre-processor macros which needs to be enabled or disabled and another static library which needs to be linked or ignored based on the target executable that I am building.
Let's say, if I am building EXE1. Then I need to enable the macros in libA and link another static library to it.
If I am building EXE2, I need to disabled the macros in libA and don't link to another library.
I am confused on how to solve this issue. Please kindly help in solving this issue.
You can make use of an interface library as follows:
cmake_minimum_required(VERSION 3.10)
project(test)
add_library(libA INTERFACE)
target_sources(libA INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/liba.c)
add_executable(exe1 exe1.c)
target_link_libraries(exe1 libA)
target_compile_definitions(exe1 PUBLIC -DENABLE_THE_MACROS)
add_executable(exe2 exe2.c)
target_link_libraries(exe2 libA libOtherStatic)
target_compile_definitions(exe1 PUBLIC -DDISABLE_THE_MACROS)
libA is a "virtual" target that does not produce any output, but it can be linked to other targets (here exe1 and exe2)
Any target that links to libA will automatically receive the sources of libA as well. Note that I had to make the path absolute to prevent a warning.

How can I create a target for an _existing_ library?

In CMake, we can add_library(mylib file1.cpp file2.cpp) and have a mylib.a in the library path get built. We can also target_include_directories(mylib INTERFACE some/directory), which effects targets depending on mylib.
But what if I have a library to begin with, which I will not be compiling. How can I add a target relating to it? That what I add as a dependency, will cause the .a file to be linked against, and for which I can set target_include_directories() ?
Note: I'm asking about CMake 3.x.
CMake provide an alternate signature for libraries that are already compiled:
add_library(
mynamespace::mylib
STATIC # or it could be SHARED
IMPORTED
)
See the official CMake documentation for more details.
with that you'll be able to add properties to the target doing so
set_target_properties(
mynamespace::mylib
PROPERTIES
IMPORTED_LOCATION "path/to/libmylib.a"
)
Little precision here, if you're using a Windows DLL, you must pass the DLL file's path in IMPORTED_LOCATION and set another property IMPORTED_IMPLIB that points to the .lib file, (not very handy).
Note that there is also a equivalent properties per configuration (Debug, and Release), that will need another property to be set (IMPORTED_CONFIGURATION), e.g. IMPORTED_LOCATION_DEBUG.
See also here and here in the documentation.
To avoid missing include files you can also precise the include directory using INTERFACE_INCLUDE_DIRECTORY property
set_target_properties(
mynamespace::mylib
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "path/to/mylib/include"
)
With this, upon link declaration using target_link_libraries(), CMake will read information of the imported target and will add include directories implicitly.
Official documentation reference.

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.

CMake ordering of include paths

I have an IMPORTED static library (say foo) that I want to link against. I'm building it within my source tree using ExternalProject_Add, and then create an IMPORTED library target for which I set the appropriate properties (INTERFACE_INCLUDE_DIRECTORIES for the headers, and IMPORTED_LOCATION for the binary).
I also have a globally installed version of the same library, whose headers are installed under /usr/local/include. Unfortunately, another package (specifically FindPackage(LibLZMA)) used by my build also has headers installed in /usr/local/include.
The pseudo-script looks like this:
FindPackage(libLZMA)
add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/lib/libfoo.a)
set_property(TARGET foo PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${foo_INCLUDE_DIRS} ${LIBLZMA_INCLUDE_DIRS})
add_executable(bar ${bar_SOURCES}
target_link_libraries(bar foo)
Now the problem is that cmake generates the following order of includes (they are all added as system includes) for bar:
/usr/local/include
foo_INCLUDE_PATH
So when building, I'm actually picking up the headers for the global installation of foo, rather than for my local, vendored version. I do however link my local version, but the two don't match, leading to runtime errors.
So, I need some way to ensure that foo_INCLUDE_PATH is searched first (or, more generally, that /usr/local/include is searched last). How can this be done?