Cmake: Override subdirectory link mode to LINK_PRIVATE - cmake

I have a pretty big 3rd party cmake directory as a part of my project that some of my projects depend on. I import this directory into my dependent projects using add_subdirectory(). Unfortunately, this also imports the libraries that the 3rd party project links to into my projects.
I was able to manually fix this by specifying LINK_PRIVATE in the cmakelists.txt file of the 3rd party directory for the target_link_libraries() command. I would much prefer to do it remotely from within cmakelists using set_property or similar.
Is this possible?

In general, when using add_subdirectory such effects are hard to contain. Apart from the build targets, you may also get similar pollution effects on global and cache variables, tests and other places, which is why I would not recommend this approach for third library dependencies.
A cleaner approach is provided by the ExternalProject module. This gives you a command ExternalProject_Add that can be used to configure and build a third party library with CMake (or other build systems). The advantage here is that the library's CMake run is completely independent of your own, so there are no pollution effects. The disadvantage is that no targets from that library get imported automatically into your own project, so you might need some additional glue code to get them back in. Still, overall this should be a much cleaner approach.

Related

Should I supply external libraries with a CMakeLists.txt or supply find_packages instead?

I am working on a project that needs some external libraries. Since it is meant to be cross platform, I am using cmake.
What is the preferred way when distributing such projects? Should I supply the external libraries (such as zlib) with their own CMakeLists.txt or should I signal the dependency by simply supplying find_packages()?
the former provides all things needed. while the latter let's the developer decide how to supply the dependency (vcpkg for example)
Althoug there is no universally preferred approach, I absolutely believe you should stick to find_package. Declare your dependencies like this:
find_package(Pkg [version] REQUIRED [components])
Include [version] and [components] only if you know Pkg itself provides first-party CMake package configuration files. If you are writing and distributing a library, you will include equivalent find_dependency calls in your MyProjConfig.cmake file.
If some dependency does not have a standard CMake find module or provide its own CMake package configuration file, you should write your own in ./cmake and add list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") to the root CMakeLists.txt, before any find_package call. You will install your find modules, too, and include the same addition to the module path in your config files.
Inside the find module, you can use whatever approach you want to create some imported targets for your dependencies. Using PkgConfig is a good approach here.
Going through find_package instantly works with a number of dependency providers: vcpkg, the cmake_paths Conan generator, Linux distro system packages, and so on.
The primary alternative to doing this is to vendor the code, meaning including your dependencies in your build directly, whether through copy/paste into your source tree, a git submodule, or by build-time download from the internet (FetchContent).
The mechanism used to build these is nearly always add_subdirectory in the end, which pulls your dependencies' CMake builds into yours.
Perhaps the biggest issue with this is that most projects' CMake code is totally unprepared to be used in this way. It might trample your cache variables, inject invalid flags into your targets, overwrite your generated headers, and so on. Integration is a nightmare.
Also, from a software distribution standpoint, doing this ties your code to particular versions of your dependencies and takes control away from others who might want to package your code. For instance, Debian packages are not allowed to bundle their dependencies... if libA depends on libB, then each gets its own package. With find_package, it is trivial for a maintainer to inject the appropriate dependencies into your build. Without, it typically involves a difficult-to-maintain patch.

Where should find_package be called in CMakeLists.txt?

Including external libraries in a cmake project is usually performed using find_package().
But in a large multi-application / multi-library project, it is typical for some 3rd-party and/or system libraries to be used by multiple applications and libraries.
Where should find_package() for these common libraries be called?
In the CMakeLists.txt file for each executable/library that needs them?
Or, once in a top-level CMakeLists.txt file?
The first options seems to be a more modular approach, but the associated find_package() scripts are executed for each library/executable that uses them. This can slow down the configuration step.
The second option is more efficient, but looks a bit too much like a global variable to me.
I would distinguish between subprojects/-directories that might be standalone packages on their own (or already are) and those that exclusively reflect the source code structure.
In the first case, find_package should clearly be in the subdirectory CMakeLists.txt to allow for extracting the subdirectory for a standalone build or distribution. Inefficiency of the configuration step should not be a concern here, as the configuration of a build is not performed that often.
In the second case, I would prefer find_package calls in the toplevel CMakeLists.txt. If you don't use the variables set by these calls to populate global variables but use target_link_libraries instead (as suggested in the comments), this should be clean enough. Sometimes though, found packages do not export proper targets to link against, but rather define some include path variables and the like. Then, it should be easy to define your own INTERFACE library that encapsulate these flags/paths as usage requirements and propagate them transitively when linked via target_link_libraries again.

How to make a CMake package?

I'm attempting to make a CMake package for Crypto++ inclusion in CMake projects, this will end up in the noloader/cryptopp-cmake repo if it gets done.
The ultimate goal is to come up with a working cross-platform FindCryptoPP.cmake file which can be dropped in the Crypto++ source directory to do things like:
find_package(CryptoPP REQUIRED)
target_link_libraries(libbiocoin cryptopp-static)
Or:
find_package(CryptoPP REQUIRED)
target_link_libraries(libbiocoin cryptopp-shared)
In a finished application and have it "just work."
My current best solution within a CMake application is to build Crypto++ for the platform, stick the resulting archive or library in a lib directory, reference that within the CMakeLists.txt and pull it in that way, but of course that requires packaging a binary distribution of the compiled Crypto++ for every platform targeted by the application, which would be nasty to maintain and generally bad even if it weren't crypto code.
It's better to provide a CMake configuration file. find_package will look for a configuration file if no FindFoo.cmake find script is provided. One advantage over a find script is that you won't end with different, maybe conflicting versions of the find script.
See https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html, especially the section Create Layout.

How to include external libraries in CMakeLists?

I have been trying to build my project with CMakeLists for a couple of days now with some success but I'm not sure on what it is exactly that I am doing.
I am currently using 5 external libraries. (Assimp, glew, glfw, glm, DevIL)
And my main platform is windows but platform independence is a big plus.
So the question I have is how to proceed when I want to include an external library to my project?
This is what I think I know so far.
(1) If you are "lucky" you can just use find_library and link it with target_link_libraries you are good to go.
(2) Since glm is a header only library, all I need is an include_directories.
(3) If the external library has its own CMakeLists, you do add_subdirectory and set include_directories to where the header files are.
This is what I know I don't understand.
(4) If the external library has .h files and .cpp files but no CMakeLists. How do I include (and build?) this library?
(5) (This is the most important question for me!) If the external library has .h files and either .lib or .dll files, how do I include this library? In both cases! (lib/dll)
Thanks in advance for any and all replies!
Best regards
Edvin
If you want to do the quick-and-dirty way you can just
create a super-repo with all your dependencies (or alternatively, write a script that downloads or clones them)
write a shell script that builds all the source-only dependencies
hardcode the include dirs, libraries and compile flags of the dependencies into the CMakeLists.txt of you main project (well, relative to super-repo root)
build the dependencies before you configure your main project
If a dependency has no CMakeLists.txt or any other build script suitable for your system, you need to find or write one.
If you want to do it elegantly and reusable you need to study the topics of CMake config-modules. For a quick introduction please check out this Stackoverflow answer: How to use CMake to find and link to a library using install-export and find_package?
You will need to write config-modules for the dependencies (or find a fork which has it). Your main project will contain only a list of find_package commands and you list your dependencies with the target_link_libraries command and all other details will be taken care by the config modules.
Generally you don't need to include third-party projects directly into your main project's CMakeLists (add_subdirectory or ExternalProject_add) since you will not be working on them so they will not change. There's no need to clutter your IDE workspace with them. It's best to download/clone/build/install them with a separate script before you configure your main project.

CMake: automatically find target dependencies in other CMake projects

If we have a case of highly decentralized development environment, where there are many repositories and projects, is there an existing functionality in CMake that automatically finds dependencies between targets without a top level CMake file?
The workflow is something like this, you specify a directory and all targets are default-configured in the given tree. Then you can go and build any of the projects. I am looking for a behavior similar to that when you build the Android OS.
There is no build-time dependency tracking in CMake across different projects. For this case you need to have a project on the top-level which adds all the subdirectories, so that the target names are available inside a single CMake project.
I am aware of one helper script around CMake which provides the required inter-project dependencies: https://github.com/aldebaran/qibuild
I would say that is getting close to a mature code base. However, it requires additional descriptor files for each project. Might be worth to have a look at it.