cmake function for using a modular library with multiple targets - cmake

I develop some library which is mainly some set of interfaces to third-party packages. In my library there are core routines which need to be compiled anyway and a set of optional ones which one need to compile in case of using it in the code.
Let me illustrate it on some example.
Library
|---interfaces
| |---first
| | |---CMakeLists.txt
| | |---...
| |---second
| |---CMakeLists.txt
| |---...
|---core_routines
| |---CMakeLists.txt
| |---...
|---dependencies.cmake
This is a tree of my project. My library is a header-only. I need to write cmake function which one can use to efficiently add required submodules in its project.
Currently I use my own quite ugly solution. Namely, below you can see how my dependencies.cmake file looks like
#first interface dependencies
if(first)
link_directories(...)
include_directories(...)
add_subdirectory(${SOME_PATH}/interfaces/first
"${CMAKE_CURRENT_BINARY_DIR}/first")
endif()
#second interface dependencies
if(second)
...
endif()
#core routines
add_subdirectory(${SOME_PATH}/core_routines/
"${CMAKE_CURRENT_BINARY_DIR}/core_routines")
#function for adding required libs to new target
function(add_new_target new_target)
target_link_libraries(
${new_target} core_lib
)
if(first)
target_link_libraries(
${new_target} first_lib
)
endif()
if(second)
target_link_libraries(
...
)
endif()
endfunction(add_new_target)
Now, when I use my library in some code, the corresponding CMakeLists.txt file looks like
set(first ON)
set(second ON)
include(dependencies.cmake)
add_executable(main.exe main.cpp)
add_new_target(main.exe)
I use this way to compile the example since, as far as I know, one needs to specify link_directories and include_directories before adding new executable and then specify required libraries with target_link_libraries after it. Correct me if I am wrong.
My question is whether it is possible to write something better in this case? I need to keep modular structure of my code because, first, some interfaces require of using libraries which can be not installed on user PC, and, second, the main usage of my library will be in projects with cmake file which includes a lot of different targets, so I need to have some function which can specify required libraries and interfaces depending on user request.

link_directories() and include_directories() are not the modern way. They are too broad. By default, almost everything in modern CMake is target_* based.
Each sub-directory target should already be using target_include_directories() and target_link_libraries() with "PUBLIC" or "INTERFACE" for transitive dependencies, i.e. to carry along what is needed to successfully include and link them.
If these libraries are third party and don't already bundle their transitive dependencies, then you can do it for them using set_target_properties() with the "INTERFACE_INCLUDE_DIRECTORIES" AND "INTERFACE_LINK_LIBRARIES" properties.

Supply a list of all the libraries that client programs need to link. This would be a great deal like the builtin FindBoost module, which takes in a list of components, then prepares variables for each component, plus BOOST_LIBRARIES which is a list of all of the components.
set(my_component_libraries core_lib)
if(first)
list(APPEND my_component_libraries first_lib)
endif()
# etc.
set(my_component_libraries ${my_component_libraries} PARENT_SCOPE)
This way you leave the actual linking up to the users.
If you write your CMake interface to be find_package compatible, you can actually use the COMPONENTS interface to find_package, just like Boost.

Related

How can I avoid clashes with targets "imported" with FetchContent_MakeAvailable?

Suppose I'm writing an app, and managing its build with CMake; and I also want to use a library, mylib, via the FetchContent mechanism.
Now, my own CMakeLists.txt defines a bunch of targets, and so does mylib's CMakeLists.txt. If I were to install mylib, then find_package(mylib), I would only get its exported targets, and even those would be prefixed with mylib:: (customarily, at least). But with FetchContent, both my app's and mylib's (internal and export-intended) targets are in the "global namespace", and may clash.
So, what can I do to separate those targets - other than meticulously name all of my own app's targets defensively?
I would really like it if it were possible to somehow "shove" all the mylib targets into a namespace of my choice.
Note: Relates to: How to avoid namespace collision when using CMake FetchContent?
In the current CMake (<=3.24) world, there are no features for adjusting the names of the targets in other black-box projects, whether included via find_package, add_subdirectory, or FetchContent. Thus, for now, it is incumbent on you to avoid name-clashes in targets, install components, test names, and anywhere else this could be a problem.
Craig Scott says as much in his (very good) talk at CppCon 2019, see here: https://youtu.be/m0DwB4OvDXk?t=2186
The convention he proposes is to use names that are prefixed with SomeProj_. He doesn't suggest to literally use ${PROJECT_NAME}_, and I wouldn't either, because doing so makes the code harder to read and grep (which is extremely useful for understanding a 3rd-party build).
To be a good add_subdirectory or FetchContent citizen, however, it is not enough to simply namespace your targets as SomeProj_Target; you must also provide an ALIAS target SomeProj::Target. There are a few reasons for this:
Your imported targets from find_package will almost certainly be named SomeProj::Target. It should be possible for consumers of your library to switch between FetchContent and find_package easily, without changing other parts of their code. The ALIAS target lets you expose the same interface in both cases. This will become especially pressing when CMake 3.24 lands with its new find_package-to-FetchContent redirection features.
CMake's target_link_libraries function treats names that contain :: as target names always and will throw configure-time error if the target does not exist. Without the ::, it will be treated as a target preferentially, but will turn into a linker flag if the target doesn't exist. Thus, it is preferable to link to targets with :: in their names.
Yet, only IMPORTED and ALIAS targets may have :: in their names.
Points (2) and (3) are good enough for me to define aliases.
Unfortunately, many (most?) CMake builds are not good FetchContent citizens and will flaunt this convention. Following this convention yourself reduces the chance of integration issues between your project and any other, but obviously does nothing to prevent issues between two third party projects that might define conflicting targets. In these cases, you're just out of luck.
An example of defining a library called Target that will play nice with FetchContent:
add_library(SomeProj_Target ${sources})
add_library(SomeProj::Target ALIAS SomeProj_Target)
set_target_properties(
SomeProj_Target
PROPERTIES
EXPORT_NAME Target
OUTPUT_NAME Target # optional: makes the file libTarget.so on disk
)
install(TARGETS SomeProj_Target EXPORT SomeProj_Targets)
install(EXPORT SomeProj_Targets NAMESPACE SomeProj::)
For a more complete example that plays nice with install components, include paths, and dual shared/static import, see my blog post.
See these upstream issues to track the progress/discussion of these problems.
#22687 Project-level namespaces
#16414 Namespace support for target names in nested projects
As #AlexReinking , and, in fact, Craig Scott, suggest - there's no decent current solution.
You can follow the following CMake issues through which the solution will likely be achieved:
#22687 Project-level namespaces (more current)
#16414 Namespace support for target names in nested projects (longer discussion which influenced the above, recommended reading)

Do something for all targets

What is the best way to do additional stuff for all (binary) targets?
Examples:
I want to check that each library name follows a pattern.
I want to sign each executable.
I dont what the C/C++ developers to use nonstandard commands (like add_library2). I want them to use and learn the official CMake functions, but have them do additonal, project specific, stuff.
The built-in CMake functions add_library and add_executable can be overidden by defining CMake functions of the same name. E.g., to automatically sign all added executables add the following code:
function (add_executable _name)
_add_executable(${ARGV})
if (TARGET ${_name})
add_custom_command(TARGET ${_name} POST_BUILD
COMMAND sign_executable $<TARGET_FILE:${_name}>)
endif()
endfunction()
The original built-in add_executable can be invoked by prefixing it with an underscore character. The same pattern can be applied to add_library to check the name of the library.
You can overwrite any CMake command/function to extend its functionality, but please
Call Things by their Names
I strongly advocate to call the things by their names and not doing things implicitly. It will be easier for everybody using/maintaining/debugging your CMake based project.
If you want to sign your executable - and that's probably even platform specific - you create a function like add_post_build_step_sign_executable() which would add the appropriate post build steps:
add_executable(MyExe main.cpp)
if (WIN32)
add_post_build_step_sign_executable(MyExe)
endif()
And if you have to repeat yourself too often put that code snippet into a function like my_project_add_signed_executable() itself. It could still have the same parameter syntax as CMake's add_executable() command.
Runtime Checks vs. Static Code Analysis
Regarding library naming checks, I see this more like checking against your project's CMake script coding styles and would not use runtime checks for this.
For example you could use something like cmake-lint or your own external script to check for conformity.
References
How to frame the concept behind CMake?
cmake: get add_library() names
How to ensure consistent coding style for CMakeLists.txt and FindXXX.cmake

How to export a single header file for multiple shared libraries?

I got a top-level CMakeLists like this:
cmake_minimum_required(VERSION 3.6.2)
project(Template)
set(CMAKE_LINK_DEPENDS_NO_SHARED ON)
add_subdirectory(external/libA)
add_subdirectory(external/libB)
add_subdirectory(external/libC)
add_subdirectory(apps/app1)
add_subdirectory(apps/app2)
It's a simple project which has 3 shared libraries {libA, libB, libC} and a couple of executables {app1, app2} which use those libraries.
My question is next, I've seen CMake gives some helpers to avoid going through the hazzle of coding custom headers and messing up with .defs to export symbols with something like below:
include (GenerateExportHeader)
GENERATE_EXPORT_HEADER(libA
BASE_NAME libA
EXPORT_MACRO_NAME SHARED_EXPORT
EXPORT_FILE_NAME shared_EXPORTS.h
STATIC_DEFINE SHARED_EXPORTS_BUILT_AS_STATIC)
My question is, can I use the above snippet for all my shared libraries? If so, how and where should I place it?
You can't parameter multiple libraries with only one call. You must use GenerateExportHeader for each of your shared libraries.
So you will have one call to GENERATE_EXPORT_HEADER for libA, one for libB, one for libC, each with their own parameters.

CMake: Is it possible to build an executable from only static libraries and no source?

I would like to build an executable from static libraries (i. e. .a-files) only. This is possible, because the main() function is contained in one of these libraries.
The add_executable() function requires me to provide at least one source file. But this is not what I want to do.
There is no way to do it without a hack. You need at least one *.c or *.cpp file.
What I do is make a dummy null.cpp file (zero bytes) and use that. You can also use /dev/null but that only works on Linux.
file(WRITE null.cpp "")
add_executable(tester
null.cpp
)
target_link_libraries(tester
-Wl,--whole-archive
libtest1
libtest2
libtest3
libtest4
-Wl,--no-whole-archive
gtest_main
)
There are mainly two reasons why a source file is enforced by CMake:
To determine the LINKER_LANGUAGE from the file ending(s)
Not all compilers do support an object/library only link step (for details see below)
And if you move the main() function to library please keep the following in mind: Why does the order in which libraries are linked sometimes cause errors in GCC?
So if you build the libraries with CMake in the same project, I would recommend to change your libraries (at least the one containing your main() function) to an object library:
cmake_minimum_required(VERSION 2.8.8)
project(NoSourceForExe)
file(WRITE main.cc "int main() { return 0; }")
add_library(MyLibrary OBJECT main.cc)
add_executable(MyExecutable $<TARGET_OBJECTS:MyLibrary>)
The add_library() documentation lists a warning here:
Some native build systems may not like targets that have only object files, so consider adding at least one real source file to any target that references $<TARGET_OBJECTS:objlib>.
But those are rare and listed in Tests/ObjectLibrary/CMakeLists.txt:
# VS 6 and 7 generators do not add objects as sources so we need a
# dummy object to convince the IDE to build the targets below.
...
# Xcode does not seem to support targets without sources.
Not knowing which host OS(s) you are targeting, you may just give it a try.
References
CMake Object Lib containing main
CMake/Tutorials/Object Library

CMake : how to link a library WITHOUT automatic search function FIND_PACKAGE?

I wonder how to find/link a library without any FIND_PACKAGE.
Assume that we have a "personal" library called testlib :
/perso/testlib/include/testlib1.h
/perso/testlib/include/testlib2.h
/perso/testlib/lib/testlib1.a
/perso/testlib/lib/testlib2.a
How to link it with CMake ?
1) What are the functions to link it directly in the code of the CMakeLists.txt ?
2) How to allow the user to select where are the files ?
3) I have difficulties to understand what is interpreted and what it's not by CMake. For example if you define a variable ${MYVARIABLE_INCLUDE_DIR} or ${MYVARIABLE_LIBRARIES} is "INCLUDE_DIR" or "LIBRARIES" an extension interpreted by CMake or there is no difference if I call this variable ${MYVARIABLE_INCDIR} ?
4) How to do the same procedures (including a "personal" library) if you have a library that contains ten library files or more in the lib directory ?
5) And finally, when you type TARGET_LINK_LIBRARIES(myexecutable gmp), how do you know that the name of the library is "gmp". Why not "Gmp" or "GMP" ? Is the name of the library to put in this function just equal to the .a file minus "lib" and ".a" ? For example libgmp.a -> gmp ? If I want to link a library called libtestlolexample.a, do I have to type TARGET_LINK_LIBRARIES(myexecutable testlolexample) ?
Thank you very much.
You can use target_link_libraries(myexecutable mylib) to link to the library "mylib". The compiler will use its default way to find the specified library (e.g. it will look for libmylib.a on Linux). The compiler will only look in the link_directories(directory1 directory2 ...), so you could try that command to add the required directories to the search path.
When "mylib" is also compiled with CMake this will be recognized and everything should work automatically.
When you want the user to specify a directory you can use a cached CMake variable. set(MYPATH "NOT-DEFINED" CACHE PATH "docstring").
For more complex stuff it is very advisable to write a CMake find module that can be used with find_package. I suggest you take a look at the FindALSA.cmake which can be used as a good starting point.
The interesting part is at the end:
if(ALSA_FOUND)
set( ALSA_LIBRARIES ${ALSA_LIBRARY} )
set( ALSA_INCLUDE_DIRS ${ALSA_INCLUDE_DIR} )
endif()
mark_as_advanced(ALSA_INCLUDE_DIR ALSA_LIBRARY)
The ALSA_LIBRARY and ALSA_INCLUDE_DIR variables are user configurable and stored in the cache, while ALSA_LIBRARIES and ALSA_INCLUDE_DIRS as well as ALSA_FOUND get computed and are the ones that the user of the find module is supposed to use.
Typically one would use the find module like this:
find_package(ALSA REQUIRED)
include_directories(${ALSA_INCLUDE_DIRS})
target_link_libraries(myexe ${ALSA_LIBRARIES})
I'm sure you can adapt this for your personal library.
Usually when you want to link against a library that doesn't have a find_package module (e.g. it's an uncommon library, or it's your own library), then you can use the basic commands (the find_X commands) to set variables with the paths you need. Then you use those variables as with find_package (include_directories, target_link_libraries).
If you're going to be using this library from multiple packages, you may want to create a find_package module; basically it's using the same commands with certain conventions.
Either of these allow you to specify paths (in the CMake module) to look in, and they allow the user to override the paths (the variables show up as options in ccmake/cmake-gui).
I'd be glad to add an example of one or both of these methods, just let me know what you're looking for.
If you just want a quick-and-dirty solution, you could do this, but I wouldn't recommend it:
include_directories(/perso/testlib/include)
add_executable(myexecutable myexecutable.cpp)
target_link_libraries(myexecutable
/perso/testlib/lib/testlib1.a
/perso/testlib/lib/testlib2.a)
Regarding your question about target_link_libraries (#5), you can do it several ways. If you want you can provide the full name (e.g. target_link_libraries(myexe libfoo.a)), but I think it's better (more portable I suppose) to use the short name (e.g. target_link_libraries(myexe foo). You can also include linker flags; I'm not sure where I read it, but I think it may translate the -L and -l flags for different linkers.
For example, if I have a bunch of libraries in the same directory, and I know the names, I might find the directory, store it in a variable, and then do this:
# First, find and set TESTLIB_LIBRARY_DIR, e.g. with find_path
# ...
# This assumes the libraries are e.g. 'libtestlib1.a' and 'libtestlib2.a'
set(TESTLIB_LIBRARIES
-L${TESTLIB_LIBRARY_DIR)
-l testlib1
-l testlib2)
add_executable(myexecutable myexecutable.cpp)
target_link_libraries(myexecutable ${TESTLIB_LIBRARIES})
If you want to make your own find_package module (like trenki mentioned, FindALSA.cmake seems to be a good starting point), you can use it by adding the directory to the CMAKE_MODULE_PATH; for example, if you put your module(s) in a cmake/modules/ subdirectory:
# Look for extra CMake modules in a subdirectory of this project
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/" ${CMAKE_MODULE_PATH})
One possible issue with FindALSA.cmake: I'm not sure CMAKE_CURRENT_LIST_DIR will work. So I think you should make this change (the second work for me in a module I wrote):
# Change this line
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
# To this (with no path/extension it will search the CMake modules path):
include(FindPackageHandleStandardArgs)
And to get the usage of FIND_PACKAGE_HANDLE_STANDARD_ARGS, look at FindPackageHandleStandardArgs.cmake in the CMake Modules directory.
CMake has a good documentation.
Static linkage (if i understand you correct) is archived by passing the STATIC keyword to add_library
I would suggest to not do that (I'm not a CMake expert) but it sounds like the expense would be to large.
There is no difference, ${MYVARIABLE_INCLUDE_DIR} ist just a variable name it whatever you want. But i would suggest that you follow the naming convention.
One libary is always one .lib/.a file so there should be no problem just use the add_library& target_link_libraries& add_dependencies function.
The library name is always the name that you pass to add_library. However Gmp or gMP would be the same as CMake is case intensitive