How to use cmake to organize libraries feature - build

In a project, there are many libraries:
there are dependencies between libraries.
each library has some features to enable/disable at build time.
one library feature may depends on another library's specified feature.
Problem:
How to use cmake to organize such feature based dependency.
How to configure different target with different feature list.

Use option() and set() with CACHE keyword commands to define build options.
Use if(OPTION_NAME) to test for option value. Use add_dependencies() inside if block to make some of them optional.
Use configure_file() to replace template file (config headerm for example) with options' values.

Related

How add compile-only dependency with cmake

I need to add a compile-only dependency to external lib my in CMake file - referred to as the_lib later in the question. By saying compile-only dependency I mean propagating compile-time properties, most importantly -I rules.
The library in question is created as a library target using add_library. Normally I would simply use target_link_libraries(my_exec the_lib), but this adds both compile-time and link-time properties, i.e., this adds both -I and -l/-L rules to compilation commands, while I only need -I. (If anyone is curios why I need such a setup, this is because reasons.)
Please note, target_include_directories with something like ${the_lib_SOURCE_DIR} (or anything similar to that effect) would not work for me, because it wouldn't add include directories necessary for the lib. I need something like ${the_lib_INCLUDE_DIRS} where the_lib_INCLUDE_DIRS would be populated to as -I rules required by the_lib - but I didn't find any variable which would match that.
It is worth noting that I can't (or shan't) modify the the_lib.
I need to add a compile-only dependency to external lib my in CMake
file
By an "external" lib, I take you to mean one that is not part of the same project -- i.e. one that is not configured within the scope of the same CMake build system as the target you're trying to build.
[...] Normally
dependencies are added with target_link_libraries(my_exec the_lib),
but this adds both compile-time and link-time dependency.
Well no, not necessarily. As it says on the tin, that adds a link dependency, which you can think of as an -l option. To the best of my knowledge, it does not generate any -I options for external libraries, or otherwise have any impact on the compilation phase with regard to external libraries. Similarly, as far as I am aware, it propagates transitive dependencies of any sort only when the the added library is another target configured and built by the same build system. That is, only for internal libraries, not external ones.
Please note, target_include_directories with something like
${the_lib_SOURCE_DIR} (or anything similar to that effect) would not
work for me, because it wouldn't add include directories necessary for
the lib. I need something like ${the_lib_INCLUDE_DIRS} where
the_lib_INCLUDE_DIRS would be populated to as -I rules required by
the_lib - but I didn't find any variable which would match that.
I think you're asking for the include directories that would be necessary to successfully use the_lib's headers, in a situation where those headers have their own external dependencies. There's a reason why you don't find a variable appropriate to that: there is no consistent or standard way to obtain that information for external libraries. The available techniques depend on the_lib. They will include some, but probably not all, of the following:
Use a CMake macro provided with the_lib or with CMake itself to define a CMake variable conveying the wanted information.
Use pkg-config to read the information from a pkg-config entry associated with the_lib.
Use some technique idiosynchratic to the_lib, along the line of Python's python-config.
Analyze the_lib's headers to determine the external packages they depend upon, and search explicitly, individually for those packages' headers.
Require the user to specify manually for any required packages that are not in the default include path.
There is no magic variable or function in CMake to automatically glean such information, because generally speaking, it is not available from the_lib's headers themselves. Note, too, that most of those would depend on the_lib's include dependencies being installed in specific places anticipated at the_lib's build time. That's pretty hard to ensure.

CMake find_package not handling multi-configurations

We're using Jenkins 2.60.2 and CMake 3.9.1 to automate our build system. This all works well for multiple versions of build tools, architectures and debug/release targets (if ALL configurations have been built and installed, so both Debug AND Release).
A Debug-only configuration that uses find_package() typically ignores the CMAKE_BUILD_TYPE at discovery. Internally the scripts search for file and libraries and store the locations in variables. At the end of the script, the variables are scanned for _NOTFOUND strings, which is the result of a file or library not found in all the reference paths/hints. So essentially a find_package() will fail if the Release lib can not be found, and mark the whole package as not installed properly, even though the build is only strictly interested in the Debug target.
Typically the XXXConfig.cmake files use a call to find_package_handle_standard_args(.. PATH_TO_LIB) that scans for _NOTFOUND strings in the path variables to the libraries. These variables typically get set to _NOTFOUND by earlier calls to find_library(PATH_TO_LIB libname ..). For more information I refer to the CMake docs.
The user can indeed tag debug libraries with 'debug' and release libs with 'optimized', but this does not seem to help during the lib discovery and is only used during linking.
Anyone knows how to handle this properly?
Kind regards
This is one of the unfortunate shortcomings of the classic use of find_package.
Note that find_package also allows a different mode of operation, based on config file packages, which is well-suited to address this particular problem, but will require some changes to your build system. You will need config scripts for all your libraries (CMake can generate them for you if the libraries are themselves also built by CMake; if not, this can be a bit of a hassle), and depending targets will refer to those libraries via imported targets instead of variables (which usually makes things way easier for those depending targets). I would strongly recommend you adopt this as the long-term solution.
If for some reason you cannot do this, you will have to modify your find scripts. A common technique is to search for debug and release binaries separately, but then combine the find libraries from those calls into a single variable (together with the debug and optimized specifiers) and then have that variable as an argument to find_package_handle_standard_args. That way, as long as one of the two is found, your find script will be happy, although you might not be able to build all possible configurations in the end. Alternatively, you can also skip the call to find_package_handle_standard_args altogether and manually implement your own logic for detecting whether the library was found. As you can see from the manpage for that function, it does mostly boilerplate stuff and can be easily replaced by a more flexible, handwritten implementation if necessary.

target_compile_options() for only C++ files?

Is it possible to use target_compile_options() for only C++ files? I'd like to use it for a target that is uses as a dependency for other applications so that the library can propagate its compiler flags to those apps. However, there are certain flags, such as -std=c++14, that cause the build to fail if they are used with C or ObjC files.
I've read that I should CXX_FLAGS instead to only add those flags to C++ files, however this won't (automatically) propagate through cmake's packages system.
Solution
You can do this with generator expressions:
target_compile_options(MyLib PUBLIC $<$<COMPILE_LANGUAGE:CXX>:-std=c++14>)
Alternative
But the more platform independent way of doing it in this particular case would be to use target_compile_features(). I'm not sure which compiler feature you're using, so the following is only an example:
target_compile_features(MyLib PUBLIC cxx_explicit_conversions)
I've read that I should CXX_FLAGS instead to only add those flags to C++ files, however this won't (automatically) propagate through cmake's packages system.
In CMake, if you use any function prefixed with target_* it only has the scope of one build target. If you want your whole project to build with given standard you can use set(CMAKE_CXX_STANDARD 14). If some of the targets requires another setting, you can override it locally with the target prefixed function and CXX_STANDARD variable (it's default value is ${CMAKE_CXX_STANDARD}).
As pointed out by RAM this option is available since v3.1. But judging by https://repology.org/project/cmake/versions it is already widely supported.
It is also often pointed out that you should not set cpp version fixed. I have not formed my opinion on this yet, but you can but you can use the keyword CACHED to make it overridable.

exporting cmake build options to external project

I have got a C++ Library A. A can be installed in a multitude of ways depending on which external dependencies are used. This also changes depending on whether the library is build in debug or release mode. This means that some features might not be available or some types/defines need to be changed in order to link to the library.
Now I want to link A to a local project B. I have set up a ProjectConfig.cmake file for A which is located at /path/lib/CMake/A/AConfig.cmake which is found and works fine in a minimal build. However as soon as I add definitions to the compilation or include some packages, this information is not automatically exported. This makes linking to A hard as for example I need to know that OpenMP was used to have a coherent build.
Is there a way to export this information the same way the ProjectConfig.cmake does it?
Generate the ProjectConfig.cmake file to contain what you need it to contain.
http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html
Note that if you set the usage requirements of the targets, you have less need to generate the file.
http://www.cmake.org/cmake/help/v3.0/manual/cmake-buildsystem.7.html

Building Boost without filename decorations?

The default naming convention for the Boost C++ libraries is:
libboost_regex-vc71-mt-d-1_34.lib
where all libraries are built into the same directory. I'd like to modify the build process so that the filename does not contain the target architecture or build type (versions are okay). I want the file to end up in a different directory depending on the architecture being built for:
vc71/debug/libboost-1_34.lib
vc71/release/libboost-1_34.lib
Any idea on how to do this?
You can remove all decoration from the library filenames by passing "--layout=system". Your example above shows "vc71/release" paths -- there's no out-of-box way to get this layout. You can do that with a bit of hackign. In Jamroot, find the 'stage-proper' target, which specifies the location as:
<location>$(stage-locate)/lib
You can modify that to specify different locations depending on properties, e.g:
<variant>release:<location>$(stage-locate)/lib/release
<variant>debug:<location>$(stage-locate)/lib/debug
Please see Boost.Build website for more documentation
I don't know of any way to do that with the Boost build system, but you could use a fairly simple script to move and rename them without too much difficulty.
On the other hand, with most Windows compilers, you seldom need to concern yourself with the library filenames because, for those libraries that require a separate binary, Boost employs auto-linking:
Most Windows compilers and linkers have so-called “auto-linking support,” which eliminates the second challenge. Special code in Boost header files detects your compiler options and uses that information to encode the name of the correct library into your object files; the linker selects the library with that name from the directories you've told it to search.
Moving and renaming the files would break that.